Compare commits
43 Commits
7e8103565d
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| dc839e098c | |||
| 380145a986 | |||
| 0cce9d9bce | |||
| 572f4be896 | |||
| 8d45a93e6e | |||
| 09266e678f | |||
| cbf0d6a9da | |||
| 712e214f61 | |||
| fe4a585b6b | |||
| 9a4932e629 | |||
| 6315589fa1 | |||
| 1f07cc38ba | |||
| e09a00a4ba | |||
| 738ba5bd37 | |||
| 66d49ea8d5 | |||
| f02ece22fa | |||
| 2e97aef842 | |||
| 9e7955495f | |||
| ef15201f21 | |||
| 85f4957661 | |||
| 2756957f9b | |||
| 4f2aad8065 | |||
| f2b7817cac | |||
| 0a3d82a562 | |||
| 6363bc3bd1 | |||
| 868550703f | |||
| eb3c3814ec | |||
| fa42b20136 | |||
| 0b23707041 | |||
| b0deb958d2 | |||
| d03910f6f0 | |||
| a6f67d3f0d | |||
| 9fb8ec9454 | |||
| 99f8ae059c | |||
| 1e8e14ed67 | |||
| 11e948d500 | |||
| 31651dc96a | |||
| 34f0b1fb89 | |||
| 7963813034 | |||
| 49cf6e4237 | |||
| 6c9a786926 | |||
| a17a39a9a4 | |||
| e019470687 |
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,2 +1 @@
|
||||
*.dll filter=lfs diff=lfs merge=lfs -text
|
||||
*.pdf filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
.zig-cache
|
||||
zig-out
|
||||
zig-pkg
|
||||
|
||||
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -1,5 +1,8 @@
|
||||
{
|
||||
"files.exclude":{
|
||||
"files.exclude": {
|
||||
"**/.zig-cache": true,
|
||||
},
|
||||
"files.associations": {
|
||||
"**/*.{c,h}": "c",
|
||||
},
|
||||
}
|
||||
|
||||
88
README.md
Normal file
88
README.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# Building my own castle
|
||||
|
||||
I'm building my own castle in Zig ⚡.
|
||||
|
||||
This is a collection of experimental, recreational or actually useful libraries,
|
||||
which I may or may not use as a part of some other projects. Most of them are
|
||||
work in progress and might remain so indefinitely.
|
||||
|
||||
## cjit
|
||||
|
||||
JIT compiler for the C language inspired by Fabrice Bellard's Tiny C Compiler
|
||||
(TCC). Meant to compile x86_64 and aarch64 for Windows and Linux straight to
|
||||
virtual memory, without the ability to output an executable file or library. C
|
||||
standard library is not fully supported, only some includes and builtins (see
|
||||
[src/includes](packages/cjit/src/includes)).
|
||||
|
||||
It's in very early stage and cannot be used as of now. Currently, parts of the
|
||||
tokenizer, x86_64 emit code and relocation logic are implemented. Aarch64 would
|
||||
come after x86_64 is fully implemented and actually works.
|
||||
|
||||
You can see an example of how this library is supposed to be used in
|
||||
[test/root.zig](packages/cjit/test/root.zig).
|
||||
|
||||
## js
|
||||
|
||||
Zig bindings for Fabrice Bellard's QuickJS. Original C sources are included and
|
||||
compiled with Zig's build system. The bindings are not complete, but usable.
|
||||
|
||||
## media
|
||||
|
||||
Set of utilities for decoding and encoding media files. As of now, no purely Zig
|
||||
decoder/encoder exists, which would be the goal. There is fairly comprehensive
|
||||
PNG chunk decoder, though.
|
||||
|
||||
Bindings for stb_image.h from Sean T. Barrett's single-file C libraries are
|
||||
included. They can be used to decode PNG, JPEG and HDR images and support Zig's
|
||||
allocator interface in a thread-safe manner.
|
||||
|
||||
This package is actually used by my other project, codenamed
|
||||
[voxel-game](/:root/renati/voxel-game).
|
||||
|
||||
## myid
|
||||
|
||||
Remnants of a project meant to provide a simpler alternative to OpenID Connect
|
||||
(OIDC). It contains an HTTP server, which has since been improved and resides
|
||||
in *web* package.
|
||||
|
||||
The ideas behind *myid* were developed further in another project I created
|
||||
using TypeScript and Bun as a runtime. I might backport them back to Zig when I
|
||||
find the time and motivation, which would be implemented on top of the HTTP
|
||||
server from the *web* package.
|
||||
|
||||
## vecmath
|
||||
|
||||
Vector math library with support for vectors, matrices and ×8 SIMD operations
|
||||
(utilizing SOA layout).
|
||||
|
||||
This package is actually used by my other project, codenamed
|
||||
[voxel-game](/:root/renati/voxel-game) and is probably the most complete package
|
||||
in this repository.
|
||||
|
||||
## web
|
||||
|
||||
An HTTP server library and collection of other utilities commonly associated
|
||||
with web technologies.
|
||||
|
||||
The HTTP server implements HTTP version 1.1 using worker threads and supports
|
||||
SSL via OpenSSL library. The server is very low level and does not provide any
|
||||
logic to handle proper HTTP semantics. It is up to the user of the HTTP server
|
||||
library to respect (or not) all HTTP methods and headers. The server is designed
|
||||
or Linux only and uses Linux syscalls directly in its implementation.
|
||||
|
||||
The other utilities include:
|
||||
|
||||
- HTTP/1.1 request parser, which is part of the HTTP server, but can be used
|
||||
independently,
|
||||
- OpenSSL bindings, which are used by the HTTP server, but can be used
|
||||
independently (full mechanical C translation is provided and partial manual
|
||||
Zig translation),
|
||||
- UUID type and generators for v4, v5 and v7,
|
||||
- Generic ID type, a wrapper around UUID (or anything that is 16 bytes long)
|
||||
that can be instantiated for each distinct use of ID with a tag to help avoid
|
||||
type confusion at compile time.
|
||||
|
||||
## x11
|
||||
|
||||
Zig bindings for Xlib (aka libX11), a C library implementing a client of the X
|
||||
Window System protocol. The bindings are not complete.
|
||||
44
packages/cjit/build.zig
Normal file
44
packages/cjit/build.zig
Normal file
@@ -0,0 +1,44 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{
|
||||
.whitelist = &.{
|
||||
.{
|
||||
.cpu_arch = .x86_64,
|
||||
.os_tag = .windows,
|
||||
.abi = .gnu,
|
||||
},
|
||||
.{
|
||||
.cpu_arch = .x86_64,
|
||||
.os_tag = .linux,
|
||||
.abi = .gnu,
|
||||
},
|
||||
},
|
||||
});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const module = b.addModule("cjit", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
});
|
||||
|
||||
const module_internal_test = b.addTest(.{ .root_module = module });
|
||||
const run_internal_test = b.addRunArtifact(module_internal_test);
|
||||
|
||||
const module_external_test = b.addTest(.{
|
||||
.root_module = b.createModule(.{
|
||||
.target = b.resolveTargetQuery(.{}),
|
||||
.optimize = .Debug,
|
||||
.root_source_file = b.path("test/root.zig"),
|
||||
.imports = &.{
|
||||
.{ .name = "cjit", .module = module },
|
||||
},
|
||||
}),
|
||||
});
|
||||
const run_external_test = b.addRunArtifact(module_external_test);
|
||||
|
||||
const step_test = b.step("test", "Run tests");
|
||||
step_test.dependOn(&run_internal_test.step);
|
||||
step_test.dependOn(&run_external_test.step);
|
||||
}
|
||||
11
packages/cjit/build.zig.zon
Normal file
11
packages/cjit/build.zig.zon
Normal file
@@ -0,0 +1,11 @@
|
||||
.{
|
||||
.name = .cjit,
|
||||
.version = "0.0.0",
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.paths = .{
|
||||
"src",
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
},
|
||||
.fingerprint = 0xaec0accc19243440,
|
||||
}
|
||||
154
packages/cjit/src/Runtime.zig
Normal file
154
packages/cjit/src/Runtime.zig
Normal file
@@ -0,0 +1,154 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Self = @This();
|
||||
|
||||
const tokens = @import("tokens.zig");
|
||||
const types = @import("types.zig");
|
||||
|
||||
const Sections = @import("Sections.zig");
|
||||
const StackValue = @import("StackValue.zig");
|
||||
const Tokenizer = tokens.Tokenizer;
|
||||
const Type = types.Type;
|
||||
|
||||
pub const virtual_stack_size = 256;
|
||||
|
||||
allocator: std.mem.Allocator,
|
||||
arena: std.heap.ArenaAllocator,
|
||||
symbols_needed: std.StringHashMapUnmanaged(ExternSymbol) = .{},
|
||||
symbols_provided: std.StringHashMapUnmanaged(InternSymbol) = .{},
|
||||
symbols_located: std.StringHashMapUnmanaged(LocatedSymbol) = .{},
|
||||
relocation_table: std.ArrayList(Relocation) = .{},
|
||||
/// Bounded, preallocated with the capacity of `virtual_stack_size`.
|
||||
virtual_stack: std.ArrayList(StackValue),
|
||||
tokenizer: Tokenizer,
|
||||
sections: Sections = .{},
|
||||
|
||||
pub const ExternSymbol = struct {
|
||||
name: []const u8,
|
||||
c_type: Type,
|
||||
ptr: ?*anyopaque,
|
||||
};
|
||||
|
||||
pub const InternSymbol = struct {
|
||||
name: []const u8,
|
||||
public: bool,
|
||||
c_type: Type,
|
||||
location: Location,
|
||||
};
|
||||
|
||||
pub const Location = union(enum) {
|
||||
text: usize,
|
||||
data: usize,
|
||||
rodata: usize,
|
||||
};
|
||||
|
||||
pub const LocatedSymbol = struct {
|
||||
name: []const u8,
|
||||
public: bool,
|
||||
c_type: Type,
|
||||
ptr: ?*anyopaque,
|
||||
};
|
||||
|
||||
pub const Relocation = struct {
|
||||
addr: usize,
|
||||
location: Location,
|
||||
type: RelocationType,
|
||||
};
|
||||
|
||||
pub const RelocationType = enum {
|
||||
global_offset_table,
|
||||
rip_disp32,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) !Self {
|
||||
var arena: std.heap.ArenaAllocator = .init(allocator);
|
||||
const arena_allocator = arena.allocator();
|
||||
errdefer arena.deinit();
|
||||
|
||||
const virtual_stack_buffer = try arena_allocator.alloc(StackValue, virtual_stack_size);
|
||||
const tokenizer = try Tokenizer.init(arena_allocator);
|
||||
|
||||
return .{
|
||||
.allocator = allocator,
|
||||
.arena = arena,
|
||||
.virtual_stack = .initBuffer(virtual_stack_buffer),
|
||||
.tokenizer = tokenizer,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.symbols_needed.deinit(self.allocator);
|
||||
self.symbols_provided.deinit(self.allocator);
|
||||
self.symbols_located.deinit(self.allocator);
|
||||
self.relocation_table.deinit(self.allocator);
|
||||
self.arena.deinit();
|
||||
// TODO deinit sections (stage-depentent)
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn compile(self: *Self, filename: []const u8, code: []const u8) !void {
|
||||
self.tokenizer.setSource(filename, code);
|
||||
while (try self.tokenizer.nextToken(self.arena.allocator())) |token| {
|
||||
_ = token;
|
||||
std.debug.panic("Not implemented", .{});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setSymbol(self: *Self, comptime T: type, name: []const u8, ptr: *const T) !void {
|
||||
const symbol = self.symbols_needed.getPtr(name) orelse return error.SymbolNotFound;
|
||||
if (!types.isCompatible(T, symbol.c_type)) return error.IncompatibleType;
|
||||
|
||||
// TODO Figure out const-correctness
|
||||
symbol.ptr = @ptrCast(@constCast(ptr));
|
||||
}
|
||||
|
||||
pub fn link(self: *Self) !void {
|
||||
try self.sections.relocateSections(self.allocator);
|
||||
|
||||
const text_base = @intFromPtr(self.sections.text.data.items.ptr);
|
||||
const data_base = @intFromPtr(self.sections.data.data.items.ptr);
|
||||
const rodata_base = @intFromPtr(self.sections.rodata.data.items.ptr);
|
||||
|
||||
for (self.relocation_table.items) |relocation| {
|
||||
const target_addr = switch (relocation.location) {
|
||||
.text => |offset| text_base + offset,
|
||||
.data => |offset| data_base + offset,
|
||||
.rodata => |offset| rodata_base + offset,
|
||||
};
|
||||
|
||||
switch (relocation.type) {
|
||||
.global_offset_table => {
|
||||
std.mem.writeInt(
|
||||
usize,
|
||||
self.sections.rodata.data.items[relocation.addr..][0..@sizeOf(usize)],
|
||||
target_addr,
|
||||
builtin.cpu.arch.endian(),
|
||||
);
|
||||
},
|
||||
.rip_disp32 => {
|
||||
const rip = text_base + relocation.addr + 4;
|
||||
const disp64: isize = @bitCast(rip -% target_addr);
|
||||
const disp32 = std.math.cast(i32, disp64) orelse return error.RelocationError;
|
||||
std.mem.writeInt(
|
||||
i32,
|
||||
self.sections.text.data.items[relocation.addr..][0..4],
|
||||
disp32,
|
||||
builtin.cpu.arch.endian(),
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
self.relocation_table.clearAndFree(self.allocator);
|
||||
|
||||
try self.sections.protectSections();
|
||||
}
|
||||
|
||||
pub fn getSymbol(self: *const Self, comptime T: type, name: []const u8) ?*const T {
|
||||
const symbol = self.symbols_located.get(name) orelse return null;
|
||||
return @ptrCast(@alignCast(symbol.ptr));
|
||||
}
|
||||
|
||||
fn create(self: *Self, comptime T: type) error{OutOfMemory}!*T {
|
||||
return self.arena.allocator().create(T);
|
||||
}
|
||||
185
packages/cjit/src/Sections.zig
Normal file
185
packages/cjit/src/Sections.zig
Normal file
@@ -0,0 +1,185 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
const Self = @This();
|
||||
|
||||
text: Section = .{ .protection = .executable },
|
||||
data: Section = .{ .protection = .read_write },
|
||||
rodata: Section = .{ .protection = .read_only },
|
||||
|
||||
pub const Section = struct {
|
||||
data: std.ArrayList(u8) = .{},
|
||||
protection: Protection,
|
||||
|
||||
pub const Protection = enum {
|
||||
executable,
|
||||
read_only,
|
||||
read_write,
|
||||
};
|
||||
|
||||
pub fn writeValue(self: *Section, value: anytype, allocator: std.mem.Allocator) !void {
|
||||
const T = @TypeOf(value);
|
||||
std.debug.assert(std.meta.hasUniqueRepresentation(T));
|
||||
const bytes = std.mem.asBytes(&value);
|
||||
try self.writeBytes(bytes, allocator);
|
||||
}
|
||||
|
||||
pub fn writeByte(self: *Section, byte: u8, allocator: std.mem.Allocator) !void {
|
||||
try self.data.append(allocator, byte);
|
||||
}
|
||||
|
||||
pub fn writeBytes(self: *Section, data: []const u8, allocator: std.mem.Allocator) !void {
|
||||
try self.data.appendSlice(allocator, data);
|
||||
}
|
||||
|
||||
pub fn alignForward(self: *Section, alignment: u16, allocator: std.mem.Allocator) !void {
|
||||
const ptr = self.data.items.len;
|
||||
const ptr_aligned = std.mem.alignForward(usize, ptr, alignment);
|
||||
const padding = ptr_aligned - ptr;
|
||||
|
||||
try self.data.appendNTimes(allocator, 0, padding);
|
||||
}
|
||||
|
||||
pub fn pageCount(self: Section) usize {
|
||||
const page_size = std.heap.pageSize();
|
||||
const section_size = self.data.items.len;
|
||||
return @divFloor(section_size + page_size - 1, page_size);
|
||||
}
|
||||
};
|
||||
|
||||
pub fn relocateSections(self: *Self, allocator: std.mem.Allocator) !void {
|
||||
const page_size = std.heap.pageSize();
|
||||
|
||||
const text_pages = self.text.pageCount();
|
||||
const text_bytes = text_pages * page_size;
|
||||
|
||||
const data_pages = self.data.pageCount();
|
||||
const data_bytes = data_pages * page_size;
|
||||
|
||||
const rodata_pages = self.rodata.pageCount();
|
||||
const rodata_bytes = rodata_pages * page_size;
|
||||
|
||||
const total_pages = text_pages + data_pages + rodata_pages;
|
||||
const total_bytes = text_bytes + data_bytes + rodata_bytes;
|
||||
std.debug.assert(total_bytes == total_pages * page_size);
|
||||
|
||||
const ptr: [*]u8 = sw: switch (builtin.os.tag) {
|
||||
.windows => {
|
||||
const windows = std.os.windows;
|
||||
const ntdll = windows.ntdll;
|
||||
|
||||
var base_addr: ?*anyopaque = null;
|
||||
var size: windows.SIZE_T = total_bytes;
|
||||
const status = ntdll.NtAllocateVirtualMemory(
|
||||
windows.GetCurrentProcess(),
|
||||
@ptrCast(&base_addr),
|
||||
0,
|
||||
&size,
|
||||
windows.MEM_COMMIT | windows.MEM_RESERVE,
|
||||
windows.PAGE_READWRITE,
|
||||
);
|
||||
|
||||
if (status == .SUCCESS) {
|
||||
break :sw @ptrCast(base_addr);
|
||||
} else {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
},
|
||||
.linux => {
|
||||
const linux = std.os.linux;
|
||||
|
||||
const rc = linux.mmap(
|
||||
null,
|
||||
total_bytes,
|
||||
linux.PROT.READ | linux.PROT.WRITE,
|
||||
.{ .TYPE = .PRIVATE, .ANONYMOUS = true },
|
||||
-1,
|
||||
0,
|
||||
);
|
||||
const status: linux.E = .init(rc);
|
||||
|
||||
if (status == .SUCCESS) {
|
||||
break :sw @ptrFromInt(rc);
|
||||
} else {
|
||||
return error.OutOfMemory;
|
||||
}
|
||||
},
|
||||
else => @compileError("Operating system " ++ @tagName(builtin.os.tag) ++ " not supported"),
|
||||
};
|
||||
|
||||
const text_slice = ptr[0..text_bytes];
|
||||
const data_slice = ptr[text_bytes .. text_bytes + data_bytes];
|
||||
const rodata_slice = ptr[text_bytes + data_bytes .. text_bytes + data_bytes + rodata_bytes];
|
||||
|
||||
@memcpy(text_slice[0..self.text.data.items.len], self.text.data.items);
|
||||
@memcpy(data_slice[0..self.data.data.items.len], self.data.data.items);
|
||||
@memcpy(rodata_slice[0..self.rodata.data.items.len], self.rodata.data.items);
|
||||
|
||||
self.text.data.clearAndFree(allocator);
|
||||
self.data.data.clearAndFree(allocator);
|
||||
self.rodata.data.clearAndFree(allocator);
|
||||
|
||||
self.text.data = .{ .items = text_slice, .capacity = text_bytes };
|
||||
self.data.data = .{ .items = data_slice, .capacity = data_bytes };
|
||||
self.rodata.data = .{ .items = rodata_slice, .capacity = rodata_bytes };
|
||||
}
|
||||
|
||||
pub fn protectSections(self: *Self) !void {
|
||||
const sections = [_]*Section{ &self.text, &self.data, &self.rodata };
|
||||
|
||||
for (sections) |section| {
|
||||
switch (builtin.os.tag) {
|
||||
.windows => {
|
||||
const windows = std.os.windows;
|
||||
const ntdll = windows.ntdll;
|
||||
|
||||
const protection = switch (section.protection) {
|
||||
.executable => windows.PAGE_EXECUTE,
|
||||
.read_only => windows.PAGE_READONLY,
|
||||
.read_write => windows.PAGE_READWRITE,
|
||||
};
|
||||
|
||||
var base_addr: ?*anyopaque = section.data.items.ptr;
|
||||
var size: windows.SIZE_T = section.data.capacity;
|
||||
var old_protection: u32 = undefined;
|
||||
|
||||
const status = ntdll.NtProtectVirtualMemory(
|
||||
windows.GetCurrentProcess(),
|
||||
&base_addr,
|
||||
&size,
|
||||
protection,
|
||||
&old_protection,
|
||||
);
|
||||
|
||||
if (status != .SUCCESS) {
|
||||
return error.ProtectionError;
|
||||
}
|
||||
},
|
||||
.linux => {
|
||||
const linux = std.os.linux;
|
||||
|
||||
const protection: usize = switch (section.protection) {
|
||||
.executable => linux.PROT.EXEC,
|
||||
.read_only => linux.PROT.READ,
|
||||
.read_write => linux.PROT.READ | linux.PROT.WRITE,
|
||||
};
|
||||
|
||||
const rc = linux.mprotect(
|
||||
section.data.items.ptr,
|
||||
section.data.capacity,
|
||||
protection,
|
||||
);
|
||||
const status: linux.E = .init(rc);
|
||||
|
||||
if (status != .SUCCESS) {
|
||||
return error.ProtectionError;
|
||||
}
|
||||
},
|
||||
else => @compileError("Operating system " ++ @tagName(builtin.os.tag) ++ " not supported"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn freeSections(self: *Self) void {
|
||||
_ = self;
|
||||
std.debug.panic("Not implemented", .{});
|
||||
}
|
||||
20
packages/cjit/src/StackValue.zig
Normal file
20
packages/cjit/src/StackValue.zig
Normal file
@@ -0,0 +1,20 @@
|
||||
const std = @import("std");
|
||||
const Self = @This();
|
||||
|
||||
const types = @import("types.zig");
|
||||
const x86_64 = @import("x86_64.zig");
|
||||
|
||||
const Register = x86_64.Register;
|
||||
const Type = types.Type;
|
||||
|
||||
pub const Value = union(enum) {
|
||||
register: Register,
|
||||
constant: u64,
|
||||
symbol: []const u8,
|
||||
/// Displacement in bytes from current value of base pointer register.
|
||||
stack: i32,
|
||||
cpu_flags: void,
|
||||
};
|
||||
|
||||
c_type: Type,
|
||||
value: Value,
|
||||
31
packages/cjit/src/includes/builtin.h
Normal file
31
packages/cjit/src/includes/builtin.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#define abs(value) __builtin_abs(value)
|
||||
#define byteswap(value) __builtin_byteswap(value)
|
||||
#define ceil(value) __builtin_ceil(value)
|
||||
#define clz(value) __builtin_clz(value)
|
||||
#define containerof(ptr, type, member) __builtin_containerof(ptr, type, member)
|
||||
#define cos(value) __builtin_cos(value)
|
||||
#define ctz(value) __builtin_ctz(value)
|
||||
#define embedfile(path) __builtin_embedfile(path)
|
||||
#define exp(value) __builtin_exp(value)
|
||||
#define exp2(value) __builtin_exp2(value)
|
||||
#define floor(value) __builtin_floor(value)
|
||||
#define frameaddress() __builtin_frameaddress()
|
||||
#define log(value) __builtin_log(value)
|
||||
#define log10(value) __builtin_log10(value)
|
||||
#define log2(value) __builtin_log2(value)
|
||||
#define max(...) __builtin_max(__VA_ARGS__)
|
||||
#define memcpy(dest, src, count) __builtin_memcpy(dest, src, count)
|
||||
#define memmove(dest, src, count) __builtin_memmove(dest, src, count)
|
||||
#define memset(value) __builtin_memset(value)
|
||||
#define min(...) __builtin_min(__VA_ARGS__)
|
||||
#define popcount(value) __builtin_popcount(value)
|
||||
#define returnaddress() __builtin_returnaddress()
|
||||
#define round(value) __builtin_round(value)
|
||||
#define sin(value) __builtin_sin(value)
|
||||
#define sqrt(value) __builtin_sqrt(value)
|
||||
#define tan(value) __builtin_tan(value)
|
||||
#define trunc(value) __builtin_trunc(value)
|
||||
#define typename(type) __builtin_typename(type)
|
||||
#define typeof(...) __builtin_typeof(__VA_ARGS__)
|
||||
7
packages/cjit/src/includes/stdalign.h
Normal file
7
packages/cjit/src/includes/stdalign.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define alignas _Alignas
|
||||
#define alignof _Alignof
|
||||
|
||||
#define __alignas_is_defined 1
|
||||
#define __alignof_is_defined 1
|
||||
7
packages/cjit/src/includes/stdbool.h
Normal file
7
packages/cjit/src/includes/stdbool.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define bool _Bool
|
||||
|
||||
#define true 1
|
||||
#define false 0
|
||||
#define __bool_true_false_are_defined 1
|
||||
9
packages/cjit/src/includes/stddef.h
Normal file
9
packages/cjit/src/includes/stddef.h
Normal file
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
typedef long ptrdiff_t;
|
||||
typedef long max_align_t;
|
||||
typedef unsigned long size_t;
|
||||
|
||||
#define NULL ((void *)0)
|
||||
|
||||
#define offsetof(type, member) __builtin_offsetof(type, member)
|
||||
118
packages/cjit/src/includes/stdint.h
Normal file
118
packages/cjit/src/includes/stdint.h
Normal file
@@ -0,0 +1,118 @@
|
||||
#pragma once
|
||||
|
||||
typedef signed char int8_t;
|
||||
typedef short int16_t;
|
||||
typedef int int32_t;
|
||||
typedef long int64_t;
|
||||
|
||||
typedef int8_t int_fast8_t;
|
||||
typedef int16_t int_fast16_t;
|
||||
typedef int32_t int_fast32_t;
|
||||
typedef int64_t int_fast64_t;
|
||||
|
||||
typedef int8_t int_least8_t;
|
||||
typedef int16_t int_least16_t;
|
||||
typedef int32_t int_least32_t;
|
||||
typedef int64_t int_least64_t;
|
||||
|
||||
typedef int64_t intmax_t;
|
||||
typedef int64_t intptr_t;
|
||||
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned long uint64_t;
|
||||
|
||||
typedef uint8_t uint_fast8_t;
|
||||
typedef uint16_t uint_fast16_t;
|
||||
typedef uint32_t uint_fast32_t;
|
||||
typedef uint64_t uint_fast64_t;
|
||||
|
||||
typedef uint8_t uint_least8_t;
|
||||
typedef uint16_t uint_least16_t;
|
||||
typedef uint32_t uint_least32_t;
|
||||
typedef uint64_t uint_least64_t;
|
||||
|
||||
typedef uint64_t uintmax_t;
|
||||
typedef uint64_t uintptr_t;
|
||||
|
||||
#define INT8_MIN (-0x80)
|
||||
#define INT16_MIN (-0x8000)
|
||||
#define INT32_MIN (-0x80000000)
|
||||
#define INT64_MIN (-0x8000000000000000L)
|
||||
|
||||
#define INT_FAST8_MIN INT8_MIN
|
||||
#define INT_FAST16_MIN INT16_MIN
|
||||
#define INT_FAST32_MIN INT32_MIN
|
||||
#define INT_FAST64_MIN INT64_MIN
|
||||
|
||||
#define INT_LEAST8_MIN INT8_MIN
|
||||
#define INT_LEAST16_MIN INT16_MIN
|
||||
#define INT_LEAST32_MIN INT32_MIN
|
||||
#define INT_LEAST64_MIN INT64_MIN
|
||||
|
||||
#define INTPTR_MIN INT64_MIN
|
||||
#define INTMAX_MIN INT64_MIN
|
||||
|
||||
#define INT8_MAX (0x7F)
|
||||
#define INT16_MAX (0x7FFF)
|
||||
#define INT32_MAX (0x7FFFFFFF)
|
||||
#define INT64_MAX (0x7FFFFFFFFFFFFFFFL)
|
||||
|
||||
#define INT_FAST8_MAX INT8_MAX
|
||||
#define INT_FAST16_MAX INT16_MAX
|
||||
#define INT_FAST32_MAX INT32_MAX
|
||||
#define INT_FAST64_MAX INT64_MAX
|
||||
|
||||
#define INT_LEAST8_MAX INT8_MAX
|
||||
#define INT_LEAST16_MAX INT16_MAX
|
||||
#define INT_LEAST32_MAX INT32_MAX
|
||||
#define INT_LEAST64_MAX INT64_MAX
|
||||
|
||||
#define INTPTR_MAX INT64_MAX
|
||||
#define INTMAX_MAX INT64_MAX
|
||||
|
||||
#define UINT8_MAX (0xFF)
|
||||
#define UINT16_MAX (0xFFFF)
|
||||
#define UINT32_MAX (0xFFFFFFFFU)
|
||||
#define UINT64_MAX (0xFFFFFFFFFFFFFFFFUL)
|
||||
|
||||
#define UINT_FAST8_MAX UINT8_MAX
|
||||
#define UINT_FAST16_MAX UINT16_MAX
|
||||
#define UINT_FAST32_MAX UINT32_MAX
|
||||
#define UINT_FAST64_MAX UINT64_MAX
|
||||
|
||||
#define UINT_LEAST8_MAX UINT8_MAX
|
||||
#define UINT_LEAST16_MAX UINT16_MAX
|
||||
#define UINT_LEAST32_MAX UINT32_MAX
|
||||
#define UINT_LEAST64_MAX UINT64_MAX
|
||||
|
||||
#define UINTPTR_MAX UINT64_MAX
|
||||
#define UINTMAX_MAX UINT64_MAX
|
||||
|
||||
#define INT8_C(value) value
|
||||
#define INT16_C(value) value
|
||||
#define INT32_C(value) value
|
||||
#define INT64_C(value) value ## L
|
||||
|
||||
#define INTMAX_C(value) value ## L
|
||||
|
||||
#define UINT8_C(value) value
|
||||
#define UINT16_C(value) value
|
||||
#define UINT32_C(value) value ## U
|
||||
#define UINT64_C(value) value ## UL
|
||||
|
||||
#define UINTMAX_C(value) value ## UL
|
||||
|
||||
#define PTRDIFF_MIN (-0x8000000000000000L)
|
||||
#define PTRDIFF_MAX (0x7FFFFFFFFFFFFFFFL)
|
||||
|
||||
#define SIZE_MAX (0xFFFFFFFFFFFFFFFFUL)
|
||||
|
||||
#define WINT_MIN (-0x80000000)
|
||||
#define WINT_MAX (0x7FFFFFFF)
|
||||
|
||||
#define WCHAR_MIN (0)
|
||||
#define WCHAR_MAX (0x10FFFF)
|
||||
|
||||
#endif
|
||||
3
packages/cjit/src/includes/stdnoreturn.h
Normal file
3
packages/cjit/src/includes/stdnoreturn.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#define noreturn _Noreturn
|
||||
19
packages/cjit/src/root.zig
Normal file
19
packages/cjit/src/root.zig
Normal file
@@ -0,0 +1,19 @@
|
||||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
pub const Runtime = @import("Runtime.zig");
|
||||
pub const Sections = @import("Sections.zig");
|
||||
pub const StackValue = @import("StackValue.zig");
|
||||
pub const tokens = @import("tokens.zig");
|
||||
pub const types = @import("types.zig");
|
||||
pub const x86_64 = @import("x86_64.zig");
|
||||
|
||||
pub const call: std.builtin.CallingConvention = switch (builtin.cpu.arch) {
|
||||
.aarch64 => .{ .aarch64_aapcs = .{} },
|
||||
.x86_64 => .{ .x86_64_sysv = .{} },
|
||||
else => @compileError("Architecture " ++ @tagName(builtin.cpu.arch) ++ " not supported"),
|
||||
};
|
||||
|
||||
test {
|
||||
std.testing.refAllDeclsRecursive(@This());
|
||||
}
|
||||
7
packages/cjit/src/tokens.zig
Normal file
7
packages/cjit/src/tokens.zig
Normal file
@@ -0,0 +1,7 @@
|
||||
pub const Builtin = @import("tokens/Builtin.zig").Builtin;
|
||||
pub const Constant = @import("tokens/Constant.zig").Constant;
|
||||
pub const Keyword = @import("tokens/Keyword.zig").Keyword;
|
||||
pub const Punctuator = @import("tokens/Punctuator.zig").Punctuator;
|
||||
pub const Token = @import("tokens/Token.zig").Token;
|
||||
pub const Tokenizer = @import("tokens/Tokenizer.zig");
|
||||
pub const Utf8Iterator = @import("tokens/Utf8Iterator.zig");
|
||||
156
packages/cjit/src/tokens/Builtin.zig
Normal file
156
packages/cjit/src/tokens/Builtin.zig
Normal file
@@ -0,0 +1,156 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Builtin = enum {
|
||||
/// Usage: `__builtin_abs(value)`
|
||||
///
|
||||
/// `value` can be any integer or real type. Equivalent to
|
||||
/// `value < 0 ? -value : value`. Noop for unsigned integer types.
|
||||
__builtin_abs,
|
||||
/// Usage: `__builtin_byteswap(value)`
|
||||
///
|
||||
/// `value` can be any integer type.
|
||||
__builtin_byteswap,
|
||||
/// Usage: `__builtin_ceil(value)`
|
||||
///
|
||||
/// `value` can be any real type.
|
||||
__builtin_ceil,
|
||||
/// Usage: `__builtin_clz(value)`
|
||||
///
|
||||
/// Count leading zeroes. `value` can be any integer type. The return type
|
||||
/// is `int`.
|
||||
__builtin_clz,
|
||||
/// Usage `__builtin_containerof(ptr, type, member)`
|
||||
///
|
||||
/// `ptr` must be an pointer to a struct or a union. `type` must be a type.
|
||||
/// `member` must be an identifier. Given `ptr` is a pointer to a given
|
||||
/// member of `type`, returns a pointer to the entire container.
|
||||
__builtin_containerof,
|
||||
/// Usage: `__builtin_cos(value)`
|
||||
///
|
||||
/// `value` can be any real type.
|
||||
__builtin_cos,
|
||||
/// Usage: `__builtin_ctz(value)`
|
||||
///
|
||||
/// Count trailing zeroes. `value` can be any integer type. The return type
|
||||
/// is `int`.
|
||||
__builtin_ctz,
|
||||
/// Usage: `__builtin_embedfile(path)`
|
||||
///
|
||||
/// `path` must be a string literal. The return type is `const char *`. The
|
||||
/// data is null-terminated.
|
||||
__builtin_embedfile,
|
||||
/// Usage `__builtin_exp(value)`
|
||||
///
|
||||
/// `value` can be any real type. Calculates e^value.
|
||||
__builtin_exp,
|
||||
/// Usage `__builtin_exp2(value)`
|
||||
///
|
||||
/// `value` can be any real type. Calculates 2^value.
|
||||
__builtin_exp2,
|
||||
/// Usage: `__bultin_floor(value)`
|
||||
///
|
||||
/// `value` can be any real type.
|
||||
__builtin_floor,
|
||||
/// Usage: `__builtin_frameaddress()`
|
||||
///
|
||||
/// Returns the value of base pointer. The return type is equivalent to
|
||||
/// `uintptr_t`.
|
||||
__builtin_frameaddress,
|
||||
/// Usage: `__builtin_log(value)`
|
||||
///
|
||||
/// `value` can be any real type. Calculates natural logarithm (base e).
|
||||
__builtin_log,
|
||||
/// Usage: `__builtin_log10(value)`
|
||||
///
|
||||
/// `value` can be any real type. Calculates base 10 logarithm.
|
||||
__builtin_log10,
|
||||
/// Usage: `__builtin_log2(value)`
|
||||
///
|
||||
/// `value` can be any real type. Calculates base 2 logarithm.
|
||||
__builtin_log2,
|
||||
/// Usage: `__builtin_max(...)`
|
||||
///
|
||||
/// The arguments can be any integer or real types. NaN values are ignored.
|
||||
__builtin_max,
|
||||
/// Usage: `__builtin_memcpy(dest, src, count)`
|
||||
///
|
||||
/// `dest` and `src` must be pointers. The pointers are reinterpreted as
|
||||
/// pointers to `char`. `count` is coerced to the equivalent of `size_t`.
|
||||
/// `dest` must be a non-const pointer. The regions must not overlap.
|
||||
__builtin_memcpy,
|
||||
/// Usage: `__builtin_memmove(dest, src, count)`
|
||||
///
|
||||
/// `dest` and `src` must be pointers. The pointers are reinterpreted as
|
||||
/// pointers to `char`. `count` is coerced to the equivalent of `size_t`.
|
||||
/// `dest` must be a non-const pointer. The regions may overlap.
|
||||
__builtin_memmove,
|
||||
/// Usage: `__builtin_memset(dest, ch, count)`
|
||||
///
|
||||
/// `dest` must be a pointer. The pointer is reinterpreted as pointer to
|
||||
/// `char`. `ch` is cast to `unsigned char`. `dest` must be a non-const
|
||||
// pointer.
|
||||
__builtin_memset,
|
||||
/// Usage: `__builtin_min(...)`
|
||||
///
|
||||
/// The arguments can be any integer or real types. NaN values are ignored.
|
||||
__builtin_min,
|
||||
/// Usage: `__builtin_offsetof(type, member)`
|
||||
///
|
||||
/// `type` must be a type. `member` must be an identifier.
|
||||
__builtin_offsetof,
|
||||
/// Usage: `__builtin_popcount(value)`
|
||||
///
|
||||
/// `value` can be any integer type. The return type is `int`.
|
||||
__builtin_popcount,
|
||||
/// Usage: `__builtin_returnaddress()`
|
||||
///
|
||||
/// Returns the address of the instruction to run after current function
|
||||
/// returns. The return type is equivalent to `uintptr_t`.
|
||||
__builtin_returnaddress,
|
||||
/// Usage: `__builtin_round(value)`
|
||||
///
|
||||
/// `value` can be any real type.
|
||||
__builtin_round,
|
||||
/// Usage: `__builtin_sin(value)`
|
||||
///
|
||||
/// `value` can be any real type.
|
||||
__builtin_sin,
|
||||
/// Usage: `__builtin_sqrt(value)`
|
||||
///
|
||||
/// `value` can be any real type.
|
||||
__builtin_sqrt,
|
||||
/// Usage: `__builtin_tan(value)`
|
||||
///
|
||||
/// `value` can be any real type.
|
||||
__builtin_tan,
|
||||
/// Usage: `__builtin_trunc(value)`
|
||||
///
|
||||
/// `value` can be any real type.
|
||||
__builtin_trunc,
|
||||
/// Usage: `__builtin_typename(type)`
|
||||
///
|
||||
/// `type` must be a type. The return type is `const char *`.
|
||||
__builtin_typename,
|
||||
/// Usage: `__builtin_typeof(...)`
|
||||
__builtin_typeof,
|
||||
|
||||
pub const map: std.StaticStringMap(Builtin) = blk: {
|
||||
const fields = @typeInfo(Builtin).@"enum".fields;
|
||||
|
||||
var kvs_list: [fields.len]struct { []const u8, Builtin } = undefined;
|
||||
for (fields, 0..) |field, i| {
|
||||
kvs_list[i] = .{ field.name, @field(Builtin, field.name) };
|
||||
}
|
||||
|
||||
break :blk .initComptime(kvs_list);
|
||||
};
|
||||
|
||||
pub fn isBuiltin(identifier: []const u8) ?Builtin {
|
||||
if (std.mem.startsWith(u8, identifier, "__builtin_")) {
|
||||
@branchHint(.unlikely);
|
||||
return map.get(identifier);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
14
packages/cjit/src/tokens/Constant.zig
Normal file
14
packages/cjit/src/tokens/Constant.zig
Normal file
@@ -0,0 +1,14 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Constant = union(enum) {
|
||||
int: i32,
|
||||
long: i64,
|
||||
long_long: i64,
|
||||
unsigned_int: u32,
|
||||
unsigned_long: u64,
|
||||
unsigned_long_long: u64,
|
||||
float: f32,
|
||||
double: f64,
|
||||
character: u8,
|
||||
wide_character: i32,
|
||||
};
|
||||
63
packages/cjit/src/tokens/Keyword.zig
Normal file
63
packages/cjit/src/tokens/Keyword.zig
Normal file
@@ -0,0 +1,63 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Keyword = enum {
|
||||
_Alignas,
|
||||
_Alignof,
|
||||
_Atomic,
|
||||
_Bool,
|
||||
_Complex,
|
||||
_Generic,
|
||||
_Imaginary,
|
||||
_Noreturn,
|
||||
_Static_assert,
|
||||
_Thread_local,
|
||||
auto,
|
||||
@"break",
|
||||
case,
|
||||
char,
|
||||
@"const",
|
||||
@"continue",
|
||||
default,
|
||||
do,
|
||||
double,
|
||||
@"else",
|
||||
@"enum",
|
||||
@"extern",
|
||||
float,
|
||||
@"for",
|
||||
goto,
|
||||
@"if",
|
||||
@"inline",
|
||||
int,
|
||||
long,
|
||||
register,
|
||||
restrict,
|
||||
@"return",
|
||||
short,
|
||||
signed,
|
||||
sizeof,
|
||||
static,
|
||||
@"struct",
|
||||
@"switch",
|
||||
typedef,
|
||||
@"union",
|
||||
unsigned,
|
||||
void,
|
||||
@"volatile",
|
||||
@"while",
|
||||
|
||||
pub const map: std.StaticStringMap(Keyword) = blk: {
|
||||
const fields = @typeInfo(Keyword).@"enum".fields;
|
||||
|
||||
var kvs_list: [fields.len]struct { []const u8, Keyword } = undefined;
|
||||
for (fields, 0..) |field, i| {
|
||||
kvs_list[i] = .{ field.name, @field(Keyword, field.name) };
|
||||
}
|
||||
|
||||
break :blk .initComptime(kvs_list);
|
||||
};
|
||||
|
||||
pub fn isKeyword(identifier: []const u8) ?Keyword {
|
||||
return map.get(identifier);
|
||||
}
|
||||
};
|
||||
70
packages/cjit/src/tokens/Punctuator.zig
Normal file
70
packages/cjit/src/tokens/Punctuator.zig
Normal file
@@ -0,0 +1,70 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Punctuator = enum(u32) {
|
||||
// three characters
|
||||
@"..." = strToInt3("..."),
|
||||
@"<<=" = strToInt3("<<="),
|
||||
@">>=" = strToInt3(">>="),
|
||||
// two characters
|
||||
@"--" = strToInt2("--"),
|
||||
@"-=" = strToInt2("-="),
|
||||
@"->" = strToInt2("->"),
|
||||
@"!=" = strToInt2("!="),
|
||||
@"*=" = strToInt2("*="),
|
||||
@"/=" = strToInt2("/="),
|
||||
@"&&" = strToInt2("&&"),
|
||||
@"&=" = strToInt2("&="),
|
||||
@"##" = strToInt2("##"),
|
||||
@"%=" = strToInt2("%="),
|
||||
@"^=" = strToInt2("^="),
|
||||
@"++" = strToInt2("++"),
|
||||
@"+=" = strToInt2("+="),
|
||||
@"<<" = strToInt2("<<"),
|
||||
@"<=" = strToInt2("<="),
|
||||
@"==" = strToInt2("=="),
|
||||
@">=" = strToInt2(">="),
|
||||
@">>" = strToInt2(">>"),
|
||||
@"|=" = strToInt2("|="),
|
||||
@"||" = strToInt2("||"),
|
||||
// single character
|
||||
@"-" = strToInt1("-"),
|
||||
@"," = strToInt1(","),
|
||||
@";" = strToInt1(";"),
|
||||
@":" = strToInt1(":"),
|
||||
@"!" = strToInt1("!"),
|
||||
@"?" = strToInt1("?"),
|
||||
@"." = strToInt1("."),
|
||||
@"(" = strToInt1("("),
|
||||
@")" = strToInt1(")"),
|
||||
@"[" = strToInt1("["),
|
||||
@"]" = strToInt1("]"),
|
||||
@"{" = strToInt1("{"),
|
||||
@"}" = strToInt1("}"),
|
||||
@"*" = strToInt1("*"),
|
||||
@"/" = strToInt1("/"),
|
||||
@"&" = strToInt1("&"),
|
||||
@"#" = strToInt1("#"),
|
||||
@"%" = strToInt1("%"),
|
||||
@"^" = strToInt1("^"),
|
||||
@"+" = strToInt1("+"),
|
||||
@"<" = strToInt1("<"),
|
||||
@"=" = strToInt1("="),
|
||||
@">" = strToInt1(">"),
|
||||
@"|" = strToInt1("|"),
|
||||
@"~" = strToInt1("~"),
|
||||
|
||||
pub const line_continuation_lf = strToInt2("\\\n");
|
||||
pub const line_continuation_crlf = strToInt3("\\\r\n");
|
||||
};
|
||||
|
||||
fn strToInt1(str: *const [1]u8) u32 {
|
||||
return @as(u8, @bitCast(str.*));
|
||||
}
|
||||
|
||||
fn strToInt2(str: *const [2]u8) u32 {
|
||||
return @as(u16, @bitCast(str.*));
|
||||
}
|
||||
|
||||
fn strToInt3(str: *const [3]u8) u32 {
|
||||
return @as(u24, @bitCast(str.*));
|
||||
}
|
||||
16
packages/cjit/src/tokens/Token.zig
Normal file
16
packages/cjit/src/tokens/Token.zig
Normal file
@@ -0,0 +1,16 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Builtin = @import("Builtin.zig").Builtin;
|
||||
const Constant = @import("Constant.zig").Constant;
|
||||
const Keyword = @import("Keyword.zig").Keyword;
|
||||
const Punctuator = @import("Punctuator.zig").Punctuator;
|
||||
|
||||
pub const Token = union(enum) {
|
||||
builtin: Builtin,
|
||||
constant: Constant,
|
||||
keyword: Keyword,
|
||||
punctuator: Punctuator,
|
||||
identifier: []const u8,
|
||||
string_literal: [:0]const u8,
|
||||
wide_string_literal: [:0]const u32,
|
||||
};
|
||||
263
packages/cjit/src/tokens/Tokenizer.zig
Normal file
263
packages/cjit/src/tokens/Tokenizer.zig
Normal file
@@ -0,0 +1,263 @@
|
||||
const std = @import("std");
|
||||
const Self = @This();
|
||||
|
||||
const Builtin = @import("Builtin.zig").Builtin;
|
||||
const Keyword = @import("Keyword.zig").Keyword;
|
||||
const Punctuator = @import("Punctuator.zig").Punctuator;
|
||||
const Token = @import("Token.zig").Token;
|
||||
const Utf8Iterator = @import("Utf8Iterator.zig");
|
||||
|
||||
pub const max_string_length = 4096;
|
||||
pub const max_wide_string_length = 4096;
|
||||
|
||||
filename: []const u8 = &.{},
|
||||
it: Utf8Iterator = .init(&.{}),
|
||||
defines: std.StringHashMapUnmanaged([]Token) = .{},
|
||||
/// Bounded, preallocated with the capacity of `max_string_length`.
|
||||
string: std.ArrayList(u8),
|
||||
/// Bounded, preallocated with the capacity of `max_wide_string_length`.
|
||||
wide_string: std.ArrayList(u32),
|
||||
|
||||
pub fn init(arena_allocator: std.mem.Allocator) !Self {
|
||||
const string_buffer = try arena_allocator.alloc(u8, max_string_length);
|
||||
const wide_string_buffer = try arena_allocator.alloc(u32, max_wide_string_length);
|
||||
|
||||
return .{
|
||||
.string = .initBuffer(string_buffer),
|
||||
.wide_string = .initBuffer(wide_string_buffer),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn setSource(self: *Self, filename: []const u8, code: []const u8) void {
|
||||
self.filename = filename;
|
||||
self.it = .init(code);
|
||||
}
|
||||
|
||||
pub fn nextToken(self: *Self, arena_allocator: std.mem.Allocator) !?Token {
|
||||
try self.skipWhitespace();
|
||||
|
||||
// TODO Skip C and C++ style comments
|
||||
// TODO Preprocessor directives
|
||||
|
||||
const cp = try self.peekCodepointSkipLineContinuation() orelse return null;
|
||||
|
||||
switch (cp) {
|
||||
// Identifier start
|
||||
'A'...'Z', '_', 'a'...'z', 128...std.math.maxInt(u21) => {
|
||||
// This is an identifier, with the possible exception of:
|
||||
// - wide string: L"
|
||||
// - wide char: L'
|
||||
// - any keyword
|
||||
|
||||
if (cp == 'L') {
|
||||
const state = self.it.save();
|
||||
|
||||
self.it.advanceCodepoint(cp);
|
||||
const cp2 = try self.peekCodepointSkipLineContinuation() orelse 0;
|
||||
|
||||
switch (cp2) {
|
||||
// Wide string
|
||||
'\"' => {
|
||||
self.it.advanceCodepoint(cp2);
|
||||
self.wide_string.clearRetainingCapacity();
|
||||
// TODO Parse wide string
|
||||
},
|
||||
// Wide char
|
||||
'\'' => {
|
||||
self.it.advanceCodepoint(cp2);
|
||||
// TODO Parse wide char
|
||||
},
|
||||
// Identifier or keyword
|
||||
else => {
|
||||
self.it.restore(state);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const identifier_start = self.it.ptr;
|
||||
self.it.advanceCodepoint(cp);
|
||||
|
||||
var next_cp = try self.peekCodepointSkipLineContinuation();
|
||||
while (next_cp != null and isIdentifierMiddle(next_cp.?)) {
|
||||
self.it.advanceCodepoint(next_cp.?);
|
||||
next_cp = try self.peekCodepointSkipLineContinuation();
|
||||
}
|
||||
|
||||
const identifier = self.it.str[identifier_start..self.it.ptr];
|
||||
|
||||
// TODO Preprocessor
|
||||
|
||||
if (Keyword.isKeyword(identifier)) |keyword| {
|
||||
return .{ .keyword = keyword };
|
||||
} else if (Builtin.isBuiltin(identifier)) |builtin| {
|
||||
return .{ .builtin = builtin };
|
||||
} else {
|
||||
return .{ .identifier = try arena_allocator.dupe(u8, identifier) };
|
||||
}
|
||||
},
|
||||
// String
|
||||
'\"' => {
|
||||
self.it.advanceCodepoint(cp);
|
||||
self.string.clearRetainingCapacity();
|
||||
// TODO Parse string
|
||||
},
|
||||
// Char
|
||||
'\'' => {
|
||||
self.it.advanceCodepoint(cp);
|
||||
// TODO Parse char
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
// Higher code points should've been already handled. The code below may
|
||||
// assume that `cp` is an ASCII character.
|
||||
std.debug.assert(cp < 128);
|
||||
|
||||
// TODO Numeric constants
|
||||
|
||||
const cp3 = self.it.peekThreeBytes().?;
|
||||
|
||||
switch (cp3 & 0x00_FF_FF_FF) {
|
||||
inline @intFromEnum(Punctuator.@"..."),
|
||||
@intFromEnum(Punctuator.@"<<="),
|
||||
@intFromEnum(Punctuator.@">>="),
|
||||
=> |p| {
|
||||
self.it.ptr += 3;
|
||||
self.it.col += 3;
|
||||
return .{
|
||||
.punctuator = @enumFromInt(p),
|
||||
};
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (cp3 & 0x00_00_FF_FF) {
|
||||
inline @intFromEnum(Punctuator.@"--"),
|
||||
@intFromEnum(Punctuator.@"-="),
|
||||
@intFromEnum(Punctuator.@"->"),
|
||||
@intFromEnum(Punctuator.@"!="),
|
||||
@intFromEnum(Punctuator.@"*="),
|
||||
@intFromEnum(Punctuator.@"/="),
|
||||
@intFromEnum(Punctuator.@"&&"),
|
||||
@intFromEnum(Punctuator.@"&="),
|
||||
@intFromEnum(Punctuator.@"##"),
|
||||
@intFromEnum(Punctuator.@"%="),
|
||||
@intFromEnum(Punctuator.@"^="),
|
||||
@intFromEnum(Punctuator.@"++"),
|
||||
@intFromEnum(Punctuator.@"+="),
|
||||
@intFromEnum(Punctuator.@"<<"),
|
||||
@intFromEnum(Punctuator.@"<="),
|
||||
@intFromEnum(Punctuator.@"=="),
|
||||
@intFromEnum(Punctuator.@">="),
|
||||
@intFromEnum(Punctuator.@">>"),
|
||||
@intFromEnum(Punctuator.@"|="),
|
||||
@intFromEnum(Punctuator.@"||"),
|
||||
=> |p| {
|
||||
self.it.ptr += 2;
|
||||
self.it.col += 2;
|
||||
return .{
|
||||
.punctuator = @enumFromInt(p),
|
||||
};
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
switch (cp3 & 0x00_00_00_FF) {
|
||||
inline @intFromEnum(Punctuator.@"-"),
|
||||
@intFromEnum(Punctuator.@","),
|
||||
@intFromEnum(Punctuator.@";"),
|
||||
@intFromEnum(Punctuator.@":"),
|
||||
@intFromEnum(Punctuator.@"!"),
|
||||
@intFromEnum(Punctuator.@"?"),
|
||||
@intFromEnum(Punctuator.@"."),
|
||||
@intFromEnum(Punctuator.@"("),
|
||||
@intFromEnum(Punctuator.@")"),
|
||||
@intFromEnum(Punctuator.@"["),
|
||||
@intFromEnum(Punctuator.@"]"),
|
||||
@intFromEnum(Punctuator.@"{"),
|
||||
@intFromEnum(Punctuator.@"}"),
|
||||
@intFromEnum(Punctuator.@"*"),
|
||||
@intFromEnum(Punctuator.@"/"),
|
||||
@intFromEnum(Punctuator.@"&"),
|
||||
@intFromEnum(Punctuator.@"#"),
|
||||
@intFromEnum(Punctuator.@"%"),
|
||||
@intFromEnum(Punctuator.@"^"),
|
||||
@intFromEnum(Punctuator.@"+"),
|
||||
@intFromEnum(Punctuator.@"<"),
|
||||
@intFromEnum(Punctuator.@"="),
|
||||
@intFromEnum(Punctuator.@">"),
|
||||
@intFromEnum(Punctuator.@"|"),
|
||||
@intFromEnum(Punctuator.@"~"),
|
||||
=> |p| {
|
||||
self.it.ptr += 1;
|
||||
self.it.col += 1;
|
||||
return .{
|
||||
.punctuator = @enumFromInt(p),
|
||||
};
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
return error.InvalidToken;
|
||||
}
|
||||
|
||||
fn peekCodepointSkipLineContinuation(self: *Self) !?u21 {
|
||||
while (self.skipLineContinuation()) {}
|
||||
const cp = try self.it.peekCodepoint();
|
||||
return cp;
|
||||
}
|
||||
|
||||
/// Line continuation is defined as a backslash followed imediatelly by LF or
|
||||
/// CRLF. Return whether a line continuation was encountered and therefore
|
||||
/// skipped past.
|
||||
fn skipLineContinuation(self: *Self) bool {
|
||||
if (self.it.peekThreeBytes()) |b| {
|
||||
@branchHint(.likely);
|
||||
if (b & 0x00_00_FF_FF == Punctuator.line_continuation_lf) {
|
||||
@branchHint(.unlikely);
|
||||
self.it.ptr += 2;
|
||||
self.it.line += 1;
|
||||
self.it.col = 1;
|
||||
return true;
|
||||
} else if (b & 0x00_FF_FF_FF == Punctuator.line_continuation_crlf) {
|
||||
@branchHint(.unlikely);
|
||||
self.it.ptr += 3;
|
||||
self.it.line += 1;
|
||||
self.it.col = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
fn skipWhitespace(self: *Self) !void {
|
||||
while (try self.peekCodepointSkipLineContinuation()) |cp| {
|
||||
switch (cp) {
|
||||
// <Character Tabulation> (HT, TAB)
|
||||
0x0009,
|
||||
// <End of Line> (EOL, LF, NL)
|
||||
0x000A,
|
||||
// <Line Tabulation> (VT)
|
||||
0x000B,
|
||||
// <Form Feed> (FF)
|
||||
0x000C,
|
||||
// <Carriage Return> (CR)
|
||||
0x000D,
|
||||
// Space (SP)
|
||||
0x0020,
|
||||
=> self.it.advanceCodepoint(cp),
|
||||
else => return,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn isIdentifierMiddle(code_point: u21) bool {
|
||||
// zig fmt: off
|
||||
return code_point >= '0' and code_point <= '9'
|
||||
or code_point >= 'A' and code_point <= 'Z'
|
||||
or code_point == '_'
|
||||
or code_point >= 'a' and code_point <= 'z'
|
||||
or code_point >= 128;
|
||||
// zig fmt: on
|
||||
}
|
||||
99
packages/cjit/src/tokens/Utf8Iterator.zig
Normal file
99
packages/cjit/src/tokens/Utf8Iterator.zig
Normal file
@@ -0,0 +1,99 @@
|
||||
const std = @import("std");
|
||||
const Self = @This();
|
||||
|
||||
str: []const u8,
|
||||
ptr: usize,
|
||||
line: usize,
|
||||
col: usize,
|
||||
|
||||
pub const State = struct {
|
||||
ptr: usize,
|
||||
line: usize,
|
||||
col: usize,
|
||||
};
|
||||
|
||||
pub fn init(str: []const u8) Self {
|
||||
return .{
|
||||
.str = str,
|
||||
.ptr = 0,
|
||||
.line = 1,
|
||||
.col = 1,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn save(self: Self) State {
|
||||
return .{
|
||||
.ptr = self.ptr,
|
||||
.line = self.line,
|
||||
.col = self.col,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn restore(self: *Self, state: State) void {
|
||||
self.ptr = state.ptr;
|
||||
self.line = state.line;
|
||||
self.col = state.col;
|
||||
}
|
||||
|
||||
pub fn peekByte(self: *Self) ?u8 {
|
||||
if (self.ptr >= self.str.len) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self.str[self.ptr];
|
||||
}
|
||||
|
||||
pub fn peekCodepoint(self: Self) !?u21 {
|
||||
if (self.ptr >= self.str.len) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const cp_len = std.unicode.utf8ByteSequenceLength(self.str[self.ptr]) catch return error.InvalidUtf8;
|
||||
if (self.ptr + cp_len > self.str.len) return error.InvalidUtf8;
|
||||
|
||||
const cp_slice = self.str[self.ptr .. self.ptr + cp_len];
|
||||
const cp = std.unicode.utf8Decode(cp_slice) catch return error.InvalidUtf8;
|
||||
|
||||
return cp;
|
||||
}
|
||||
|
||||
pub fn peekThreeBytes(self: Self) ?u32 {
|
||||
var bytes: [3]u8 = .{ 0, 0, 0 };
|
||||
|
||||
const bytes_left = self.str.len - self.ptr;
|
||||
sw: switch (bytes_left) {
|
||||
0 => return null,
|
||||
1 => {
|
||||
bytes[0] = self.str[self.ptr];
|
||||
return @as(u24, @bitCast(bytes));
|
||||
},
|
||||
2 => {
|
||||
bytes[1] = self.str[self.ptr + 1];
|
||||
continue :sw 1;
|
||||
},
|
||||
else => {
|
||||
bytes[2] = self.str[self.ptr + 2];
|
||||
continue :sw 2;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Call with value returned by `peekCodepoint`.
|
||||
pub fn advanceCodepoint(self: *Self, cp: u21) void {
|
||||
std.debug.assert(blk: {
|
||||
const actual_cp = self.peekCodepoint() catch break :blk false;
|
||||
break :blk cp == actual_cp;
|
||||
});
|
||||
|
||||
const cp_len = std.unicode.utf8CodepointSequenceLength(cp) catch unreachable;
|
||||
|
||||
self.ptr += cp_len;
|
||||
|
||||
if (cp == '\n') {
|
||||
self.line += 1;
|
||||
// NOTE Columns start as 1, it will be incremented below.
|
||||
self.col = 0;
|
||||
}
|
||||
|
||||
self.col += 1;
|
||||
}
|
||||
201
packages/cjit/src/types.zig
Normal file
201
packages/cjit/src/types.zig
Normal file
@@ -0,0 +1,201 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const Type = union(enum) {
|
||||
signed_char: void,
|
||||
signed_short: void,
|
||||
signed_int: void,
|
||||
signed_long: void,
|
||||
signed_long_long: void,
|
||||
|
||||
unsigned_char: void,
|
||||
unsigned_short: void,
|
||||
unsigned_int: void,
|
||||
unsigned_long: void,
|
||||
unsigned_long_long: void,
|
||||
|
||||
float: void,
|
||||
double: void,
|
||||
long_double: void,
|
||||
|
||||
void: void,
|
||||
noreturn: void,
|
||||
char: void,
|
||||
bool: void,
|
||||
|
||||
@"enum": *const Enum,
|
||||
@"struct": *const Struct,
|
||||
@"union": *const Union,
|
||||
array: *const Array,
|
||||
function: *const Function,
|
||||
pointer: *const Pointer,
|
||||
|
||||
pub fn sizeOf(self: Type) ?usize {
|
||||
return switch (self) {
|
||||
.signed_char => 1,
|
||||
.signed_short => 2,
|
||||
.signed_int => 4,
|
||||
.signed_long => 8,
|
||||
.signed_long_long => 8,
|
||||
|
||||
.unsigned_char => 1,
|
||||
.unsigned_short => 2,
|
||||
.unsigned_int => 4,
|
||||
.unsigned_long => 8,
|
||||
.unsigned_long_long => 8,
|
||||
|
||||
.float => 4,
|
||||
.double => 8,
|
||||
.long_double => 8,
|
||||
|
||||
.void => null,
|
||||
.noreturn => null,
|
||||
.char => 1,
|
||||
.bool => 1,
|
||||
|
||||
.@"enum" => 4,
|
||||
.@"struct" => |s| s.size,
|
||||
.@"union" => |u| u.size,
|
||||
.array => |a| if (a.length) |l| (if (sizeOf(a.child)) |c| l * c else null) else 8,
|
||||
.function => null,
|
||||
.pointer => 8,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn alignOf(self: Type) ?u16 {
|
||||
return switch (self) {
|
||||
.signed_char => 1,
|
||||
.signed_short => 2,
|
||||
.signed_int => 4,
|
||||
.signed_long => 8,
|
||||
.signed_long_long => 8,
|
||||
|
||||
.unsigned_char => 1,
|
||||
.unsigned_short => 2,
|
||||
.unsigned_int => 4,
|
||||
.unsigned_long => 8,
|
||||
.unsigned_long_long => 8,
|
||||
|
||||
.float => 4,
|
||||
.double => 8,
|
||||
.long_double => 8,
|
||||
|
||||
.void => null,
|
||||
.noreturn => null,
|
||||
.char => 1,
|
||||
.bool => 1,
|
||||
|
||||
.@"enum" => 4,
|
||||
.@"struct" => |s| s.@"align",
|
||||
.@"union" => |u| u.@"align",
|
||||
.array => |a| if (a.length != null) alignOf(a.child) else 8,
|
||||
.function => null,
|
||||
.pointer => 8,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Enum = struct {
|
||||
name: []const u8,
|
||||
constants: []const EnumConstant,
|
||||
};
|
||||
|
||||
pub const EnumConstant = struct {
|
||||
name: []const u8,
|
||||
value: i32,
|
||||
inferred: bool,
|
||||
};
|
||||
|
||||
pub const Struct = struct {
|
||||
name: []const u8,
|
||||
fields: []const StructField,
|
||||
size: usize,
|
||||
@"align": u16,
|
||||
};
|
||||
|
||||
pub const StructField = struct {
|
||||
name: []const u8,
|
||||
type: Type,
|
||||
offset: usize,
|
||||
};
|
||||
|
||||
pub const Union = struct {
|
||||
name: []const u8,
|
||||
fields: []const UnionField,
|
||||
size: usize,
|
||||
@"align": u16,
|
||||
};
|
||||
|
||||
pub const UnionField = struct {
|
||||
name: []const u8,
|
||||
type: Type,
|
||||
};
|
||||
|
||||
pub const Array = struct {
|
||||
child: Type,
|
||||
length: ?usize,
|
||||
};
|
||||
|
||||
pub const Function = struct {
|
||||
arguments: []const FunctionArgument,
|
||||
@"return": Type,
|
||||
};
|
||||
|
||||
pub const FunctionArgument = struct {
|
||||
name: []const u8,
|
||||
type: Type,
|
||||
};
|
||||
|
||||
pub const Pointer = struct {
|
||||
child: Type,
|
||||
@"const": bool,
|
||||
@"volatile": bool,
|
||||
};
|
||||
|
||||
pub fn isCompatible(comptime ZigType: type, c_type: Type) bool {
|
||||
return switch (@typeInfo(ZigType)) {
|
||||
.type => false,
|
||||
.void => c_type == .void,
|
||||
.bool => c_type == .bool,
|
||||
.noreturn => c_type == .noreturn,
|
||||
.int => |zig_int| switch (c_type) {
|
||||
.signed_char => zig_int.signedness == .signed and zig_int.bits == 8,
|
||||
.signed_short => zig_int.signedness == .signed and zig_int.bits == 16,
|
||||
.signed_int => zig_int.signedness == .signed and zig_int.bits == 32,
|
||||
.signed_long => zig_int.signedness == .signed and zig_int.bits == 64,
|
||||
.signed_long_long => zig_int.signedness == .signed and zig_int.bits == 64,
|
||||
|
||||
.unsigned_char => zig_int.signedness == .unsigned and zig_int.bits == 8,
|
||||
.unsigned_short => zig_int.signedness == .unsigned and zig_int.bits == 16,
|
||||
.unsigned_int => zig_int.signedness == .unsigned and zig_int.bits == 32,
|
||||
.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,
|
||||
|
||||
else => false,
|
||||
},
|
||||
.float => |zig_float| switch (c_type) {
|
||||
.float => zig_float.bits = 32,
|
||||
.double => zig_float.bits = 64,
|
||||
.long_double => zig_float.bits = 64,
|
||||
},
|
||||
.pointer => @compileError("TODO"),
|
||||
.array => @compileError("TODO"),
|
||||
.@"struct" => @compileError("TODO"),
|
||||
.comptime_float => false,
|
||||
.comptime_int => false,
|
||||
.undefined => true,
|
||||
.null => @compileError("TODO"),
|
||||
.optional => @compileError("TODO"),
|
||||
.error_union => false,
|
||||
.error_set => false,
|
||||
.@"enum" => @compileError("TODO"),
|
||||
.@"union" => @compileError("TODO"),
|
||||
.@"fn" => false,
|
||||
.@"opaque" => @compileError("TODO"),
|
||||
.frame => false,
|
||||
.@"anyframe" => false,
|
||||
.vector => false,
|
||||
.enum_literal => false,
|
||||
};
|
||||
}
|
||||
445
packages/cjit/src/x86_64.zig
Normal file
445
packages/cjit/src/x86_64.zig
Normal file
@@ -0,0 +1,445 @@
|
||||
const std = @import("std");
|
||||
|
||||
const tokens = @import("tokens.zig");
|
||||
const types = @import("types.zig");
|
||||
|
||||
const Location = Runtime.Location;
|
||||
const Runtime = @import("Runtime.zig");
|
||||
const StackValue = @import("StackValue.zig");
|
||||
const Type = types.Type;
|
||||
|
||||
pub const Register = union(enum) {
|
||||
gpr: Gpr,
|
||||
xmm: Xmm,
|
||||
};
|
||||
|
||||
pub const Gpr = enum(u4) {
|
||||
rax = 0,
|
||||
rcx = 1,
|
||||
rdx = 2,
|
||||
rbx = 3,
|
||||
rsp = 4,
|
||||
rbp = 5,
|
||||
rsi = 6,
|
||||
rdi = 7,
|
||||
r8 = 8,
|
||||
r9 = 9,
|
||||
r10 = 10,
|
||||
r11 = 11,
|
||||
r12 = 12,
|
||||
r13 = 13,
|
||||
r14 = 14,
|
||||
r15 = 15,
|
||||
|
||||
pub fn reg(self: Gpr) u3 {
|
||||
return @truncate(@intFromEnum(self));
|
||||
}
|
||||
|
||||
pub fn x(self: Gpr) bool {
|
||||
return @intFromEnum(self) & 0b1000 != 0;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Xmm = enum(u4) {
|
||||
xmm0 = 0,
|
||||
xmm1 = 1,
|
||||
xmm2 = 2,
|
||||
xmm3 = 3,
|
||||
xmm4 = 4,
|
||||
xmm5 = 5,
|
||||
xmm6 = 6,
|
||||
xmm7 = 7,
|
||||
xmm8 = 8,
|
||||
xmm9 = 9,
|
||||
xmm10 = 10,
|
||||
xmm11 = 11,
|
||||
xmm12 = 12,
|
||||
xmm13 = 13,
|
||||
xmm14 = 14,
|
||||
xmm15 = 15,
|
||||
|
||||
pub fn reg(self: Xmm) u3 {
|
||||
return @truncate(@intFromEnum(self));
|
||||
}
|
||||
|
||||
pub fn x(self: Xmm) bool {
|
||||
return @intFromEnum(self) & 0b1000 != 0;
|
||||
}
|
||||
};
|
||||
|
||||
// --- EMIT HELPERS ------------------------------------------------------------
|
||||
|
||||
pub const Rex = packed struct(u8) {
|
||||
b: bool = false,
|
||||
x: bool = false,
|
||||
r: bool = false,
|
||||
w: bool = false,
|
||||
/// MUST always be the default value
|
||||
prefix: u4 = 0b0100,
|
||||
};
|
||||
|
||||
pub const ModRM = packed struct(u8) {
|
||||
rm: u3,
|
||||
reg: u3,
|
||||
mod: u2,
|
||||
};
|
||||
|
||||
pub const SIB = packed struct(u8) {
|
||||
base: u3,
|
||||
index: u3,
|
||||
scale: u2,
|
||||
};
|
||||
|
||||
pub fn op16(rt: *Runtime) !void {
|
||||
try rt.sections.text.writeByte(0x66, rt.allocator);
|
||||
}
|
||||
|
||||
pub fn rex(rt: *Runtime, value: Rex) !void {
|
||||
try rt.sections.text.writeByte(@bitCast(value), rt.allocator);
|
||||
}
|
||||
|
||||
pub fn op(rt: *Runtime, value: u8) !void {
|
||||
try rt.sections.text.writeByte(value, rt.allocator);
|
||||
}
|
||||
|
||||
pub fn op2(rt: *Runtime, v0: u8, v1: u8) !void {
|
||||
const bytes: [2]u8 = .{ v0, v1 };
|
||||
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||
}
|
||||
|
||||
pub fn op3(rt: *Runtime, v0: u8, v1: u8, v2: u8) !void {
|
||||
const bytes: [3]u8 = .{ v0, v1, v2 };
|
||||
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||
}
|
||||
|
||||
pub fn modrm(rt: *Runtime, value: ModRM) !void {
|
||||
try rt.sections.text.writeByte(@bitCast(value), rt.allocator);
|
||||
}
|
||||
|
||||
pub fn sib(rt: *Runtime, value: SIB) !void {
|
||||
try rt.sections.text.writeByte(@bitCast(value), rt.allocator);
|
||||
}
|
||||
|
||||
pub fn imm8(rt: *Runtime, value: u8) !void {
|
||||
try rt.sections.text.writeByte(value, rt.allocator);
|
||||
}
|
||||
|
||||
pub fn imm16(rt: *Runtime, value: u16) !void {
|
||||
var bytes: [2]u8 = undefined;
|
||||
std.mem.writeInt(u16, &bytes, value, .little);
|
||||
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||
}
|
||||
|
||||
pub fn imm32(rt: *Runtime, value: u32) !void {
|
||||
var bytes: [4]u8 = undefined;
|
||||
std.mem.writeInt(u32, &bytes, value, .little);
|
||||
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||
}
|
||||
|
||||
pub fn imm64(rt: *Runtime, value: u64) !void {
|
||||
var bytes: [8]u8 = undefined;
|
||||
std.mem.writeInt(u64, &bytes, value, .little);
|
||||
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||
}
|
||||
|
||||
pub fn disp8(rt: *Runtime, value: i8) !void {
|
||||
try rt.sections.text.writeByte(@bitCast(value), rt.allocator);
|
||||
}
|
||||
|
||||
pub fn disp32(rt: *Runtime, value: i32) !void {
|
||||
var bytes: [4]u8 = undefined;
|
||||
std.mem.writeInt(i32, &bytes, value, .little);
|
||||
try rt.sections.text.writeBytes(&bytes, rt.allocator);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
pub const Operation = enum {
|
||||
add,
|
||||
bit_and,
|
||||
bit_not,
|
||||
bit_or,
|
||||
bit_xor,
|
||||
bool_and,
|
||||
bool_not,
|
||||
bool_or,
|
||||
cmp_eq,
|
||||
cmp_gt,
|
||||
cmp_gte,
|
||||
cmp_lt,
|
||||
cmp_lte,
|
||||
cmp_neq,
|
||||
div,
|
||||
mod,
|
||||
mul,
|
||||
neg,
|
||||
sar,
|
||||
shl,
|
||||
shr,
|
||||
sub,
|
||||
};
|
||||
|
||||
pub fn opFloat(rt: *Runtime, operation: Operation) !void {
|
||||
_ = rt;
|
||||
_ = operation;
|
||||
}
|
||||
|
||||
pub fn opInt(rt: *Runtime, operation: Operation) !void {
|
||||
_ = rt;
|
||||
_ = operation;
|
||||
}
|
||||
|
||||
pub fn cvtIntToFloat(rt: *Runtime) !void {
|
||||
_ = rt;
|
||||
}
|
||||
|
||||
pub fn cvtFloatToInt(rt: *Runtime) !void {
|
||||
_ = rt;
|
||||
}
|
||||
|
||||
pub fn cvtFloatToFloat(rt: *Runtime, target: Type) !void {
|
||||
const top = vsTop(rt);
|
||||
|
||||
switch (top.c_type) {
|
||||
.float => switch (target) {
|
||||
.float => {
|
||||
// do nothing
|
||||
},
|
||||
.double, .long_double => {
|
||||
// CVTSS2SD xmm1, xmm2/m32
|
||||
// F3 0F 5A /r
|
||||
},
|
||||
else => unreachable,
|
||||
},
|
||||
.double, .long_double => switch (target) {
|
||||
.float => {
|
||||
// CVTSD2SS xmm1, xmm2/m64
|
||||
// F2 0F 5A /r
|
||||
},
|
||||
.double, .long_double => {
|
||||
// do nothing
|
||||
},
|
||||
else => unreachable,
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
}
|
||||
|
||||
// --- LOAD AND STORE ----------------------------------------------------------
|
||||
|
||||
/// Load value into register. The value must be 1, 2, 4 or 8 bytes long.
|
||||
pub fn load(rt: *Runtime, dst_register: Register, src_value: *const StackValue) !void {
|
||||
const size = src_value.c_type.sizeOf().?;
|
||||
std.debug.assert(size == 1 or size == 2 or size == 4 or size == 8);
|
||||
|
||||
switch (src_value.value) {
|
||||
.register => |src_register| {
|
||||
if (std.meta.eql(dst_register, src_register)) return;
|
||||
// TODO
|
||||
},
|
||||
.constant => |constant| switch (dst_register) {
|
||||
.gpr => |dest_gpr| switch (size) {
|
||||
1 => {
|
||||
// MOV r8, imm8
|
||||
// B0+ rb ib
|
||||
if (@intFromEnum(dest_gpr) >= 4) {
|
||||
// NOTE spl, bpl, sil and dil need an empty REX prefix,
|
||||
// otherwise ah, ch, dh and bh would be used.
|
||||
try rex(rt, .{ .r = dest_gpr.x() });
|
||||
}
|
||||
try op(rt, 0xB0 | @as(u8, dest_gpr.reg()));
|
||||
try imm8(rt, @truncate(constant));
|
||||
},
|
||||
2 => {
|
||||
// MOV r16, imm16
|
||||
// B8+ rw iw
|
||||
try op16(rt);
|
||||
if (@intFromEnum(dest_gpr) >= 8) {
|
||||
try rex(rt, .{ .r = dest_gpr.x() });
|
||||
}
|
||||
try op(rt, 0xB8 | @as(u8, dest_gpr.reg()));
|
||||
try imm16(rt, @truncate(constant));
|
||||
},
|
||||
4 => {
|
||||
// MOV r32, imm32
|
||||
// B8+ rd id
|
||||
if (@intFromEnum(dest_gpr) >= 8) {
|
||||
try rex(rt, .{ .r = dest_gpr.x() });
|
||||
}
|
||||
try op(rt, 0xB8 | @as(u8, dest_gpr.reg()));
|
||||
try imm32(rt, @truncate(constant));
|
||||
},
|
||||
8 => {
|
||||
// MOV r64, imm64
|
||||
// REX.W + B8+ rd io
|
||||
try rex(rt, .{ .r = dest_gpr.x(), .w = true });
|
||||
try op(rt, 0xB8 | @as(u8, dest_gpr.reg()));
|
||||
try imm64(rt, constant);
|
||||
},
|
||||
else => unreachable,
|
||||
},
|
||||
.xmm => |dest_xmm| {
|
||||
var bytes: [8]u8 = undefined;
|
||||
std.mem.writeInt(u64, &bytes, @truncate(constant), .little);
|
||||
const data = bytes[0..size];
|
||||
|
||||
// MOVD xmm, r/m32
|
||||
// 66 0F 6E /r
|
||||
|
||||
// MOVQ xmm, r/m64
|
||||
// 66 REX.W 0F 6E /r
|
||||
|
||||
try op(rt, 0x66);
|
||||
if (@intFromEnum(dest_xmm) >= 8 or size == 8) {
|
||||
try rex(rt, .{ .r = dest_xmm.x(), .w = size == 8 });
|
||||
}
|
||||
try op2(rt, 0x0F, 0x6E);
|
||||
// [rip + disp32]
|
||||
try modrm(rt, .{ .mod = 0b00, .reg = dest_xmm.reg(), .rm = Gpr.rbp.reg() });
|
||||
try allocRodataDisp32(rt, data);
|
||||
},
|
||||
},
|
||||
.symbol => |symbol| {},
|
||||
.stack => |disp| {
|
||||
const disp_small = std.math.minInt(i8) <= disp and disp <= std.math.maxInt(i8);
|
||||
const rex_prefix: Rex = .{
|
||||
.r = switch (dst_register) {
|
||||
.gpr => |dest_gpr| dest_gpr.x(),
|
||||
.xmm => |dest_xmm| dest_xmm.x(),
|
||||
},
|
||||
.w = size == 8,
|
||||
};
|
||||
|
||||
const mod: u2 = if (disp_small) 0b01 else 0b10;
|
||||
const reg: u3 = switch (dst_register) {
|
||||
.gpr => |dest_gpr| dest_gpr.reg(),
|
||||
.xmm => |dest_xmm| dest_xmm.reg(),
|
||||
};
|
||||
|
||||
switch (dst_register) {
|
||||
.gpr => |dest_gpr| {
|
||||
switch (size) {
|
||||
1 => {
|
||||
// MOV r8, r/m8
|
||||
// 8A /r
|
||||
if (@intFromEnum(dest_gpr) >= 4) {
|
||||
// NOTE spl, bpl, sil and dil need an empty REX prefix,
|
||||
// otherwise ah, ch, dh and bh would be used.
|
||||
try rex(rt, rex_prefix);
|
||||
}
|
||||
try op(rt, 0x8A);
|
||||
},
|
||||
2 => {
|
||||
// MOV r16, r/m16
|
||||
// 8B /r
|
||||
try op16(rt);
|
||||
if (@intFromEnum(dest_gpr) >= 8) {
|
||||
try rex(rt, rex_prefix);
|
||||
}
|
||||
try op(rt, 0x8B);
|
||||
},
|
||||
4 => {
|
||||
// MOV r32, r/m32
|
||||
// 8B /r
|
||||
if (@intFromEnum(dest_gpr) >= 8) {
|
||||
try rex(rt, rex_prefix);
|
||||
}
|
||||
try op(rt, 0x8B);
|
||||
},
|
||||
8 => {
|
||||
// MOV r64, r/m64
|
||||
// REX.W + 8B /r
|
||||
try rex(rt, rex_prefix);
|
||||
try op(rt, 0x8B);
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
},
|
||||
.xmm => |dest_xmm| {
|
||||
// MOVD xmm, r/m32
|
||||
// 66 0F 6E /r
|
||||
|
||||
// MOVQ xmm, r/m64
|
||||
// 66 REX.W 0F 6E /r
|
||||
|
||||
try op(rt, 0x66);
|
||||
if (@intFromEnum(dest_xmm) >= 8 or size == 8) {
|
||||
try rex(rt, rex_prefix);
|
||||
}
|
||||
try op2(rt, 0x0F, 0x6E);
|
||||
},
|
||||
}
|
||||
// [rbp + disp8/32]
|
||||
try modrm(rt, .{ .mod = mod, .reg = reg, .rm = Gpr.rbp.reg() });
|
||||
if (disp_small) {
|
||||
try disp8(rt, @intCast(disp));
|
||||
} else {
|
||||
try disp32(rt, disp);
|
||||
}
|
||||
},
|
||||
.cpu_flags => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Store register into value.
|
||||
pub fn store(rt: *Runtime, dst_value: *const StackValue, src_register: Register) !void {
|
||||
const size = dst_value.c_type.sizeOf().?;
|
||||
std.debug.assert(size == 1 or size == 2 or size == 4 or size == 8);
|
||||
}
|
||||
|
||||
// --- STACK OPERATIONS --------------------------------------------------------
|
||||
|
||||
/// Caller asserts that the stack is not empty.
|
||||
pub fn vsTop(rt: *Runtime) *StackValue {
|
||||
const vs = rt.virtual_stack.items;
|
||||
return &vs[vs.len - 1];
|
||||
}
|
||||
|
||||
/// Caller asserts that the stack has at least two values.
|
||||
pub fn vsSwap(rt: *Runtime) void {
|
||||
const vs = rt.virtual_stack.items;
|
||||
std.mem.swap(StackValue, &vs[vs.len - 1], &vs[vs.len - 2]);
|
||||
}
|
||||
|
||||
/// Ensure the top of the stack is in an XMM register. Returns the id of the
|
||||
/// register. Caller asserts that the stack is not empty.
|
||||
pub fn vsEnsureXmm(rt: *Runtime) Xmm {
|
||||
const top = vsTop(rt);
|
||||
switch (top.value) {
|
||||
.register => {},
|
||||
.constant => {},
|
||||
.symbol => {},
|
||||
.stack => {},
|
||||
.cpu_flags => {},
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensure the top of the stack is in a GPR register. Returns the id of the
|
||||
/// register. Caller asserts that teh stack is not empty.
|
||||
pub fn vsEnsureGpr(rt: *Runtime) Gpr {
|
||||
const top = vsTop(rt);
|
||||
switch (top.value) {
|
||||
.register => {},
|
||||
.constant => {},
|
||||
.symbol => {},
|
||||
.stack => {},
|
||||
.cpu_flags => {},
|
||||
}
|
||||
}
|
||||
|
||||
// --- DATA ALLOCATIONS --------------------------------------------------------
|
||||
|
||||
/// Reserve `data.len` bytes in rodata and fill it with `data`, then add a
|
||||
/// placeholder dips32 part of an instruction and a relocation entry for it.
|
||||
pub fn allocRodataDisp32(rt: *Runtime, data: []const u8) !void {
|
||||
const addr = rt.sections.text.data.items.len;
|
||||
const location: Location = .{ .rodata = rt.sections.rodata.data.items.len };
|
||||
|
||||
try rt.sections.rodata.writeBytes(data, rt.allocator);
|
||||
try disp32(rt, 0);
|
||||
try rt.relocation_table.append(rt.allocator, .{
|
||||
.addr = addr,
|
||||
.location = location,
|
||||
.type = .rip_disp32,
|
||||
});
|
||||
}
|
||||
32
packages/cjit/test/root.zig
Normal file
32
packages/cjit/test/root.zig
Normal file
@@ -0,0 +1,32 @@
|
||||
const std = @import("std");
|
||||
const cjit = @import("cjit");
|
||||
|
||||
fn add(a: i32, b: i32) callconv(cjit.call) i32 {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
test {
|
||||
var rt: cjit.Runtime = try .init(std.testing.allocator);
|
||||
defer rt.deinit();
|
||||
|
||||
try rt.compile("test.c",
|
||||
\\int add(int a, int b);
|
||||
\\
|
||||
\\int add_one(int x)
|
||||
\\{
|
||||
\\ return add(x, 1);
|
||||
\\}
|
||||
);
|
||||
|
||||
try rt.setSymbol(fn (i32, i32) callconv(cjit.call) i32, "add", &add);
|
||||
try rt.link();
|
||||
|
||||
const add_one = rt.getSymbol(fn (i32) callconv(cjit.call) i32, "add_one").?;
|
||||
|
||||
try std.testing.expectEqual(-9, add_one(-10));
|
||||
try std.testing.expectEqual(11, add_one(10));
|
||||
|
||||
const add_ptr = rt.getSymbol(fn (i32, i32) callconv(cjit.call) i32, "add").?;
|
||||
|
||||
try std.testing.expectEqual(add_ptr, &add);
|
||||
}
|
||||
@@ -195,7 +195,7 @@ const import = struct {
|
||||
no_add: bool = false,
|
||||
/// Internal use.
|
||||
no_exotic: bool = false,
|
||||
_pad14: u18 = 0,
|
||||
_pad18: u14 = 0,
|
||||
};
|
||||
|
||||
pub const JS_EVAL = packed struct(u32) {
|
||||
@@ -599,7 +599,7 @@ const import = struct {
|
||||
|
||||
pub const Allocator = struct {
|
||||
const alignment_bytes = 16;
|
||||
const alignment: std.mem.Alignment = .fromByteUnits(alignment_bytes);
|
||||
const alignment = std.mem.Alignment.fromByteUnits(alignment_bytes);
|
||||
|
||||
const AllocationPtr = *align(alignment_bytes) anyopaque;
|
||||
const AllocationSlice = []align(alignment_bytes) u8;
|
||||
@@ -1168,7 +1168,7 @@ pub const Value = extern struct {
|
||||
}
|
||||
|
||||
pub fn getClassId(self: Value) ClassId {
|
||||
return .{ .class_id = import.JS_GetClassID(self) };
|
||||
return .{ .class_id = import.JS_GetClassID(self.value) };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1182,7 +1182,7 @@ pub const ClassId = extern struct {
|
||||
pub const invalid: ClassId = .{ .class_id = 0 };
|
||||
|
||||
pub fn new() ClassId {
|
||||
var class_id: import.JSClassId = 0;
|
||||
var class_id: import.JSClassID = 0;
|
||||
return .{ .class_id = import.JS_NewClassID(&class_id) };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,38 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const vm_dep = b.dependency("vecmath", .{});
|
||||
const vm_module = vm_dep.module("vecmath");
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
const root_module = b.addModule("media", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
const vm_dep = b.dependency("vecmath", .{
|
||||
.target = target,
|
||||
});
|
||||
root_module.addImport("vecmath", vm_module);
|
||||
const vm_mod = vm_dep.module("vecmath");
|
||||
|
||||
const mod = b.addModule("media", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.link_libc = true,
|
||||
.imports = &.{
|
||||
.{ .name = "vecmath", .module = vm_mod },
|
||||
},
|
||||
});
|
||||
|
||||
mod.addCSourceFile(.{
|
||||
.file = b.path("src/stbi/stb_image.c"),
|
||||
.flags = &.{
|
||||
"-std=c17",
|
||||
"-Wall",
|
||||
"-Wextra",
|
||||
},
|
||||
.language = .c,
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.{
|
||||
.name = .media,
|
||||
.version = "0.0.0",
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.paths = .{
|
||||
"src",
|
||||
"build.zig",
|
||||
|
||||
BIN
packages/media/jpeg-specification.pdf
(Stored with Git LFS)
Normal file
BIN
packages/media/jpeg-specification.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
packages/media/png-specification.pdf
(Stored with Git LFS)
Normal file
BIN
packages/media/png-specification.pdf
(Stored with Git LFS)
Normal file
Binary file not shown.
25
packages/media/src/Format.zig
Normal file
25
packages/media/src/Format.zig
Normal file
@@ -0,0 +1,25 @@
|
||||
const std = @import("std");
|
||||
const Self = @This();
|
||||
|
||||
/// The number of bytes necessary to confirm that a buffer contains this media
|
||||
/// format.
|
||||
magic_length: usize,
|
||||
/// The number of bytes necessary to confirm that a buffer contains this media
|
||||
/// format and to extract metadata stored at the beggining, if any. Must be at
|
||||
/// least as big as `magic_length`.
|
||||
info_length: usize,
|
||||
/// The file extension usually associated with this media format; all in
|
||||
/// lowercase and without the dot character.
|
||||
extension: []const u8,
|
||||
/// The media type (aka MIME type or Content-Type) usually associated with this
|
||||
/// media format; not necessarily officially registered.
|
||||
media_type: []const u8,
|
||||
|
||||
/// Confirm whether a buffer contains this media format. The buffer doesn't have
|
||||
/// to contain the entire file, only its beginning. The caller asserts that
|
||||
/// `buffer` is at least `magic_length` bytes long.
|
||||
isFormat: *const fn (buffer: []const u8) bool,
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
@@ -9,14 +9,26 @@ pub const Static = struct {
|
||||
const sample_rate: f64 = @floatFromInt(self.sample_rate);
|
||||
return @floatCast(samples / sample_rate);
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
pub const Sample = extern struct {
|
||||
left: i16,
|
||||
right: i16,
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
pub const Stream = struct {
|
||||
source: std.io.Reader,
|
||||
source: *std.Io.Reader,
|
||||
sample_rate: u32,
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -19,11 +19,15 @@ pub fn Static(comptime W: u32, comptime H: u32) type {
|
||||
}
|
||||
|
||||
pub fn fill(self: *@This(), color: vm.Color) void {
|
||||
@memset(self.data, color);
|
||||
@memset(&self.data, color);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test "Static - refAllDecls" {
|
||||
std.testing.refAllDecls(Static(16, 16));
|
||||
}
|
||||
|
||||
pub const Dynamic = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
@@ -65,4 +69,55 @@ pub const Dynamic = struct {
|
||||
pub fn fill(self: *@This(), color: vm.Color) void {
|
||||
@memset(self.data[0 .. self.width * self.height], color);
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
pub const Hdr = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
|
||||
data: [*]vm.ColorHdr,
|
||||
|
||||
pub fn initBuffer(width: u32, height: u32, buffer: []vm.ColorHdr) @This() {
|
||||
std.debug.assert(buffer.len == width * height);
|
||||
return .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.data = buffer.ptr,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn initAlloc(width: u32, height: u32, allocator: std.mem.Allocator) !@This() {
|
||||
const buffer = try allocator.alloc(vm.ColorHdr, width * height);
|
||||
return .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.data = buffer.ptr,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *@This(), allocator: std.mem.Allocator) void {
|
||||
allocator.free(self.data[0 .. self.width * self.height]);
|
||||
}
|
||||
|
||||
pub fn getPixel(self: *const @This(), x: u32, y: u32) vm.ColorHdr {
|
||||
std.debug.assert(x < self.width and y < self.height);
|
||||
return self.data[y * self.width + x];
|
||||
}
|
||||
|
||||
pub fn setPixel(self: *@This(), x: u32, y: u32, color: vm.ColorHdr) void {
|
||||
std.debug.assert(x < self.width and y < self.height);
|
||||
self.data[y * self.width + x] = color;
|
||||
}
|
||||
|
||||
pub fn fill(self: *@This(), color: vm.ColorHdr) void {
|
||||
@memset(self.data[0 .. self.width * self.height], color);
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
151
packages/media/src/jpeg.zig
Normal file
151
packages/media/src/jpeg.zig
Normal file
@@ -0,0 +1,151 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Format = @import("Format.zig");
|
||||
|
||||
const format: Format = .{
|
||||
.magic_length = magic.len,
|
||||
// NOTE The information (like width and height) is not in a fixed position
|
||||
.info_length = magic.len,
|
||||
.extension = "jpeg",
|
||||
.media_type = "image/jpeg",
|
||||
.isFormat = isJpeg,
|
||||
};
|
||||
|
||||
const magic = "\xFF\xD8\xFF";
|
||||
|
||||
const Info = union(enum) {
|
||||
partial: void,
|
||||
full: Header,
|
||||
|
||||
pub fn makeFull(full: Header) Info {
|
||||
return .{ .full = full };
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
const Header = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
};
|
||||
|
||||
const Marker = enum(u8) {
|
||||
/// Start of frame, Huffman coding, Baseline DCT
|
||||
SOF_0 = 0xC0,
|
||||
/// Start of frame, Huffman coding, Extended sequential DCT
|
||||
SOF_1 = 0xC1,
|
||||
/// Start of frame, Huffman coding, Progressive DCT
|
||||
SOF_2 = 0xC2,
|
||||
/// Start of frame, Huffman coding, Lossless (sequential)
|
||||
SOF_3 = 0xC3,
|
||||
/// Start of frame, Huffman coding, Differential sequential DCT
|
||||
SOF_5 = 0xC5,
|
||||
/// Start of frame, Huffman coding, Differential progressive DCT
|
||||
SOF_6 = 0xC6,
|
||||
/// Start of frame, Huffman coding, Differential lossless (sequential)
|
||||
SOF_7 = 0xC7,
|
||||
/// Start of frame, arithmetic coding, Extended sequential DCT
|
||||
SOF_9 = 0xC9,
|
||||
/// Start of frame, arithmetic coding, Progressive DCT
|
||||
SOF_10 = 0xCA,
|
||||
/// Start of frame, arithmetic coding, Lossless (sequential)
|
||||
SOF_11 = 0xCB,
|
||||
/// Start of frame, arithmetic coding, Differential sequential DCT
|
||||
SOF_13 = 0xCD,
|
||||
/// Start of frame, arithmetic coding, Differential progressive DCT
|
||||
SOF_14 = 0xCE,
|
||||
/// Start of frame, arithmetic coding, Differential lossless (sequential)
|
||||
SOF_15 = 0xCF,
|
||||
/// Define Huffman table(s)
|
||||
DHT = 0xC4,
|
||||
/// Define arithmetic coding conditioning(s)
|
||||
DAC = 0xCC,
|
||||
/// Start of image
|
||||
SOI = 0xD8,
|
||||
/// End of image
|
||||
EOI = 0xD9,
|
||||
/// Start of scan
|
||||
SOS = 0xDA,
|
||||
/// Define quantization table(s)
|
||||
DQT = 0xDB,
|
||||
/// Define number of lines
|
||||
DNL = 0xDC,
|
||||
/// Define restart interval
|
||||
DRI = 0xDD,
|
||||
/// Define hierarchical progression
|
||||
DHP = 0xDE,
|
||||
/// Expand reference component(s)
|
||||
EXP = 0xDF,
|
||||
/// Comment
|
||||
COM = 0xFE,
|
||||
/// For temporary private use in arithmetic coding
|
||||
TEM = 0x01,
|
||||
|
||||
_,
|
||||
|
||||
/// Restart with modulo 8 count `m`
|
||||
pub fn RST(m: u3) Marker {
|
||||
return @enumFromInt(0xD0 | m);
|
||||
}
|
||||
|
||||
pub fn isRST(self: Marker) ?u3 {
|
||||
return if (@intFromEnum(self) & 0b1111_1000 == 0xD0) @intCast(@intFromEnum(self) & 0b0000_0111) else null;
|
||||
}
|
||||
|
||||
/// Reserved for application segments
|
||||
pub fn APP(n: u4) Marker {
|
||||
return @enumFromInt(0xE0 | n);
|
||||
}
|
||||
|
||||
pub fn isAPP(self: Marker) ?u3 {
|
||||
return if (@intFromEnum(self) & 0b1111_0000 == 0xE0) @intCast(@intFromEnum(self) & 0b0000_1111) else null;
|
||||
}
|
||||
|
||||
/// A standalone marker has no content and is not followed by segment length
|
||||
/// parameter.
|
||||
pub fn isStandalone(self: Marker) bool {
|
||||
return self.isRST() != null or
|
||||
self == .SOI or
|
||||
self == .EOI or
|
||||
self == .TEM;
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||
/// long.
|
||||
pub fn isJpeg(buffer: []const u8) bool {
|
||||
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
|
||||
}
|
||||
|
||||
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||
/// long. The information is not in a fixed position, so you need to provide a
|
||||
/// substantial amount of data, in the order of kilobytes. Depending on the
|
||||
/// amount of metadata, the information might be in the first kilobyte or dozens
|
||||
/// of kilobytes in.
|
||||
///
|
||||
/// This function returns:
|
||||
///
|
||||
/// - `null` when the buffer is not a JPEG or it's a malformed JPEG
|
||||
/// - `.partial` when the buffer appears to be a part of a JPEG, but the
|
||||
/// information could not be found within the buffer provided
|
||||
/// - `.full` when the buffer appears to be a part of a JPEG and the information
|
||||
/// was fully contained within the buffer provided
|
||||
pub fn info(buffer: []const u8) ?Info {
|
||||
std.debug.assert(buffer.len >= format.info_length);
|
||||
|
||||
if (!isJpeg(buffer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
41
packages/media/src/jxl.zig
Normal file
41
packages/media/src/jxl.zig
Normal file
@@ -0,0 +1,41 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Format = @import("Format.zig");
|
||||
|
||||
const format: Format = .{
|
||||
.magic_length = @max(magic_naked.len, magic_container.len),
|
||||
.info_length = @max(magic_naked.len, magic_container.len),
|
||||
.extension = "jxl",
|
||||
.media_type = "image/jxl",
|
||||
.isFormat = isJxl,
|
||||
};
|
||||
|
||||
const magic_naked = "\xFF\x0A";
|
||||
const magic_container = "\x00\x00\x00\x0CJXL \r\n\x87\n";
|
||||
|
||||
const Header = struct {};
|
||||
|
||||
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||
/// long.
|
||||
pub fn isJxl(buffer: []const u8) bool {
|
||||
std.debug.assert(buffer.len >= format.magic_length);
|
||||
|
||||
return std.mem.startsWith(u8, buffer, magic_naked) or
|
||||
std.mem.startsWith(u8, buffer, magic_container);
|
||||
}
|
||||
|
||||
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||
/// long.
|
||||
pub fn info(buffer: []const u8) ?Header {
|
||||
std.debug.assert(buffer.len >= format.info_length);
|
||||
|
||||
if (!isJxl(buffer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@panic("TODO");
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
645
packages/media/src/png.zig
Normal file
645
packages/media/src/png.zig
Normal file
@@ -0,0 +1,645 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Format = @import("Format.zig");
|
||||
|
||||
pub const format: Format = .{
|
||||
.magic_length = magic.len,
|
||||
// 4B - chunk length
|
||||
// 4B - chunk type
|
||||
// 13B - IHDR data
|
||||
// 4B - checksum
|
||||
.info_length = magic.len + 25,
|
||||
.extension = "png",
|
||||
.media_type = "image/png",
|
||||
.isFormat = isPng,
|
||||
};
|
||||
|
||||
const magic = "\x89PNG\r\n\x1A\n";
|
||||
|
||||
// --- IHDR --------------------------------------------------------------------
|
||||
|
||||
pub const Header = struct {
|
||||
width: u32,
|
||||
height: u32,
|
||||
bit_depth: BitDepth,
|
||||
color_type: ColorType,
|
||||
compression_method: CompressionMethod,
|
||||
filter_method: FilterMethod,
|
||||
interlace_method: InterlaceMethod,
|
||||
|
||||
pub fn decode(chunk: Chunk) !Header {
|
||||
std.debug.assert(chunk.chunk_type == .IHDR);
|
||||
|
||||
if (chunk.data.len != 13) return error.InvalidPng;
|
||||
const width = std.mem.readInt(u32, chunk.data[0..4], .big);
|
||||
const height = std.mem.readInt(u32, chunk.data[4..8], .big);
|
||||
const bit_depth = chunk.data[8];
|
||||
const color_type = chunk.data[9];
|
||||
const compression_method = chunk.data[10];
|
||||
const filter_method = chunk.data[11];
|
||||
const interlace_method = chunk.data[12];
|
||||
|
||||
if (width == 0 or width > 0x7FFF_FFFF or
|
||||
height == 0 or height > 0x7FFF_FFFF or
|
||||
bit_depth == 0 or bit_depth > 16 or !std.math.isPowerOfTwo(bit_depth) or
|
||||
color_type == 1 or color_type == 5 or color_type > 6 or
|
||||
compression_method != 0 or
|
||||
filter_method != 0 or
|
||||
interlace_method > 1)
|
||||
{
|
||||
return error.InvalidPng;
|
||||
}
|
||||
|
||||
switch (color_type) {
|
||||
0 => {
|
||||
// all bit depths allowed
|
||||
},
|
||||
2 => {
|
||||
if (bit_depth < 8) return error.InvalidPng;
|
||||
},
|
||||
3 => {
|
||||
if (bit_depth > 8) return error.InvalidPng;
|
||||
},
|
||||
4 => {
|
||||
if (bit_depth < 8) return error.InvalidPng;
|
||||
},
|
||||
6 => {
|
||||
if (bit_depth < 8) return error.InvalidPng;
|
||||
},
|
||||
else => unreachable,
|
||||
}
|
||||
|
||||
return .{
|
||||
.width = width,
|
||||
.height = height,
|
||||
.bit_depth = @enumFromInt(bit_depth),
|
||||
.color_type = @enumFromInt(color_type),
|
||||
.compression_method = @enumFromInt(compression_method),
|
||||
.filter_method = @enumFromInt(filter_method),
|
||||
.interlace_method = @enumFromInt(interlace_method),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
pub const BitDepth = enum(u8) {
|
||||
@"1" = 1,
|
||||
@"2" = 2,
|
||||
@"4" = 4,
|
||||
@"8" = 8,
|
||||
@"16" = 16,
|
||||
|
||||
pub fn range(self: BitDepth) usize {
|
||||
return @as(usize, 2) << @intCast(@intFromEnum(self));
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
pub const ColorType = enum(u8) {
|
||||
grayscale = 0,
|
||||
rgb = 2,
|
||||
palette = 3,
|
||||
grayscale_alpha = 4,
|
||||
rgba = 6,
|
||||
|
||||
pub fn paletteUsed(self: ColorType) bool {
|
||||
return @intFromEnum(self) & 0b0000_0001 != 0;
|
||||
}
|
||||
|
||||
pub fn colorUsed(self: ColorType) bool {
|
||||
return @intFromEnum(self) & 0b0000_0010 != 0;
|
||||
}
|
||||
|
||||
pub fn alphaChannelUsed(self: ColorType) bool {
|
||||
return @intFromEnum(self) & 0b0000_0100 != 0;
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
pub const CompressionMethod = enum(u8) {
|
||||
flate = 0,
|
||||
};
|
||||
|
||||
pub const FilterMethod = enum(u8) {
|
||||
adaptive = 0,
|
||||
};
|
||||
|
||||
pub const InterlaceMethod = enum(u8) {
|
||||
none = 0,
|
||||
adam7 = 1,
|
||||
};
|
||||
|
||||
// --- PLTE --------------------------------------------------------------------
|
||||
|
||||
pub const Palette = struct {
|
||||
len: u16,
|
||||
entries: [256][3]u8,
|
||||
|
||||
pub fn asSlice(self: *const Palette) []const [3]u8 {
|
||||
return self.entries[0..self.len];
|
||||
}
|
||||
|
||||
pub fn decode(chunk: Chunk, ctx: Header) !Palette {
|
||||
std.debug.assert(chunk.chunk_type == .PLTE);
|
||||
|
||||
const len = std.math.divExact(usize, chunk.data.len, 3) catch return error.InvalidPng;
|
||||
|
||||
switch (ctx.color_type) {
|
||||
.grayscale => return error.InvalidPng,
|
||||
.rgb => if (len < 1 or len > 256) return error.InvalidPng,
|
||||
.palette => if (len < 1 or len > ctx.bit_depth.range()) return error.InvalidPng,
|
||||
.grayscale_alpha => return error.InvalidPng,
|
||||
.rgba => if (len < 1 or len > 256) return error.InvalidPng,
|
||||
}
|
||||
|
||||
var entries: [256][3]u8 = undefined;
|
||||
@memcpy(entries[0..len], @as([]const [3]u8, @ptrCast(chunk.data)));
|
||||
return .{
|
||||
.len = @intCast(len),
|
||||
.entries = entries,
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
// --- tRNS --------------------------------------------------------------------
|
||||
|
||||
pub const Transparency = union(enum) {
|
||||
grayscale: u16,
|
||||
rgb: [3]u16,
|
||||
palette: TransparencyPalette,
|
||||
|
||||
pub fn initGrayscale(grayscale: u16) Transparency {
|
||||
return .{ .grayscale = grayscale };
|
||||
}
|
||||
|
||||
pub fn initRgb(rgb: [3]u16) Transparency {
|
||||
return .{ .rgb = rgb };
|
||||
}
|
||||
|
||||
pub fn initPalette(palette: TransparencyPalette) Transparency {
|
||||
return .{ .palette = palette };
|
||||
}
|
||||
|
||||
pub fn decode(chunk: Chunk, header: Header, maybe_palette: ?Palette) !Transparency {
|
||||
std.debug.assert(chunk.chunk_type == .tRNS);
|
||||
|
||||
return switch (header.color_type) {
|
||||
.grayscale => {
|
||||
if (chunk.data.len != 2) return error.InvalidPng;
|
||||
const grayscale = std.mem.readInt(u16, chunk.data[0..2], .big);
|
||||
return .initGrayscale(grayscale);
|
||||
},
|
||||
.rgb => {
|
||||
if (chunk.data.len != 6) return error.InvalidPng;
|
||||
const r = std.mem.readInt(u16, chunk.data[0..2], .big);
|
||||
const g = std.mem.readInt(u16, chunk.data[2..4], .big);
|
||||
const b = std.mem.readInt(u16, chunk.data[4..6], .big);
|
||||
return .initRgb(.{ r, g, b });
|
||||
},
|
||||
.palette => if (maybe_palette) |palette| {
|
||||
const len = chunk.data.len;
|
||||
if (len < 1 or len > palette.len) return error.InvalidPng;
|
||||
var entries: [256]u8 = undefined;
|
||||
@memcpy(entries[0..len], chunk.data[0..len]);
|
||||
return .initPalette(.{
|
||||
.len = @intCast(len),
|
||||
.entries = entries,
|
||||
});
|
||||
} else {
|
||||
return error.InvalidPng;
|
||||
},
|
||||
.grayscale_alpha => error.InvalidPng,
|
||||
.rgba => error.InvalidPng,
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
const TransparencyPalette = struct {
|
||||
len: u16,
|
||||
entries: [256]u8,
|
||||
|
||||
pub fn asSlice(self: *const TransparencyPalette) []const u8 {
|
||||
return self.entries[0..self.len];
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
// --- gAMA --------------------------------------------------------------------
|
||||
|
||||
const Gamma = struct {
|
||||
/// Gamma times `scale`, i.e. 100000, For example, a gamma of 1/2.2 would be
|
||||
/// stored as 45455.
|
||||
gamma: u32,
|
||||
|
||||
const scale = 100000;
|
||||
|
||||
pub fn initExponent(exponent: f32) Gamma {
|
||||
const gamma: u32 = @intFromFloat(@round(exponent * scale));
|
||||
return .{ .gamma = gamma };
|
||||
}
|
||||
|
||||
pub fn asExponent(self: Gamma) f32 {
|
||||
return @as(f32, @floatFromInt(self.gamma)) / scale;
|
||||
}
|
||||
|
||||
pub fn decode(chunk: Chunk) Gamma {
|
||||
std.debug.assert(chunk.chunk_type == .gAMA);
|
||||
|
||||
if (chunk.data.len != 4) return error.InvalidPng;
|
||||
const gamma = std.mem.readInt(u32, chunk.data[0..4], .big);
|
||||
return .{ .gamma = gamma };
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
// --- cHRM --------------------------------------------------------------------
|
||||
|
||||
const Chromaticities = struct {
|
||||
white_point_x: u32,
|
||||
white_point_y: u32,
|
||||
red_x: u32,
|
||||
red_y: u32,
|
||||
green_x: u32,
|
||||
green_y: u32,
|
||||
blue_x: u32,
|
||||
blue_y: u32,
|
||||
|
||||
pub fn decode(chunk: Chunk) !Chunk {
|
||||
std.debug.assert(chunk.chunk_type == .cHRM);
|
||||
|
||||
if (chunk.data.len != 32) return error.InvalidPng;
|
||||
return .{
|
||||
.white_point_x = std.mem.readInt(u32, chunk.data[0..4], .big),
|
||||
.white_point_y = std.mem.readInt(u32, chunk.data[4..8], .big),
|
||||
.red_x = std.mem.readInt(u32, chunk.data[8..12], .big),
|
||||
.red_y = std.mem.readInt(u32, chunk.data[12..16], .big),
|
||||
.green_x = std.mem.readInt(u32, chunk.data[16..20], .big),
|
||||
.green_y = std.mem.readInt(u32, chunk.data[20..24], .big),
|
||||
.blue_x = std.mem.readInt(u32, chunk.data[24..28], .big),
|
||||
.blue_y = std.mem.readInt(u32, chunk.data[28..32], .big),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
// --- sRGB --------------------------------------------------------------------
|
||||
|
||||
const RenderingIntent = enum(u8) {
|
||||
/// Perceptual intent is for images preferring good adaptation to the output
|
||||
/// device gamut at the expense of colorimetric accuracy, like photographs.
|
||||
perceptual = 0,
|
||||
/// Relative colorimetric intent is for images requiring color appearance
|
||||
/// matching (relative to the output device white point), like logos.
|
||||
relative_colorimetric = 1,
|
||||
/// Saturation intent is for images preferring preservation of saturation at
|
||||
/// the expense of hue and lightness, like charts and graphs.
|
||||
saturation = 2,
|
||||
/// Absolute colorimetric intent is for images requiring preservation of
|
||||
/// absolute colorimetry, like proofs (previews of images destined for a
|
||||
/// different output device).
|
||||
absolute_colorimetric = 3,
|
||||
|
||||
pub fn decode(chunk: Chunk) RenderingIntent {
|
||||
std.debug.assert(chunk.chunk_type == .sRGB);
|
||||
|
||||
if (chunk.data.len != 1) return error.InvalidPng;
|
||||
const rendering_intent = chunk.data[0];
|
||||
if (rendering_intent > 3) return error.InvalidPng;
|
||||
return @enumFromInt(rendering_intent);
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
// --- pHYs --------------------------------------------------------------------
|
||||
|
||||
pub const PhysicalPixelDimensions = struct {
|
||||
pixels_per_unit_x: u32,
|
||||
pixels_per_unit_y: u32,
|
||||
unit: PhysicalUnit,
|
||||
|
||||
pub fn decode(chunk: Chunk) !PhysicalPixelDimensions {
|
||||
std.debug.assert(chunk.chunk_type == .pHYs);
|
||||
|
||||
if (chunk.data.len != 9) return error.InvalidPng;
|
||||
const pixels_per_unit_x = std.mem.readInt(u32, chunk.data[0..4], .big);
|
||||
const pixels_per_unit_y = std.mem.readInt(u32, chunk.data[4..8], .big);
|
||||
const unit = chunk.data[8];
|
||||
|
||||
if (pixels_per_unit_x == 0 or pixels_per_unit_y == 0 or unit > 1) {
|
||||
return error.InvalidPng;
|
||||
}
|
||||
|
||||
return .{
|
||||
.pixels_per_unit_x = pixels_per_unit_x,
|
||||
.pixels_per_unit_y = pixels_per_unit_y,
|
||||
.unit = @enumFromInt(unit),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
pub const PhysicalUnit = enum(u8) {
|
||||
unknown = 0,
|
||||
meter = 1,
|
||||
};
|
||||
|
||||
// --- tIME --------------------------------------------------------------------
|
||||
|
||||
pub const LastModificationTime = struct {
|
||||
year: u16,
|
||||
/// 1 - 12
|
||||
month: u8,
|
||||
/// 1 - 31
|
||||
day: u8,
|
||||
/// 0 - 23
|
||||
hour: u8,
|
||||
/// 0 - 59
|
||||
minute: u8,
|
||||
/// 0 - 60 (may include leap seconds)
|
||||
second: u8,
|
||||
|
||||
pub fn decode(chunk: Chunk) !LastModificationTime {
|
||||
std.debug.assert(chunk.chunk_type == .tIME);
|
||||
|
||||
if (chunk.data.len == 7) return error.InvalidPng;
|
||||
const year = std.mem.readInt(u16, chunk.data[0..2], .big);
|
||||
const month = chunk.data[2];
|
||||
const day = chunk.data[3];
|
||||
const hour = chunk.data[4];
|
||||
const minute = chunk.data[5];
|
||||
const second = chunk.data[6];
|
||||
|
||||
// NOTE This implementation is intentionally trying not to be too clever
|
||||
// with validation.
|
||||
if (month < 1 or month > 12 or
|
||||
day < 1 or day > 31 or
|
||||
hour > 23 or
|
||||
minute > 59 or
|
||||
second > 60)
|
||||
{
|
||||
return error.InvalidPng;
|
||||
}
|
||||
|
||||
return .{
|
||||
.year = year,
|
||||
.month = month,
|
||||
.day = day,
|
||||
.hour = hour,
|
||||
.minute = minute,
|
||||
.second = second,
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
pub const ChunkType = enum(u32) {
|
||||
IHDR = fromName("IHDR"),
|
||||
PLTE = fromName("PLTE"),
|
||||
IDAT = fromName("IDAT"),
|
||||
IEND = fromName("IEND"),
|
||||
tRNS = fromName("tRNS"),
|
||||
gAMA = fromName("gAMA"),
|
||||
cHRM = fromName("cHRM"),
|
||||
sRGB = fromName("sRGB"),
|
||||
iCCP = fromName("iCCP"),
|
||||
tEXt = fromName("tEXt"),
|
||||
zTXt = fromName("zTXt"),
|
||||
iTXt = fromName("iTXt"),
|
||||
bKGD = fromName("bKGD"),
|
||||
pHYs = fromName("pHYs"),
|
||||
sBIT = fromName("sBIT"),
|
||||
sPLT = fromName("sPLT"),
|
||||
hIST = fromName("hIST"),
|
||||
tIME = fromName("tIME"),
|
||||
_,
|
||||
|
||||
fn fromName(name: *const [4]u8) u32 {
|
||||
return @bitCast(name.*);
|
||||
}
|
||||
|
||||
pub fn fromBytes(bytes: *const [4]u8) ChunkType {
|
||||
return @enumFromInt(fromName(bytes));
|
||||
}
|
||||
|
||||
pub fn toBytes(self: ChunkType) [4]u8 {
|
||||
return @bitCast(@intFromEnum(self));
|
||||
}
|
||||
|
||||
pub fn ancillary(self: ChunkType) bool {
|
||||
return self.toBytes()[0] & 0b0010_0000 != 0;
|
||||
}
|
||||
|
||||
pub fn private(self: ChunkType) bool {
|
||||
return self.toBytes()[1] & 0b0010_0000 != 0;
|
||||
}
|
||||
|
||||
pub fn safeToCopy(self: ChunkType) bool {
|
||||
return self.toBytes()[3] & 0b0010_0000 != 0;
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
pub const Chunk = struct {
|
||||
chunk_type: ChunkType,
|
||||
data: []const u8,
|
||||
};
|
||||
|
||||
pub const StandardKeyword = enum {
|
||||
/// Short (one line) title or caption for image
|
||||
Title,
|
||||
/// Name of image’s creator
|
||||
Author,
|
||||
/// Description of image (possibly long)
|
||||
Description,
|
||||
/// Copyright notice
|
||||
Copyright,
|
||||
/// Time Time of original image creation
|
||||
Creation,
|
||||
/// Software used to create the image
|
||||
Software,
|
||||
/// Legal disclaimer
|
||||
Disclaimer,
|
||||
/// Warning of nature of content
|
||||
Warning,
|
||||
/// Device used to create the image
|
||||
Source,
|
||||
/// Miscellaneous comment; conversion from GIF comment
|
||||
Comment,
|
||||
|
||||
pub const map: std.StaticStringMap(StandardKeyword) = blk: {
|
||||
const fields = @typeInfo(StandardKeyword).@"enum".fields;
|
||||
|
||||
var kvs_list: [fields.len]struct { []const u8, StandardKeyword } = undefined;
|
||||
for (fields, 0..) |field, i| {
|
||||
kvs_list[i] = .{ field.name, @field(StandardKeyword, field.name) };
|
||||
}
|
||||
|
||||
break :blk .initComptime(kvs_list);
|
||||
};
|
||||
|
||||
pub fn isStandardKeyword(keyword: []const u8) ?StandardKeyword {
|
||||
return map.get(keyword);
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||
/// long.
|
||||
pub fn isPng(buffer: []const u8) bool {
|
||||
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
|
||||
}
|
||||
|
||||
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||
/// long.
|
||||
pub fn info(buffer: []const u8) ?Header {
|
||||
std.debug.assert(buffer.len >= format.info_length);
|
||||
|
||||
if (!isPng(buffer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const chunk, _ = (decodeChunk(buffer[format.magic_length..]) catch return null) orelse return null;
|
||||
if (chunk.chunk_type != .IHDR) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const header = Header.decode(chunk) catch return null;
|
||||
return header;
|
||||
}
|
||||
|
||||
pub fn decodeChunk(buffer: []const u8) !?struct { Chunk, []const u8 } {
|
||||
var rest: []const u8 = buffer;
|
||||
|
||||
if (rest.len < 8) {
|
||||
// Not enough data
|
||||
return null;
|
||||
}
|
||||
|
||||
const length = std.mem.readInt(u32, rest[0..4], .big);
|
||||
const chunk_type: ChunkType = .fromBytes(rest[4..8]);
|
||||
rest = rest[8..];
|
||||
|
||||
if (length > 0x7FFF_FFFF) {
|
||||
return error.InvalidPng;
|
||||
}
|
||||
|
||||
if (rest.len < length) {
|
||||
// Not enough data
|
||||
return null;
|
||||
}
|
||||
|
||||
const data = rest[0..length];
|
||||
rest = rest[length..];
|
||||
|
||||
if (rest.len < 4) {
|
||||
// Not enough data
|
||||
return null;
|
||||
}
|
||||
|
||||
const crc = std.mem.readInt(u32, rest[0..4], .big);
|
||||
rest = rest[4..];
|
||||
|
||||
if (std.hash.crc.Crc32IsoHdlc.hash(data) != crc) {
|
||||
return error.InvalidPng;
|
||||
}
|
||||
|
||||
return .{
|
||||
.{ .chunk_type = chunk_type, .data = data },
|
||||
rest,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn decodeChunks(buffer: []const u8, chunks: ?[]Chunk) !usize {
|
||||
if (buffer.len < format.magic_length or !isPng(buffer)) {
|
||||
return error.InvalidPng;
|
||||
}
|
||||
|
||||
var index: usize = 0;
|
||||
var rest: []const u8 = buffer[format.magic_length..];
|
||||
|
||||
while (rest.len > 0) : (index += 1) {
|
||||
const chunk, rest = try decodeChunk(rest) orelse return error.InvalidPng;
|
||||
|
||||
if (chunks) |ck| {
|
||||
if (index < ck.len) {
|
||||
ck[index] = chunk;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
pub fn decodeChunksAlloc(buffer: []const u8, allocator: std.mem.Allocator) ![]Chunk {
|
||||
const n = try decodeChunks(buffer, null);
|
||||
|
||||
const chunks = try allocator.alloc(Chunk, n);
|
||||
errdefer allocator.free(chunks);
|
||||
|
||||
_ = try decodeChunks(buffer, chunks);
|
||||
return chunks;
|
||||
}
|
||||
|
||||
pub fn encodeChunks(chunks: []const Chunk, writer: *std.Io.Writer) !void {
|
||||
try writer.writeAll(magic);
|
||||
|
||||
for (chunks) |chunk| {
|
||||
var crc: std.hash.crc.Crc32IsoHdlc = .init();
|
||||
var data: []const u8 = chunk.data;
|
||||
|
||||
while (data.len > 0) {
|
||||
const bytes_written = try writer.write(data);
|
||||
crc.update(data[0..bytes_written]);
|
||||
data = data[bytes_written..];
|
||||
}
|
||||
|
||||
try writer.writeInt(u32, crc.final(), .big);
|
||||
}
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
@@ -1,8 +1,31 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Format = @import("Format.zig");
|
||||
|
||||
const format: Format = .{
|
||||
.magic_length = magic.len,
|
||||
// 4B - sample count
|
||||
// 1B - channels
|
||||
// 3B - sample rate
|
||||
.info_length = magic.len + 8,
|
||||
.extension = "qoa",
|
||||
.media_type = "audio/qoa",
|
||||
.isFormat = isQoa,
|
||||
};
|
||||
|
||||
const magic = "qoaf";
|
||||
|
||||
const Header = union(enum) {
|
||||
streaming: HeaderStreaming,
|
||||
static: HeaderStatic,
|
||||
|
||||
pub fn initStatic(static: HeaderStatic) Header {
|
||||
return .{ .static = static };
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
const HeaderStreaming = void;
|
||||
@@ -17,32 +40,48 @@ const HeaderStatic = struct {
|
||||
const sample_rate: f64 = @floatFromInt(self.sample_rate);
|
||||
return @floatCast(samples / sample_rate);
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
/// The caller asserts that the buffer is at least 12 bytes long, which can
|
||||
/// contain the entirety of a QOA file header and the relevant information in
|
||||
/// the first frame header.
|
||||
pub fn info(buffer: []const u8) ?Header {
|
||||
std.debug.assert(buffer.len >= 12);
|
||||
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||
/// long.
|
||||
pub fn isQoa(buffer: []const u8) bool {
|
||||
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
|
||||
}
|
||||
|
||||
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||
/// long.
|
||||
pub fn info(buffer: []const u8) ?Header {
|
||||
std.debug.assert(buffer.len >= format.info_length);
|
||||
|
||||
if (!isQoa(buffer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const magic = buffer[0..4];
|
||||
const samples = std.mem.readInt(u32, buffer[4..8], .big);
|
||||
const channels = buffer[8];
|
||||
const sample_rate = std.mem.readInt(u24, buffer[9..12], .big);
|
||||
|
||||
if (!std.mem.eql(u8, magic, "qoaf") or channels == 0 or channels > 8 or sample_rate == 0) {
|
||||
if (channels == 0 or channels > 8 or
|
||||
sample_rate == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (samples == 0) {
|
||||
return .streaming;
|
||||
} else {
|
||||
return .{
|
||||
.static = .{
|
||||
return .initStatic(.{
|
||||
.samples = samples,
|
||||
.channels = channels,
|
||||
.sample_rate = sample_rate,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Format = @import("Format.zig");
|
||||
|
||||
const format: Format = .{
|
||||
.magic_length = magic.len,
|
||||
// 4B - width
|
||||
// 4B - height
|
||||
// 1B - channels
|
||||
// 1B - color space
|
||||
.info_length = magic.len + 10,
|
||||
.extension = "qoi",
|
||||
.media_type = "image/qoi",
|
||||
.isFormat = isQoi,
|
||||
};
|
||||
|
||||
const magic = "qoif";
|
||||
|
||||
const Channels = enum(u8) {
|
||||
rgb = 3,
|
||||
rgba = 4,
|
||||
@@ -17,18 +33,31 @@ const Header = struct {
|
||||
color_space: ColorSpace,
|
||||
};
|
||||
|
||||
/// The caller asserts that the buffer is at least 14 bytes long, which can
|
||||
/// contain the entirety of a QOI header.
|
||||
pub fn info(buffer: []const u8) ?Header {
|
||||
std.debug.assert(buffer.len >= 14);
|
||||
/// The caller asserts that the buffer is at least `format.magic_length` bytes
|
||||
/// long.
|
||||
pub fn isQoi(buffer: []const u8) bool {
|
||||
return std.mem.eql(u8, buffer[0..format.magic_length], magic);
|
||||
}
|
||||
|
||||
/// The caller asserts that the buffer is at least `format.info_length` bytes
|
||||
/// long.
|
||||
pub fn info(buffer: []const u8) ?Header {
|
||||
std.debug.assert(buffer.len >= format.info_length);
|
||||
|
||||
if (!isQoi(buffer)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const magic = buffer[0..4];
|
||||
const width = std.mem.readInt(u32, buffer[4..8], .big);
|
||||
const height = std.mem.readInt(u32, buffer[8..12], .big);
|
||||
const channels = buffer[12];
|
||||
const color_space = buffer[13];
|
||||
|
||||
if (!std.mem.eql(u8, magic, "qoif") or width == 0 or height == 0 or channels < 3 or channels > 4 or color_space > 1) {
|
||||
if (width == 0 or
|
||||
height == 0 or
|
||||
channels < 3 or channels > 4 or
|
||||
color_space > 1)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -39,3 +68,7 @@ pub fn info(buffer: []const u8) ?Header {
|
||||
.color_space = @enumFromInt(color_space),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const audio = @import("audio.zig");
|
||||
pub const Format = @import("Format.zig");
|
||||
pub const image = @import("image.zig");
|
||||
pub const jpeg = @import("jpeg.zig");
|
||||
pub const jxl = @import("jxl.zig");
|
||||
pub const png = @import("png.zig");
|
||||
pub const qoa = @import("qoa.zig");
|
||||
pub const qoi = @import("qoi.zig");
|
||||
pub const stbi = @import("stbi.zig");
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
310
packages/media/src/stbi.zig
Normal file
310
packages/media/src/stbi.zig
Normal file
@@ -0,0 +1,310 @@
|
||||
const std = @import("std");
|
||||
const Self = @This();
|
||||
|
||||
const image = @import("image.zig");
|
||||
const vm = @import("vecmath");
|
||||
|
||||
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 {
|
||||
extern fn stbi_load_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u8;
|
||||
extern fn stbi_load_16_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u16;
|
||||
extern fn stbi_loadf_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) f32;
|
||||
|
||||
extern fn stbi_load_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u8;
|
||||
extern fn stbi_load_16_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) u16;
|
||||
extern fn stbi_loadf_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32, desired_channels: i32) ?[*]align(alignment.toByteUnits()) f32;
|
||||
|
||||
extern fn stbi_info_from_memory(buffer: [*]const u8, len: i32, x: ?*i32, y: ?*i32, channels_in_file: ?*i32) i32;
|
||||
extern fn stbi_is_16_bit_from_memory(buffer: [*]const u8, len: i32) i32;
|
||||
extern fn stbi_is_hdr_from_memory(buffer: [*]const u8, len: i32) i32;
|
||||
|
||||
extern fn stbi_info_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque, x: ?*i32, y: ?*i32, channels_in_file: ?*i32) i32;
|
||||
extern fn stbi_is_16_bit_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque) i32;
|
||||
extern fn stbi_is_hdr_from_callbacks(callbacks: *const IoCallbacks, ctx: ?*anyopaque) i32;
|
||||
};
|
||||
|
||||
pub fn loadStaticBuf(self: *Self, comptime W: u32, comptime H: u32, buf: []const u8) !image.Static(W, H) {
|
||||
current_self = self;
|
||||
defer current_self = undefined;
|
||||
|
||||
var x: i32 = undefined;
|
||||
var y: i32 = undefined;
|
||||
|
||||
const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, null, 4) orelse return error.StbiError;
|
||||
defer castle_media_stbi_free(res);
|
||||
|
||||
if (x != W or y != H) return error.WrongDimensions;
|
||||
|
||||
return .{ .data = @as(*const [W * H]vm.Color, @ptrCast(@alignCast(res))).* };
|
||||
}
|
||||
|
||||
pub fn loadStaticIo(self: *Self, comptime W: u32, comptime H: u32, reader: *std.Io.Reader) !image.Static(W, H) {
|
||||
current_self = self;
|
||||
defer current_self = undefined;
|
||||
|
||||
var x: i32 = undefined;
|
||||
var y: i32 = undefined;
|
||||
|
||||
const res = import.stbi_load_from_callbacks(&.std_io_reader_interface, reader, &x, &y, null, 4) orelse return error.StbiError;
|
||||
defer castle_media_stbi_free(res);
|
||||
|
||||
if (x != W or y != H) return error.WrongDimensions;
|
||||
|
||||
return .{ .data = @as(*const [W * H]vm.Color, @ptrCast(@alignCast(res))).* };
|
||||
}
|
||||
|
||||
/// On success, must free memory by calling `freeDynamic` method.
|
||||
pub fn loadDynamicBuf(self: *Self, buf: []const u8) !image.Dynamic {
|
||||
current_self = self;
|
||||
defer current_self = undefined;
|
||||
|
||||
var x: i32 = undefined;
|
||||
var y: i32 = undefined;
|
||||
|
||||
const res = import.stbi_load_from_memory(buf.ptr, @intCast(buf.len), &x, &y, null, 4) orelse return error.StbiError;
|
||||
|
||||
const buffer_ptr: [*]vm.Color = @ptrCast(@alignCast(res));
|
||||
const ux: u32 = @intCast(x);
|
||||
const uy: u32 = @intCast(y);
|
||||
|
||||
return .initBuffer(ux, uy, buffer_ptr[0 .. ux * uy]);
|
||||
}
|
||||
|
||||
/// On success, must free memory by calling `freeDynamic` method.
|
||||
pub fn loadDynamicIo(self: *Self, reader: *std.Io.Reader) !image.Dynamic {
|
||||
current_self = self;
|
||||
defer current_self = undefined;
|
||||
|
||||
var x: i32 = undefined;
|
||||
var y: i32 = undefined;
|
||||
|
||||
const res = import.stbi_load_from_callbacks(&.std_io_reader_interface, reader, &x, &y, null, 4) orelse return error.StbiError;
|
||||
|
||||
const buffer_ptr: [*]vm.Color = @ptrCast(@alignCast(res));
|
||||
const ux: u32 = @intCast(x);
|
||||
const uy: u32 = @intCast(y);
|
||||
|
||||
return .initBuffer(ux, uy, buffer_ptr[0 .. ux * uy]);
|
||||
}
|
||||
|
||||
/// On success, must free memory by calling `freeHdr` method.
|
||||
pub fn loadHdrBuf(self: *Self, buf: []const u8) !image.Hdr {
|
||||
current_self = self;
|
||||
defer current_self = undefined;
|
||||
|
||||
var x: i32 = undefined;
|
||||
var y: i32 = undefined;
|
||||
|
||||
const res_f32 = import.stbi_loadf_from_memory(buf.ptr, @intCast(buf.len), &x, &y, null, 4) orelse return error.StbiError;
|
||||
defer castle_media_stbi_free(res_f32);
|
||||
|
||||
const ux: u32 = @intCast(x);
|
||||
const uy: u32 = @intCast(y);
|
||||
const buffer_ptr_f32: [*]vm.Vector4 = @ptrCast(res_f32);
|
||||
const buffer_ptr_f16: [*]vm.ColorHdr = @ptrCast(castle_media_stbi_malloc(ux * uy * @sizeOf(vm.ColorHdr)) orelse return error.OutOfMemory);
|
||||
errdefer castle_media_stbi_free(buffer_ptr_f16);
|
||||
|
||||
for (buffer_ptr_f16[0 .. ux * uy], buffer_ptr_f32[0 .. ux * uy]) |*sample_f16, sample_f32| {
|
||||
sample_f16.* = .init(
|
||||
std.math.clamp(@as(f16, @floatCast(sample_f32.x)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||
std.math.clamp(@as(f16, @floatCast(sample_f32.y)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||
std.math.clamp(@as(f16, @floatCast(sample_f32.z)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||
std.math.clamp(@as(f16, @floatCast(sample_f32.w)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||
);
|
||||
}
|
||||
|
||||
return .initBuffer(ux, uy, buffer_ptr_f16[0 .. ux * uy]);
|
||||
}
|
||||
|
||||
/// On success, must free memory by calling `freeHdr` method.
|
||||
pub fn loadHdrIo(self: *Self, reader: *std.Io.Reader) !image.Hdr {
|
||||
current_self = self;
|
||||
defer current_self = undefined;
|
||||
|
||||
var x: i32 = undefined;
|
||||
var y: i32 = undefined;
|
||||
|
||||
const res_f32 = import.stbi_loadf_from_callbacks(&.std_io_reader_interface, reader, &x, &y, null, 4) orelse return error.StbiError;
|
||||
defer castle_media_stbi_free(res_f32);
|
||||
|
||||
const ux: u32 = @intCast(x);
|
||||
const uy: u32 = @intCast(y);
|
||||
const buffer_ptr_f32: [*]vm.Vector4 = @ptrCast(res_f32);
|
||||
const buffer_ptr_f16: [*]vm.ColorHdr = @ptrCast(castle_media_stbi_malloc(ux * uy * @sizeOf(vm.ColorHdr)) orelse return error.OutOfMemory);
|
||||
errdefer castle_media_stbi_free(buffer_ptr_f16);
|
||||
|
||||
for (buffer_ptr_f16[0 .. ux * uy], buffer_ptr_f32[0 .. ux * uy]) |*sample_f16, sample_f32| {
|
||||
sample_f16.* = .init(
|
||||
std.math.clamp(@as(f16, @floatCast(sample_f32.x)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||
std.math.clamp(@as(f16, @floatCast(sample_f32.y)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||
std.math.clamp(@as(f16, @floatCast(sample_f32.z)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||
std.math.clamp(@as(f16, @floatCast(sample_f32.w)), -std.math.floatMax(f16), std.math.floatMax(f16)),
|
||||
);
|
||||
}
|
||||
|
||||
return .initBuffer(ux, uy, buffer_ptr_f16[0 .. ux * uy]);
|
||||
}
|
||||
|
||||
pub fn freeDynamic(self: *Self, img: image.Dynamic) void {
|
||||
current_self = self;
|
||||
defer current_self = undefined;
|
||||
|
||||
castle_media_stbi_free(@ptrCast(@alignCast(img.data)));
|
||||
}
|
||||
|
||||
pub fn freeHdr(self: *Self, img: image.Hdr) void {
|
||||
current_self = self;
|
||||
defer current_self = undefined;
|
||||
|
||||
castle_media_stbi_free(@ptrCast(@alignCast(img.data)));
|
||||
}
|
||||
|
||||
// --- IO INTERFACE ------------------------------------------------------------
|
||||
|
||||
pub const IoCallbacks = extern struct {
|
||||
/// Fill `data` with `size` bytes. Return number of bytes actually read.
|
||||
read: ?*const fn (ctx: ?*anyopaque, data: [*]u8, size: i32) callconv(.c) i32,
|
||||
/// Skip the next `n` bytes, or backtrack `-n` bytes if `n < 0`.
|
||||
skip: ?*const fn (ctx: ?*anyopaque, n: i32) callconv(.c) i32,
|
||||
/// Return non-zero value if at the end of file/data.
|
||||
eof: ?*const fn (cxt: ?*anyopaque) callconv(.c) i32,
|
||||
|
||||
pub const std_io_reader_interface: IoCallbacks = .{
|
||||
.read = stdIoReader_ReadFn,
|
||||
.skip = stdIoReader_SkipFn,
|
||||
.eof = stdIoReader_EofFn,
|
||||
};
|
||||
|
||||
pub fn stdIoReader_ReadFn(ctx: ?*anyopaque, data: [*]u8, size: i32) callconv(.c) i32 {
|
||||
const reader: *std.Io.Reader = @ptrCast(@alignCast(ctx.?));
|
||||
const bytes_read = reader.readSliceShort(data[0..@intCast(size)]) catch return 0;
|
||||
return @intCast(bytes_read);
|
||||
}
|
||||
|
||||
pub fn stdIoReader_SkipFn(ctx: ?*anyopaque, n: i32) callconv(.c) i32 {
|
||||
const reader: *std.Io.Reader = @ptrCast(@alignCast(ctx.?));
|
||||
// NOTE stb_image.h actually discards the return value from this
|
||||
// callback. If an actual error occurs, we're cooked (but it will be
|
||||
// very likely caught as a parsing error later).
|
||||
_ = reader.discardAll(@intCast(n)) catch return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn stdIoReader_EofFn(ctx: ?*anyopaque) callconv(.c) i32 {
|
||||
const reader: *std.Io.Reader = @ptrCast(@alignCast(ctx.?));
|
||||
|
||||
_ = reader.peekByte() catch return 1;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// --- MALLOC INTERFACE --------------------------------------------------------
|
||||
|
||||
threadlocal var current_self: *Self = undefined;
|
||||
|
||||
export fn castle_media_stbi_malloc(size: usize) callconv(.c) VoidPtr {
|
||||
const self = current_self;
|
||||
|
||||
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_stbi_realloc(maybe_ptr: VoidPtr, size: usize) callconv(.c) VoidPtr {
|
||||
const self = current_self;
|
||||
|
||||
self.mutex.lock(self.io) catch return null;
|
||||
defer self.mutex.unlock(self.io);
|
||||
|
||||
// NOTE If we were pedantic, we would consider the fact that we might not
|
||||
// need unused capacity if the memory doesn't get relocated.
|
||||
self.allocations.ensureUnusedCapacity(self.allocator, 1) catch return null;
|
||||
|
||||
const old_memory = if (maybe_ptr) |ptr| blk_then: {
|
||||
const old_size = self.allocations.get(ptr).?;
|
||||
break :blk_then @as([*]align(alignment.toByteUnits()) u8, @ptrCast(ptr))[0..old_size];
|
||||
} else blk_else: {
|
||||
break :blk_else @as([]align(alignment.toByteUnits()) u8, &.{});
|
||||
};
|
||||
|
||||
const memory = self.allocator.realloc(old_memory, size) catch return null;
|
||||
|
||||
if (maybe_ptr) |ptr| {
|
||||
const old_size = self.allocations.fetchRemove(ptr).?.value;
|
||||
self.allocated_bytes -= old_size;
|
||||
}
|
||||
|
||||
self.allocations.putAssumeCapacityNoClobber(memory.ptr, size);
|
||||
self.allocated_bytes += size;
|
||||
//log.debug("Reallocated into {d} bytes(s) at 0x{x} from 0x{x}", .{ size, @intFromPtr(memory.ptr), @intFromPtr(maybe_ptr) });
|
||||
|
||||
return memory.ptr;
|
||||
}
|
||||
|
||||
export fn castle_media_stbi_free(maybe_ptr: VoidPtr) callconv(.c) void {
|
||||
const self = current_self;
|
||||
|
||||
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());
|
||||
}
|
||||
17
packages/media/src/stbi/stb_image.c
Normal file
17
packages/media/src/stbi/stb_image.c
Normal file
@@ -0,0 +1,17 @@
|
||||
#include <stddef.h>
|
||||
|
||||
void *castle_media_stbi_malloc(size_t size);
|
||||
void *castle_media_stbi_realloc(void *ptr, size_t size);
|
||||
void castle_media_stbi_free(void *ptr);
|
||||
|
||||
#define STBI_MALLOC(size) castle_media_stbi_malloc(size)
|
||||
#define STBI_REALLOC(ptr, size) castle_media_stbi_realloc(ptr, size)
|
||||
#define STBI_FREE(ptr) castle_media_stbi_free(ptr)
|
||||
|
||||
#define STBI_NO_STDIO
|
||||
#define STBI_ONLY_JPEG
|
||||
#define STBI_ONLY_PNG
|
||||
#define STBI_ONLY_HDR
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
7988
packages/media/src/stbi/stb_image.h
Normal file
7988
packages/media/src/stbi/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -11,11 +11,19 @@ pub fn build(b: *std.Build) void {
|
||||
|
||||
const sqlite_mod = sqlite_dep.module("sqlite");
|
||||
|
||||
const web_dep = b.dependency("web", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const web_mod = web_dep.module("web");
|
||||
|
||||
const myid_mod = b.addModule("myid", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.imports = &.{
|
||||
.{ .name = "sqlite", .module = sqlite_mod },
|
||||
.{ .name = "web", .module = web_mod },
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -13,5 +13,8 @@
|
||||
.url = "git+https://github.com/vrischmann/zig-sqlite#6d90ee900d186a7fbb6066f28ee13beeaf8be345",
|
||||
.hash = "sqlite-3.48.0-F2R_a5yODgDFvwwsytm7ZONcSqYBo3qv1PmXOtw3tqLA",
|
||||
},
|
||||
.web = .{
|
||||
.path = "../web",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
259
packages/myid/src/data.zig
Normal file
259
packages/myid/src/data.zig
Normal file
@@ -0,0 +1,259 @@
|
||||
const std = @import("std");
|
||||
const sqlite = @import("sqlite");
|
||||
const web = @import("web");
|
||||
|
||||
const id = @import("id.zig");
|
||||
|
||||
pub const callback_regex = "^https?://([a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,}/";
|
||||
|
||||
pub const App = struct {
|
||||
aid: id.App,
|
||||
name: []const u8,
|
||||
callbacks: []const []const u8,
|
||||
};
|
||||
|
||||
pub const AppWithSecret = struct {
|
||||
aid: id.App,
|
||||
name: []const u8,
|
||||
callbacks: []const []const u8,
|
||||
secret: []const u8,
|
||||
};
|
||||
|
||||
pub const CreateAppData = struct {
|
||||
name: []const u8,
|
||||
callback: ?[]const []const u8,
|
||||
};
|
||||
|
||||
pub const CreateAppResult = struct {
|
||||
aid: id.App,
|
||||
plainSecret: []const u8,
|
||||
};
|
||||
|
||||
pub const User = struct {
|
||||
uid: id.User,
|
||||
name: []const u8,
|
||||
email: []const u8,
|
||||
admin: bool,
|
||||
};
|
||||
|
||||
pub const UserWithPassword = struct {
|
||||
uid: id.User,
|
||||
name: []const u8,
|
||||
email: []const u8,
|
||||
admin: bool,
|
||||
passworD: []const u8,
|
||||
};
|
||||
|
||||
pub const InviteUserData = struct {
|
||||
name: []const u8,
|
||||
email: []const u8,
|
||||
admin: bool,
|
||||
};
|
||||
|
||||
pub const CreateUserData = struct {
|
||||
name: []const u8,
|
||||
email: []const u8,
|
||||
plainPassword: []const u8,
|
||||
admin: bool,
|
||||
};
|
||||
|
||||
pub const All = struct {
|
||||
users: []const User,
|
||||
apps: []const App,
|
||||
};
|
||||
|
||||
pub const Database = struct {
|
||||
db: sqlite.Db,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn init(path: [:0]const u8, allocator: std.mem.Allocator) !Database {
|
||||
var db = try sqlite.Db.init(.{
|
||||
.mode = .{ .File = path },
|
||||
.open_flags = .{
|
||||
.create = true,
|
||||
.write = true,
|
||||
},
|
||||
.threading_mode = .MultiThread,
|
||||
});
|
||||
errdefer db.deinit();
|
||||
|
||||
_ = try db.one(void, "PRAGMA journal_mode = WAL", .{}, .{});
|
||||
_ = try db.one(void, "PRAGMA foreign_keys = ON", .{}, .{});
|
||||
|
||||
return .{
|
||||
.db = db,
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Database) void {
|
||||
self.db.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
// --- MIGRATION -----------------------------------------------------------
|
||||
|
||||
fn getUserVersion(self: *Database) !i32 {
|
||||
const version = try self.db.one(i32, "PRAGMA user_version", .{}, .{});
|
||||
return version.?;
|
||||
}
|
||||
|
||||
fn setUserVersion(self: *Database, version: i32) !void {
|
||||
var buf: [100]u8 = undefined;
|
||||
const query = std.fmt.bufPrint(&buf, "PRAGMA user_version = {d}", .{version}) catch unreachable;
|
||||
_ = try self.db.oneDynamic(void, query, .{}, .{});
|
||||
}
|
||||
|
||||
pub fn migrate(self: *Database) !void {
|
||||
var user_version = try self.getUserVersion();
|
||||
|
||||
if (user_version == 0) {
|
||||
_ = try self.db.exec(
|
||||
\\CREATE TABLE users (
|
||||
\\ uid BLOB NOT NULL,
|
||||
\\ name TEXT NOT NULL,
|
||||
\\ email TEXT NOT NULL UNIQUE,
|
||||
\\ password TEXT NOT NULL,
|
||||
\\ PRIMARY KEY (uid)
|
||||
\\)
|
||||
, .{}, .{});
|
||||
_ = try self.db.exec(
|
||||
\\CREATE TABLE apps (
|
||||
\\ aid BLOB NOT NULL,
|
||||
\\ name TEXT NOT NULL,
|
||||
\\ secret TEXT NOT NULL,
|
||||
\\ PRIMARY KEY (aid)
|
||||
\\)
|
||||
, .{}, .{});
|
||||
_ = try self.db.exec(
|
||||
\\CREATE TABLE app_callbacks (
|
||||
\\ aid BLOB NOT NULL,
|
||||
\\ callback TEXT NOT NULL,
|
||||
\\ PRIMARY KEY (aid, callback),
|
||||
\\ FOREIGN KEY (aid) REFERENCES apps (aid)
|
||||
\\ ON UPDATE CASCADE
|
||||
\\ ON DELETE CASCADE
|
||||
\\)
|
||||
, .{}, .{});
|
||||
user_version += 1;
|
||||
try self.setUserVersion(user_version);
|
||||
}
|
||||
}
|
||||
|
||||
// --- USERS ---------------------------------------------------------------
|
||||
|
||||
pub fn createUser(self: *Database, name: []const u8, email: []const u8, plain_password: []const u8) !id.User {
|
||||
const uid = id.User.init(web.UUID.v7());
|
||||
var password_buf: [1000]u8 = undefined;
|
||||
const password = try std.crypto.pwhash.argon2.strHash(plain_password, .{
|
||||
.allocator = self.allocator,
|
||||
.mode = .argon2id,
|
||||
.params = .owasp_2id,
|
||||
}, &password_buf);
|
||||
|
||||
try self.db.exec("INSERT INTO users (uid, name, email, password) VALUES (?, ?, ?, ?)", .{}, .{
|
||||
.uid = uid.bytes,
|
||||
.name = name,
|
||||
.email = email,
|
||||
.password = password,
|
||||
});
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
// --- APPS ----------------------------------------------------------------
|
||||
|
||||
pub fn createApp(self: *Database, name: []const u8, callbacks: []const []const u8) !CreateAppResult {
|
||||
_ = try self.db.exec("BEGIN", .{}, .{});
|
||||
errdefer self.db.exec("ROLLBACK", .{}, .{}) catch unreachable;
|
||||
|
||||
const secret_bytes = blk: {
|
||||
var bytes: [24]u8 = undefined;
|
||||
std.crypto.random.bytes(&bytes);
|
||||
break :blk bytes;
|
||||
};
|
||||
|
||||
var plain_secret_buf: [32]u8 = undefined;
|
||||
const plain_secret = std.base64.url_safe_no_pad.Encoder.encode(&plain_secret_buf, &secret_bytes);
|
||||
std.debug.assert(plain_secret_buf.len == plain_secret.len);
|
||||
|
||||
const aid = id.App.init(web.UUID.v7());
|
||||
var secret_buf: [1000]u8 = undefined;
|
||||
const secret = try std.crypto.pwhash.argon2.strHash(plain_secret, .{
|
||||
.allocator = self.allocator,
|
||||
.mode = .argon2id,
|
||||
.params = .owasp_2id,
|
||||
}, &secret_buf);
|
||||
|
||||
try self.db.exec("INSERT INTO apps (aid, name, secret) VALUES (?, ?, ?)", .{}, .{
|
||||
.aid = aid.bytes,
|
||||
.name = name,
|
||||
.secret = secret,
|
||||
});
|
||||
|
||||
var insert_callback = try self.db.prepare("INSERT INTO app_callbacks (aid, callback) VALUES (?, ?)");
|
||||
defer insert_callback.deinit();
|
||||
for (callbacks) |callback| {
|
||||
try insert_callback.exec(.{}, .{ .aid = aid.bytes, .callback = callback });
|
||||
insert_callback.reset();
|
||||
}
|
||||
|
||||
try self.db.exec("COMMIT", .{}, .{});
|
||||
return .{
|
||||
.aid = aid,
|
||||
.plain_secret = plain_secret_buf,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "user version" {
|
||||
var db = try Database.init(":memory:", std.testing.allocator);
|
||||
defer db.deinit();
|
||||
|
||||
try std.testing.expectEqual(0, try db.getUserVersion());
|
||||
try db.setUserVersion(1);
|
||||
try std.testing.expectEqual(1, try db.getUserVersion());
|
||||
}
|
||||
|
||||
test "migrate" {
|
||||
var db = try Database.init(":memory:", std.testing.allocator);
|
||||
defer db.deinit();
|
||||
|
||||
try db.migrate();
|
||||
}
|
||||
|
||||
test "create user" {
|
||||
var db = try Database.init(":memory:", std.testing.allocator);
|
||||
defer db.deinit();
|
||||
|
||||
try db.migrate();
|
||||
|
||||
const uid = try db.createUser("admin", "admin@test.invalid", "admin");
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
const maybe_user = try db.db.oneAlloc(struct {
|
||||
name: []const u8,
|
||||
email: []const u8,
|
||||
password: []const u8,
|
||||
}, arena.allocator(), "SELECT name, email, password FROM users WHERE uid = ?", .{}, .{
|
||||
.uid = uid.bytes,
|
||||
});
|
||||
defer arena.deinit();
|
||||
|
||||
try std.testing.expect(maybe_user != null);
|
||||
if (maybe_user) |user| {
|
||||
try std.testing.expectEqualSlices(u8, "admin", user.name);
|
||||
}
|
||||
}
|
||||
|
||||
test "create app" {
|
||||
var db = try Database.init(":memory:", std.testing.allocator);
|
||||
defer db.deinit();
|
||||
|
||||
try db.migrate();
|
||||
|
||||
_ = try db.createApp("app", &.{
|
||||
"http://localhost:3000/callback",
|
||||
"https://example.com/callback",
|
||||
});
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
pub const database_path = "db.sqlite3";
|
||||
pub const socket_path = "myid.sock";
|
||||
@@ -1,206 +0,0 @@
|
||||
const std = @import("std");
|
||||
const main = @import("main.zig");
|
||||
|
||||
const Parser = @import("http/Parser.zig");
|
||||
|
||||
threadlocal var read_buffer: [2 * 1024 * 1024]u8 = undefined;
|
||||
threadlocal var write_buffer: [2 * 1024 * 1024]u8 = undefined;
|
||||
|
||||
const log = std.log.scoped(.http);
|
||||
|
||||
const status = struct {
|
||||
pub const ok = "HTTP/1.1 200 OK\r\n";
|
||||
pub const created = "HTTP/1.1 201 Created\r\n";
|
||||
pub const accepted = "HTTP/1.1 202 Accepted\r\n";
|
||||
pub const non_authoritative_information = "HTTP/1.1 203 Non-Authoritative Information\r\n";
|
||||
pub const no_content = "HTTP/1.1 204 No Content\r\n";
|
||||
pub const reset_content = "HTTP/1.1 205 Reset Content\r\n";
|
||||
pub const partial_content = "HTTP/1.1 206 Partial Content\r\n";
|
||||
pub const multi_status = "HTTP/1.1 207 Multi-Status\r\n";
|
||||
pub const already_reported = "HTTP/1.1 208 Already Reported\r\n";
|
||||
|
||||
pub const multiple_choices = "HTTP/1.1 300 Multiple Choices\r\n";
|
||||
pub const moved_permanently = "HTTP/1.1 301 Moved Permanently\r\n";
|
||||
pub const found = "HTTP/1.1 302 Found\r\n";
|
||||
pub const see_other = "HTTP/1.1 303 See Other\r\n";
|
||||
pub const not_modified = "HTTP/1.1 304 Not Modified\r\n";
|
||||
pub const temporary_redirect = "HTTP/1.1 307 Temporary Redirect\r\n";
|
||||
pub const permanent_redirect = "HTTP/1.1 308 Permanent Redirect\r\n";
|
||||
|
||||
pub const bad_request = "HTTP/1.1 400 Bad Request\r\n";
|
||||
pub const unauthorized = "HTTP/1.1 401 Unauthorized\r\n";
|
||||
pub const payment_required = "HTTP/1.1 402 Payment Required\r\n";
|
||||
pub const forbidden = "HTTP/1.1 403 Forbidden\r\n";
|
||||
pub const not_found = "HTTP/1.1 404 Not Found\r\n";
|
||||
pub const method_not_allowed = "HTTP/1.1 405 Method Not Allowed\r\n";
|
||||
pub const not_acceptable = "HTTP/1.1 406 Not Acceptable\r\n";
|
||||
pub const proxy_authentication_required = "HTTP/1.1 407 Proxy Authentication Required\r\n";
|
||||
pub const request_timeout = "HTTP/1.1 408 Request Timeout\r\n";
|
||||
pub const conflict = "HTTP/1.1 409 Conflict\r\n";
|
||||
pub const gone = "HTTP/1.1 410 Gone\r\n";
|
||||
pub const length_required = "HTTP/1.1 411 Length Required\r\n";
|
||||
pub const precondition_failed = "HTTP/1.1 412 Precondition Failed\r\n";
|
||||
pub const content_too_large = "HTTP/1.1 413 Content Too Large\r\n";
|
||||
pub const uri_too_long = "HTTP/1.1 414 URI Too Long\r\n";
|
||||
pub const unsupported_media_type = "HTTP/1.1 415 Unsupported Media Type\r\n";
|
||||
pub const range_not_satisfiable = "HTTP/1.1 416 Range Not Satisfiable\r\n";
|
||||
pub const expectation_failed = "HTTP/1.1 417 Expectation Failed\r\n";
|
||||
pub const im_a_teapot = "HTTP/1.1 418 I'm a teapot\r\n";
|
||||
pub const misdirected_request = "HTTP/1.1 421 Misdirected Request\r\n";
|
||||
pub const unprocessable_content = "HTTP/1.1 422 Unprocessable Content\r\n";
|
||||
pub const locked = "HTTP/1.1 423 Locked\r\n";
|
||||
pub const failed_dependency = "HTTP/1.1 424 Failed Dependency\r\n";
|
||||
pub const upgrade_required = "HTTP/1.1 426 Upgrade Required\r\n";
|
||||
pub const precondition_required = "HTTP/1.1 428 Precondition Required\r\n";
|
||||
pub const too_many_requests = "HTTP/1.1 429 Too Many Requests\r\n";
|
||||
pub const request_header_fields_too_large = "HTTP/1.1 431 Request Header Fields Too Large\r\n";
|
||||
pub const unavailable_for_legal_reasons = "HTTP/1.1 451 Unavailable For Legal Reasons\r\n";
|
||||
|
||||
pub const internal_server_error = "HTTP/1.1 500 Internal Server Error\r\n";
|
||||
pub const not_implemented = "HTTP/1.1 501 Not Implemented\r\n";
|
||||
pub const bad_gateway = "HTTP/1.1 502 Bad Gateway\r\n";
|
||||
pub const service_unavailable = "HTTP/1.1 503 Service Unavailable\r\n";
|
||||
pub const gateway_timeout = "HTTP/1.1 504 Gateway Timeout\r\n";
|
||||
pub const http_version_not_supported = "HTTP/1.1 505 HTTP Version Not Supported\r\n";
|
||||
pub const variant_also_negotiates = "HTTP/1.1 506 Variant Also Negotiates\r\n";
|
||||
pub const insufficient_storage = "HTTP/1.1 507 Insufficient Storage\r\n";
|
||||
pub const loop_detected = "HTTP/1.1 508 Loop Detected\r\n";
|
||||
pub const not_extended = "HTTP/1.1 510 Not Extended\r\n";
|
||||
pub const network_authentication_required = "HTTP/1.1 511 Network Authentication Required\r\n";
|
||||
};
|
||||
|
||||
const ResponseEmptyOptions = struct {
|
||||
status_text: []const u8 = status.ok,
|
||||
};
|
||||
|
||||
const ResponseOptions = struct {
|
||||
status_text: []const u8 = status.ok,
|
||||
media_type: []const u8 = "text/plain; charset=utf-8",
|
||||
response_body: []const u8,
|
||||
};
|
||||
|
||||
fn makeResponseEmpty(options: ResponseEmptyOptions) ![]const u8 {
|
||||
var fbs = std.io.fixedBufferStream(&write_buffer);
|
||||
const writer = fbs.writer();
|
||||
|
||||
try writer.print("{s}", .{options.status_text});
|
||||
try writer.print("\r\n", .{});
|
||||
|
||||
return fbs.getWritten();
|
||||
}
|
||||
|
||||
fn makeResponseClose(options: ResponseEmptyOptions) ![]const u8 {
|
||||
var fbs = std.io.fixedBufferStream(&write_buffer);
|
||||
const writer = fbs.writer();
|
||||
|
||||
try writer.print("{s}", .{options.status_text});
|
||||
try writer.print("Connection: close\r\n", .{});
|
||||
try writer.print("\r\n", .{});
|
||||
|
||||
return fbs.getWritten();
|
||||
}
|
||||
|
||||
fn makeResponse(options: ResponseOptions) ![]const u8 {
|
||||
var fbs = std.io.fixedBufferStream(&write_buffer);
|
||||
const writer = fbs.writer();
|
||||
|
||||
try writer.print("{s}", .{options.status_text});
|
||||
try writer.print("Content-Type: {s}\r\n", .{options.media_type});
|
||||
try writer.print("Content-Length: {d}\r\n", .{options.response_body.len});
|
||||
try writer.print("\r\n", .{});
|
||||
try writer.print("{s}", .{options.response_body});
|
||||
|
||||
return fbs.getWritten();
|
||||
}
|
||||
|
||||
pub fn process(conn: std.net.Server.Connection) !void {
|
||||
defer conn.stream.close();
|
||||
|
||||
var leftover_bytes: usize = 0;
|
||||
|
||||
while (true) {
|
||||
const start = try std.time.Instant.now();
|
||||
|
||||
var route: Parser.Route = undefined;
|
||||
|
||||
var parser = Parser.init(.{
|
||||
.self = &route,
|
||||
.route = routeCallback,
|
||||
});
|
||||
var total_bytes_read: usize = 0;
|
||||
|
||||
while (true) {
|
||||
var bytes_read: usize = undefined;
|
||||
var chars: []const u8 = undefined;
|
||||
|
||||
if (leftover_bytes > 0) {
|
||||
bytes_read = leftover_bytes;
|
||||
chars = read_buffer[0..leftover_bytes];
|
||||
leftover_bytes = 0;
|
||||
} else {
|
||||
bytes_read = try conn.stream.read(read_buffer[total_bytes_read..]);
|
||||
chars = read_buffer[total_bytes_read .. total_bytes_read + bytes_read];
|
||||
}
|
||||
|
||||
total_bytes_read += bytes_read;
|
||||
|
||||
const res = parser.consume(chars) catch |err| switch (err) {
|
||||
error.MethodNotSupported => {
|
||||
const response = try makeResponseClose(.{ .status_text = status.method_not_allowed });
|
||||
try conn.stream.writeAll(response);
|
||||
return;
|
||||
},
|
||||
error.HttpVersionNotSupported => {
|
||||
const response = try makeResponseClose(.{ .status_text = status.http_version_not_supported });
|
||||
try conn.stream.writeAll(response);
|
||||
return;
|
||||
},
|
||||
error.MissingLineFeed => {
|
||||
const response = try makeResponseClose(.{ .status_text = status.bad_request });
|
||||
try conn.stream.writeAll(response);
|
||||
return;
|
||||
},
|
||||
error.InvalidContentLength => {
|
||||
const response = try makeResponseClose(.{ .status_text = status.bad_request });
|
||||
try conn.stream.writeAll(response);
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
if (total_bytes_read >= read_buffer.len and !res.done) {
|
||||
if (parser.state == .body) {
|
||||
const response = try makeResponseClose(.{ .status_text = status.content_too_large });
|
||||
try conn.stream.writeAll(response);
|
||||
return;
|
||||
} else {
|
||||
const response = try makeResponseClose(.{ .status_text = status.request_header_fields_too_large });
|
||||
try conn.stream.writeAll(response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (res.done) {
|
||||
leftover_bytes = bytes_read - res.consumed;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const response = try makeResponse(.{ .response_body = "PONG\n" });
|
||||
|
||||
try conn.stream.writeAll(response);
|
||||
|
||||
if (leftover_bytes > 0) {
|
||||
@memmove(&read_buffer, read_buffer[total_bytes_read - leftover_bytes .. total_bytes_read]);
|
||||
}
|
||||
|
||||
const end = try std.time.Instant.now();
|
||||
const time_ns = end.since(start);
|
||||
const time_us = @divFloor(time_ns, std.time.ns_per_us);
|
||||
|
||||
log.info("{s} {s} ({} μs)", .{ @tagName(route.method), route.pathname, time_us });
|
||||
}
|
||||
}
|
||||
|
||||
fn routeCallback(self: ?*anyopaque, route: Parser.Route) void {
|
||||
@as(*Parser.Route, @alignCast(@ptrCast(self))).* = route;
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
const Parser = @This();
|
||||
|
||||
const Callbacks = struct {
|
||||
self: ?*anyopaque = null,
|
||||
|
||||
route: ?*const fn (self: ?*anyopaque, route: Route) void = null,
|
||||
header: ?*const fn (self: ?*anyopaque, name: []const u8, value: []const u8) void = null,
|
||||
body: ?*const fn (self: ?*anyopaque, body: []const u8) void = null,
|
||||
|
||||
pub const init: Callbacks = .{};
|
||||
};
|
||||
|
||||
const Error = error{
|
||||
MethodNotSupported,
|
||||
HttpVersionNotSupported,
|
||||
MissingLineFeed,
|
||||
InvalidContentLength,
|
||||
};
|
||||
|
||||
const State = union(enum) {
|
||||
pub fn methodComplete(method: Method) State {
|
||||
return .{
|
||||
.method_complete = .{
|
||||
.method = method,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn pathname(method: Method, p: []const u8) State {
|
||||
return .{
|
||||
.pathname_state = .{
|
||||
.method = method,
|
||||
.pathname = p,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn headerValue(name: []const u8, value: []const u8) State {
|
||||
return .{
|
||||
.header_value = .{
|
||||
.name = name,
|
||||
.value = value,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
init: void,
|
||||
method_d: void,
|
||||
method_g: void,
|
||||
method_h: void,
|
||||
method_p: void,
|
||||
method_de: void,
|
||||
method_ge: void,
|
||||
method_he: void,
|
||||
method_pa: void,
|
||||
method_po: void,
|
||||
method_pu: void,
|
||||
method_del: void,
|
||||
method_hea: void,
|
||||
method_pat: void,
|
||||
method_pos: void,
|
||||
method_dele: void,
|
||||
method_patc: void,
|
||||
method_delet: void,
|
||||
method_complete: struct { method: Method },
|
||||
pathname_state: struct { method: Method, pathname: []const u8 },
|
||||
pathname_complete: void,
|
||||
version_h: void,
|
||||
version_ht: void,
|
||||
version_htt: void,
|
||||
version_http: void,
|
||||
@"version_http/@": void,
|
||||
@"version_http/1@": void,
|
||||
@"version_http/1.@": void,
|
||||
version_complete: void,
|
||||
start_line_end: void,
|
||||
header_name_start: void,
|
||||
header_name: []const u8,
|
||||
header_value: struct { name: []const u8, value: []const u8 },
|
||||
header_line_end: void,
|
||||
headers_end: void,
|
||||
body: []const u8,
|
||||
};
|
||||
|
||||
const ConsumeResult = struct {
|
||||
consumed: usize,
|
||||
done: bool,
|
||||
};
|
||||
|
||||
const ConsumeCharResult = enum {
|
||||
not_done,
|
||||
done,
|
||||
};
|
||||
|
||||
pub const Method = enum {
|
||||
DELETE,
|
||||
GET,
|
||||
HEAD,
|
||||
PATCH,
|
||||
POST,
|
||||
PUT,
|
||||
};
|
||||
|
||||
pub const Route = struct {
|
||||
method: Method,
|
||||
pathname: []const u8,
|
||||
};
|
||||
|
||||
callbacks: Callbacks,
|
||||
state: State,
|
||||
current_header_is_content_length: bool,
|
||||
content_length: usize,
|
||||
|
||||
pub fn init(callbacks: Callbacks) Parser {
|
||||
return .{
|
||||
.callbacks = callbacks,
|
||||
.state = .init,
|
||||
.current_header_is_content_length = false,
|
||||
.content_length = 0,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn consume(self: *Parser, chars: []const u8) Error!ConsumeResult {
|
||||
var i: usize = 0;
|
||||
while (i < chars.len) {
|
||||
switch (self.state) {
|
||||
.body => |body| {
|
||||
const to_consume = @min(chars.len - i, self.content_length - body.len);
|
||||
const new_body = body.ptr[0 .. body.len + to_consume];
|
||||
self.state = .{ .body = new_body };
|
||||
i += to_consume;
|
||||
|
||||
const done = new_body.len >= self.content_length;
|
||||
|
||||
if (done) {
|
||||
if (self.callbacks.body) |bodyCallback| {
|
||||
bodyCallback(self.callbacks.self, new_body);
|
||||
}
|
||||
}
|
||||
|
||||
return .{
|
||||
.consumed = i,
|
||||
.done = done,
|
||||
};
|
||||
},
|
||||
else => {
|
||||
const res = try self.consumeChar(&chars[i]);
|
||||
i += 1;
|
||||
if (res == .done) return .{
|
||||
.consumed = i,
|
||||
.done = true,
|
||||
};
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return .{
|
||||
.consumed = chars.len,
|
||||
.done = false,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn consumeChar(self: *Parser, c_ptr: *const u8) Error!ConsumeCharResult {
|
||||
const c = c_ptr.*;
|
||||
const c_slice = @as([*]const u8, @ptrCast(c_ptr))[0..1];
|
||||
switch (self.state) {
|
||||
.init => switch (c) {
|
||||
'D' => self.state = .method_d,
|
||||
'G' => self.state = .method_g,
|
||||
'H' => self.state = .method_h,
|
||||
'P' => self.state = .method_p,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_d => switch (c) {
|
||||
'E' => self.state = .method_de,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_g => switch (c) {
|
||||
'E' => self.state = .method_ge,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_h => switch (c) {
|
||||
'E' => self.state = .method_he,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_p => switch (c) {
|
||||
'A' => self.state = .method_pa,
|
||||
'O' => self.state = .method_po,
|
||||
'U' => self.state = .method_pu,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_de => switch (c) {
|
||||
'L' => self.state = .method_del,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_ge => switch (c) {
|
||||
'T' => self.state = .methodComplete(.GET),
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_he => switch (c) {
|
||||
'A' => self.state = .method_hea,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_pa => switch (c) {
|
||||
'T' => self.state = .method_pat,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_po => switch (c) {
|
||||
'S' => self.state = .method_pos,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_pu => switch (c) {
|
||||
'T' => self.state = .methodComplete(.PUT),
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_del => switch (c) {
|
||||
'E' => self.state = .method_dele,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_hea => switch (c) {
|
||||
'D' => self.state = .methodComplete(.HEAD),
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_pat => switch (c) {
|
||||
'C' => self.state = .method_patc,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_pos => switch (c) {
|
||||
'T' => self.state = .methodComplete(.POST),
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_dele => switch (c) {
|
||||
'T' => self.state = .method_delet,
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_patc => switch (c) {
|
||||
'H' => self.state = .methodComplete(.PATCH),
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_delet => switch (c) {
|
||||
'E' => self.state = .methodComplete(.DELETE),
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.method_complete => |s| switch (c) {
|
||||
' ' => self.state = .pathname(s.method, @as([*]const u8, @ptrCast(c_ptr))[1..1]),
|
||||
else => return error.MethodNotSupported,
|
||||
},
|
||||
.pathname_state => |s| switch (c) {
|
||||
' ' => {
|
||||
self.state = .pathname_complete;
|
||||
if (self.callbacks.route) |routeCallback| {
|
||||
routeCallback(self.callbacks.self, .{
|
||||
.method = s.method,
|
||||
.pathname = s.pathname,
|
||||
});
|
||||
}
|
||||
},
|
||||
else => self.state = .pathname(s.method, s.pathname.ptr[0 .. s.pathname.len + 1]),
|
||||
},
|
||||
.pathname_complete => switch (c) {
|
||||
'H' => self.state = .version_h,
|
||||
else => return error.HttpVersionNotSupported,
|
||||
},
|
||||
.version_h => switch (c) {
|
||||
'T' => self.state = .version_ht,
|
||||
else => return error.HttpVersionNotSupported,
|
||||
},
|
||||
.version_ht => switch (c) {
|
||||
'T' => self.state = .version_htt,
|
||||
else => return error.HttpVersionNotSupported,
|
||||
},
|
||||
.version_htt => switch (c) {
|
||||
'P' => self.state = .version_http,
|
||||
else => return error.HttpVersionNotSupported,
|
||||
},
|
||||
.version_http => switch (c) {
|
||||
'/' => self.state = .@"version_http/@",
|
||||
else => return error.HttpVersionNotSupported,
|
||||
},
|
||||
.@"version_http/@" => switch (c) {
|
||||
'1' => self.state = .@"version_http/1@",
|
||||
else => return error.HttpVersionNotSupported,
|
||||
},
|
||||
.@"version_http/1@" => switch (c) {
|
||||
'.' => self.state = .@"version_http/1.@",
|
||||
else => return error.HttpVersionNotSupported,
|
||||
},
|
||||
.@"version_http/1.@" => switch (c) {
|
||||
'1' => self.state = .version_complete,
|
||||
else => return error.HttpVersionNotSupported,
|
||||
},
|
||||
.version_complete => switch (c) {
|
||||
'\r' => self.state = .start_line_end,
|
||||
else => return error.HttpVersionNotSupported,
|
||||
},
|
||||
.start_line_end => switch (c) {
|
||||
'\n' => self.state = .header_name_start,
|
||||
else => return error.MissingLineFeed,
|
||||
},
|
||||
.header_name_start => switch (c) {
|
||||
'\r' => self.state = .headers_end,
|
||||
else => self.state = .{ .header_name = c_slice },
|
||||
},
|
||||
.header_name => |name| switch (c) {
|
||||
':' => {
|
||||
self.state = .headerValue(name, @as([*]const u8, @ptrCast(c_ptr))[1..1]);
|
||||
self.current_header_is_content_length = std.ascii.eqlIgnoreCase(name, "Content-Length");
|
||||
},
|
||||
else => self.state = .{ .header_name = name.ptr[0 .. name.len + 1] },
|
||||
},
|
||||
.header_value => |s| switch (c) {
|
||||
'\r' => {
|
||||
self.state = .header_line_end;
|
||||
const value_trimmed = std.mem.trim(u8, s.value, " \t");
|
||||
if (self.current_header_is_content_length) {
|
||||
self.content_length = std.fmt.parseInt(usize, value_trimmed, 10) catch return error.InvalidContentLength;
|
||||
self.current_header_is_content_length = false;
|
||||
}
|
||||
if (self.callbacks.header) |headerCallback| {
|
||||
headerCallback(self.callbacks.self, s.name, value_trimmed);
|
||||
}
|
||||
},
|
||||
else => self.state = .headerValue(s.name, s.value.ptr[0 .. s.value.len + 1]),
|
||||
},
|
||||
.header_line_end => switch (c) {
|
||||
'\n' => self.state = .header_name_start,
|
||||
else => return error.MissingLineFeed,
|
||||
},
|
||||
.headers_end => switch (c) {
|
||||
'\n' => {
|
||||
if (self.content_length == 0) {
|
||||
if (self.callbacks.body) |bodyCallback| {
|
||||
bodyCallback(self.callbacks.self, &.{});
|
||||
}
|
||||
return .done;
|
||||
}
|
||||
self.state = .{ .body = @as([*]const u8, @ptrCast(c_ptr))[1..1] };
|
||||
},
|
||||
else => return error.MissingLineFeed,
|
||||
},
|
||||
.body => |body| {
|
||||
const new_body = body.ptr[0 .. body.len + 1];
|
||||
self.state = .{ .body = new_body };
|
||||
if (new_body.len >= self.content_length) {
|
||||
if (self.callbacks.body) |bodyCallback| {
|
||||
bodyCallback(self.callbacks.self, new_body);
|
||||
}
|
||||
return .done;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
return .not_done;
|
||||
}
|
||||
6
packages/myid/src/id.zig
Normal file
6
packages/myid/src/id.zig
Normal file
@@ -0,0 +1,6 @@
|
||||
const std = @import("std");
|
||||
const web = @import("web");
|
||||
|
||||
pub const App = web.Id(.app);
|
||||
pub const Session = web.Id(.session);
|
||||
pub const User = web.Id(.user);
|
||||
@@ -1,235 +1,3 @@
|
||||
const std = @import("std");
|
||||
const sqlite = @import("sqlite");
|
||||
const uuid = @import("uuid.zig");
|
||||
|
||||
fn Id(comptime _tag: @Type(.enum_literal)) type {
|
||||
return struct {
|
||||
pub const tag = _tag;
|
||||
|
||||
bytes: [16]u8,
|
||||
|
||||
pub fn new() @This() {
|
||||
return .{ .bytes = uuid.uuid_v7() };
|
||||
}
|
||||
|
||||
pub fn eql(a: @This(), b: @This()) bool {
|
||||
return std.mem.eql(u8, &a.bytes, &b.bytes);
|
||||
}
|
||||
|
||||
pub fn decode(encoded: *const [22]u8) !@This() {
|
||||
var bytes: [16]u8 = undefined;
|
||||
try std.base64.url_safe_no_pad.Decoder.decode(&bytes, encoded);
|
||||
return .{ .bytes = bytes };
|
||||
}
|
||||
|
||||
pub fn encode(self: @This()) [22]u8 {
|
||||
var text: [22]u8 = undefined;
|
||||
std.base64.url_safe_no_pad.Encoder.encode(&text, self.bytes);
|
||||
return text;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const AppId = Id(.app_id);
|
||||
pub const UserId = Id(.user_id);
|
||||
|
||||
pub const CreateAppResult = struct {
|
||||
aid: AppId,
|
||||
plain_secret: [32]u8,
|
||||
};
|
||||
|
||||
pub const Database = struct {
|
||||
db: sqlite.Db,
|
||||
allocator: std.mem.Allocator,
|
||||
|
||||
pub fn init(path: [:0]const u8, allocator: std.mem.Allocator) !Database {
|
||||
var db = try sqlite.Db.init(.{
|
||||
.mode = .{ .File = path },
|
||||
.open_flags = .{
|
||||
.create = true,
|
||||
.write = true,
|
||||
},
|
||||
.threading_mode = .MultiThread,
|
||||
});
|
||||
errdefer db.deinit();
|
||||
|
||||
_ = try db.one(void, "PRAGMA journal_mode = WAL", .{}, .{});
|
||||
_ = try db.one(void, "PRAGMA foreign_keys = ON", .{}, .{});
|
||||
|
||||
return .{
|
||||
.db = db,
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Database) void {
|
||||
self.db.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
// --- MIGRATION -----------------------------------------------------------
|
||||
|
||||
fn getUserVersion(self: *Database) !i32 {
|
||||
const version = try self.db.one(i32, "PRAGMA user_version", .{}, .{});
|
||||
return version.?;
|
||||
}
|
||||
|
||||
fn setUserVersion(self: *Database, version: i32) !void {
|
||||
var buf: [100]u8 = undefined;
|
||||
const query = std.fmt.bufPrint(&buf, "PRAGMA user_version = {d}", .{version}) catch unreachable;
|
||||
_ = try self.db.oneDynamic(void, query, .{}, .{});
|
||||
}
|
||||
|
||||
pub fn migrate(self: *Database) !void {
|
||||
var user_version = try self.getUserVersion();
|
||||
|
||||
if (user_version == 0) {
|
||||
_ = try self.db.exec(
|
||||
\\CREATE TABLE users (
|
||||
\\ uid BLOB NOT NULL,
|
||||
\\ name TEXT NOT NULL,
|
||||
\\ email TEXT NOT NULL UNIQUE,
|
||||
\\ password TEXT NOT NULL,
|
||||
\\ PRIMARY KEY (uid)
|
||||
\\)
|
||||
, .{}, .{});
|
||||
_ = try self.db.exec(
|
||||
\\CREATE TABLE apps (
|
||||
\\ aid BLOB NOT NULL,
|
||||
\\ name TEXT NOT NULL,
|
||||
\\ secret TEXT NOT NULL,
|
||||
\\ PRIMARY KEY (aid)
|
||||
\\)
|
||||
, .{}, .{});
|
||||
_ = try self.db.exec(
|
||||
\\CREATE TABLE app_callbacks (
|
||||
\\ aid BLOB NOT NULL,
|
||||
\\ callback TEXT NOT NULL,
|
||||
\\ PRIMARY KEY (aid, callback),
|
||||
\\ FOREIGN KEY (aid) REFERENCES apps (aid)
|
||||
\\ ON UPDATE CASCADE
|
||||
\\ ON DELETE CASCADE
|
||||
\\)
|
||||
, .{}, .{});
|
||||
user_version += 1;
|
||||
try self.setUserVersion(user_version);
|
||||
}
|
||||
}
|
||||
|
||||
// --- USERS ---------------------------------------------------------------
|
||||
|
||||
pub fn createUser(self: *Database, name: []const u8, email: []const u8, plain_password: []const u8) !UserId {
|
||||
const uid = UserId.new();
|
||||
var password_buf: [1000]u8 = undefined;
|
||||
const password = try std.crypto.pwhash.argon2.strHash(plain_password, .{
|
||||
.allocator = self.allocator,
|
||||
.mode = .argon2id,
|
||||
.params = .owasp_2id,
|
||||
}, &password_buf);
|
||||
|
||||
try self.db.exec("INSERT INTO users (uid, name, email, password) VALUES (?, ?, ?, ?)", .{}, .{
|
||||
.uid = uid.bytes,
|
||||
.name = name,
|
||||
.email = email,
|
||||
.password = password,
|
||||
});
|
||||
|
||||
return uid;
|
||||
}
|
||||
|
||||
// --- APPS ----------------------------------------------------------------
|
||||
|
||||
pub fn createApp(self: *Database, name: []const u8, callbacks: []const []const u8) !CreateAppResult {
|
||||
_ = try self.db.exec("BEGIN", .{}, .{});
|
||||
errdefer self.db.exec("ROLLBACK", .{}, .{}) catch unreachable;
|
||||
|
||||
const secret_bytes = blk: {
|
||||
var bytes: [24]u8 = undefined;
|
||||
std.crypto.random.bytes(&bytes);
|
||||
break :blk bytes;
|
||||
};
|
||||
|
||||
var plain_secret_buf: [32]u8 = undefined;
|
||||
const plain_secret = std.base64.url_safe_no_pad.Encoder.encode(&plain_secret_buf, &secret_bytes);
|
||||
std.debug.assert(plain_secret_buf.len == plain_secret.len);
|
||||
|
||||
const aid = AppId.new();
|
||||
var secret_buf: [1000]u8 = undefined;
|
||||
const secret = try std.crypto.pwhash.argon2.strHash(plain_secret, .{
|
||||
.allocator = self.allocator,
|
||||
.mode = .argon2id,
|
||||
.params = .owasp_2id,
|
||||
}, &secret_buf);
|
||||
|
||||
try self.db.exec("INSERT INTO apps (aid, name, secret) VALUES (?, ?, ?)", .{}, .{
|
||||
.aid = aid.bytes,
|
||||
.name = name,
|
||||
.secret = secret,
|
||||
});
|
||||
|
||||
var insert_callback = try self.db.prepare("INSERT INTO app_callbacks (aid, callback) VALUES (?, ?)");
|
||||
defer insert_callback.deinit();
|
||||
for (callbacks) |callback| {
|
||||
try insert_callback.exec(.{}, .{ .aid = aid.bytes, .callback = callback });
|
||||
insert_callback.reset();
|
||||
}
|
||||
|
||||
try self.db.exec("COMMIT", .{}, .{});
|
||||
return .{
|
||||
.aid = aid,
|
||||
.plain_secret = plain_secret_buf,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
test "user version" {
|
||||
var db = try Database.init(":memory:", std.testing.allocator);
|
||||
defer db.deinit();
|
||||
|
||||
try std.testing.expectEqual(0, try db.getUserVersion());
|
||||
try db.setUserVersion(1);
|
||||
try std.testing.expectEqual(1, try db.getUserVersion());
|
||||
}
|
||||
|
||||
test "migrate" {
|
||||
var db = try Database.init(":memory:", std.testing.allocator);
|
||||
defer db.deinit();
|
||||
|
||||
try db.migrate();
|
||||
}
|
||||
|
||||
test "create user" {
|
||||
var db = try Database.init(":memory:", std.testing.allocator);
|
||||
defer db.deinit();
|
||||
|
||||
try db.migrate();
|
||||
|
||||
const uid = try db.createUser("admin", "admin@test.invalid", "admin");
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
|
||||
const maybe_user = try db.db.oneAlloc(struct {
|
||||
name: []const u8,
|
||||
email: []const u8,
|
||||
password: []const u8,
|
||||
}, arena.allocator(), "SELECT name, email, password FROM users WHERE uid = ?", .{}, .{
|
||||
.uid = uid.bytes,
|
||||
});
|
||||
defer arena.deinit();
|
||||
|
||||
try std.testing.expect(maybe_user != null);
|
||||
if (maybe_user) |user| {
|
||||
try std.testing.expectEqualSlices(u8, "admin", user.name);
|
||||
}
|
||||
}
|
||||
|
||||
test "create app" {
|
||||
var db = try Database.init(":memory:", std.testing.allocator);
|
||||
defer db.deinit();
|
||||
|
||||
try db.migrate();
|
||||
|
||||
_ = try db.createApp("app", &.{
|
||||
"http://localhost:3000/callback",
|
||||
"https://example.com/callback",
|
||||
});
|
||||
}
|
||||
const web = @import("web");
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
var lock: std.Thread.Mutex = .{};
|
||||
var last_timestamp: std.atomic.Value(u64) = .{ .raw = 0 };
|
||||
var counter: std.atomic.Value(u32) = .{ .raw = 0 };
|
||||
|
||||
fn getCount(timestamp: u64) u32 {
|
||||
lock.lock();
|
||||
defer lock.unlock();
|
||||
|
||||
if (last_timestamp.swap(timestamp, .monotonic) != timestamp) {
|
||||
counter.store(0, .monotonic);
|
||||
}
|
||||
|
||||
return counter.fetchAdd(1, .monotonic) % 4096;
|
||||
}
|
||||
|
||||
pub fn uuid_v7() [16]u8 {
|
||||
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);
|
||||
break :blk bytes;
|
||||
};
|
||||
|
||||
var res: [16]u8 = undefined;
|
||||
|
||||
res[0] = @truncate(timestamp >> 40);
|
||||
res[1] = @truncate(timestamp >> 32);
|
||||
res[2] = @truncate(timestamp >> 24);
|
||||
res[3] = @truncate(timestamp >> 16);
|
||||
res[4] = @truncate(timestamp >> 8);
|
||||
res[5] = @truncate(timestamp);
|
||||
res[6] = (@as(u8, 7) << 4) | @as(u8, @truncate((count >> 8) & 0x0F));
|
||||
res[7] = @truncate(count);
|
||||
res[8] = 0x80 | (random[0] & 0x3F);
|
||||
@memcpy(res[9..16], random[1..8]);
|
||||
|
||||
return res;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.resolveTargetQuery(.{
|
||||
.cpu_arch = .x86_64,
|
||||
.os_tag = .windows,
|
||||
.abi = .msvc,
|
||||
});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const zigwin32 = b.dependency("zigwin32", .{});
|
||||
const zigwin32_mod = zigwin32.module("win32");
|
||||
zigwin32_mod.resolved_target = target;
|
||||
zigwin32_mod.optimize = optimize;
|
||||
|
||||
const exe_mod = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
exe_mod.addImport("win32", zigwin32_mod);
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "sciter",
|
||||
.root_module = exe_mod,
|
||||
});
|
||||
|
||||
b.installArtifact(exe);
|
||||
b.getInstallStep().dependOn(&b.addInstallBinFile(b.path("vendor/sciter.dll"), "sciter.dll").step);
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
.{
|
||||
.name = .sciter,
|
||||
.version = "0.0.0",
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.paths = .{
|
||||
"src",
|
||||
"vendor",
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
},
|
||||
.fingerprint = 0x51b124a630f074d7,
|
||||
.dependencies = .{
|
||||
.zigwin32 = .{
|
||||
.url = "https://github.com/marlersoft/zigwin32/archive/5587b16fa040573846a6bf531301f6206d31a6bf.zip",
|
||||
.hash = "zigwin32-25.0.28-preview-AAAAAICM5AMResOGQnQ85mfe60TTOQeMtt7GRATUOKoP",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Sciter Demo</title>
|
||||
<style>
|
||||
html {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 16px;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#hello {
|
||||
width: 120px;
|
||||
height: 32px;
|
||||
|
||||
margin: 1*;
|
||||
padding: 0;
|
||||
|
||||
border: 1px solid black;
|
||||
|
||||
text-align: center;
|
||||
line-height: 32px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<p id="hello">Hello, World!</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,115 +0,0 @@
|
||||
const sciter = @import("sciter.zig");
|
||||
const std = @import("std");
|
||||
const wam = @import("win32").ui.windows_and_messaging;
|
||||
const win32 = @import("win32").foundation;
|
||||
|
||||
const L = std.unicode.utf8ToUtf16LeStringLiteral;
|
||||
const WINAPI = @import("std").builtin.CallingConvention.winapi;
|
||||
|
||||
const html = @embedFile("index.html");
|
||||
|
||||
const min_track_size: win32.POINT = .{ .x = 640, .y = 480 };
|
||||
|
||||
var sciter_api: *sciter.API = undefined;
|
||||
|
||||
pub fn wWinMain(
|
||||
hInstance: std.os.windows.HINSTANCE,
|
||||
hPrevInstance: ?std.os.windows.HINSTANCE,
|
||||
lpCmdLine: [*:0]u16,
|
||||
nCmdShow: i32,
|
||||
) i32 {
|
||||
_ = hPrevInstance;
|
||||
_ = lpCmdLine;
|
||||
|
||||
const sciter_module = std.os.windows.LoadLibraryW(L("sciter.dll")) catch |err| {
|
||||
_ = wam.MessageBoxW(
|
||||
null,
|
||||
switch (err) {
|
||||
error.FileNotFound => L("Couldn't find sciter.dll."),
|
||||
else => L("An unknown error occured while trying to load sciter.dll."),
|
||||
},
|
||||
L("Critical error"),
|
||||
wam.MB_ICONHAND,
|
||||
);
|
||||
std.posix.exit(1);
|
||||
};
|
||||
|
||||
const sciter_api_fn: *const fn () ?*sciter.API = @ptrCast(std.os.windows.kernel32.GetProcAddress(sciter_module, "SciterAPI") orelse {
|
||||
_ = wam.MessageBoxW(null, L("Couldn't load Sciter API."), L("Critical error"), wam.MB_ICONHAND);
|
||||
std.posix.exit(1);
|
||||
});
|
||||
|
||||
sciter_api = sciter_api_fn() orelse {
|
||||
_ = wam.MessageBoxW(null, L("Couldn't load Sciter API."), L("Critical error"), wam.MB_ICONHAND);
|
||||
std.posix.exit(1);
|
||||
};
|
||||
|
||||
std.debug.print("Sciter API version is: 0x{X:0>8}\n", .{sciter_api.version});
|
||||
|
||||
const wc = std.mem.zeroInit(wam.WNDCLASSEXW, .{
|
||||
.cbSize = @sizeOf(wam.WNDCLASSEXW),
|
||||
.lpfnWndProc = &WindowProc,
|
||||
.hInstance = hInstance,
|
||||
.hIcon = wam.LoadIconW(null, wam.IDI_APPLICATION),
|
||||
.hCursor = wam.LoadCursorW(null, wam.IDC_ARROW),
|
||||
.lpszClassName = L("SCITER_WINDOW"),
|
||||
});
|
||||
|
||||
const atom = wam.RegisterClassExW(&wc);
|
||||
std.debug.assert(atom != 0);
|
||||
|
||||
const hwnd = wam.CreateWindowExW(
|
||||
wam.WS_EX_APPWINDOW,
|
||||
wc.lpszClassName,
|
||||
L("Sciter Demo"),
|
||||
wam.WS_OVERLAPPEDWINDOW,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
-1,
|
||||
null,
|
||||
null,
|
||||
wc.hInstance,
|
||||
null,
|
||||
) orelse {
|
||||
_ = wam.MessageBoxW(null, L("Couldn't create window."), L("Critical error"), wam.MB_ICONHAND);
|
||||
std.posix.exit(1);
|
||||
};
|
||||
|
||||
_ = sciter_api.SciterLoadHtml(hwnd, html, html.len, L("/"));
|
||||
_ = wam.ShowWindow(hwnd, @bitCast(nCmdShow));
|
||||
|
||||
var msg = std.mem.zeroes(wam.MSG);
|
||||
while (wam.GetMessageW(&msg, null, 0, 0) > 0) {
|
||||
_ = wam.TranslateMessage(&msg);
|
||||
_ = wam.DispatchMessageW(&msg);
|
||||
}
|
||||
|
||||
return @bitCast(@as(u32, @truncate(msg.wParam)));
|
||||
}
|
||||
|
||||
fn WindowProc(
|
||||
hwnd: win32.HWND,
|
||||
uMsg: u32,
|
||||
wParam: win32.WPARAM,
|
||||
lParam: win32.LPARAM,
|
||||
) callconv(WINAPI) win32.LRESULT {
|
||||
switch (uMsg) {
|
||||
wam.WM_DESTROY => {
|
||||
wam.PostQuitMessage(0);
|
||||
},
|
||||
wam.WM_GETMINMAXINFO => {
|
||||
const mmi: *wam.MINMAXINFO = @ptrFromInt(@as(usize, @bitCast(lParam)));
|
||||
mmi.ptMinTrackSize = min_track_size;
|
||||
},
|
||||
else => {},
|
||||
}
|
||||
|
||||
var handled: sciter.Bool = .FALSE;
|
||||
const result = sciter_api.SciterProcND(hwnd, uMsg, wParam, lParam, &handled);
|
||||
if (handled != .FALSE) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return wam.DefWindowProcW(hwnd, uMsg, wParam, lParam);
|
||||
}
|
||||
@@ -1,641 +0,0 @@
|
||||
pub const CStr = [*:0]const u8;
|
||||
pub const CWStr = [*:0]const u16;
|
||||
|
||||
pub const Element = opaque {};
|
||||
pub const HElement = *Element;
|
||||
|
||||
pub const Node = opaque {};
|
||||
pub const HNode = *Node;
|
||||
|
||||
pub const SArchive = opaque {};
|
||||
pub const HSArchive = *SArchive;
|
||||
|
||||
/// - Windows: `HWND`
|
||||
/// - OS X: `NSView*`
|
||||
/// - Linux/GTK: `GtkWidget*`
|
||||
pub const HWindow = *anyopaque;
|
||||
|
||||
pub const BehaviorEventParams = extern struct {
|
||||
cmd: BehaviorEvents,
|
||||
he_target: ?HElement,
|
||||
he: ?HElement,
|
||||
reason: extern union {
|
||||
click: enum(usize) { BY_MOUSE_CLICK, BY_KEY_CLICK, SYNTHESIZED, BY_MOUSE_ON_ICON },
|
||||
edit_changed: enum(usize) { BY_INS_CHAR, BY_INS_CHARS, BY_DEL_CHAR, BY_DEL_CHARS, BY_UNDO_REDO },
|
||||
custom: usize,
|
||||
},
|
||||
data: Value,
|
||||
name: ?CWStr,
|
||||
};
|
||||
|
||||
const BehaviorEvents = enum(u32) {
|
||||
BUTTON_CLICK = 0,
|
||||
BUTTON_PRESS = 1,
|
||||
|
||||
VALUE_CHANGED = 2,
|
||||
VALUE_CHANGING = 3,
|
||||
|
||||
SELECTION_CHANGED = 5,
|
||||
SELECTION_CHANGING = 0xC,
|
||||
|
||||
POPUP_REQUEST = 7,
|
||||
POPUP_READY = 8,
|
||||
POPUP_DISMISSED = 9,
|
||||
|
||||
MENU_ITEM_ACTIVE = 0xA,
|
||||
MENU_ITEM_CLICK = 0xB,
|
||||
|
||||
CONTEXT_MENU_REQUEST = 0x10,
|
||||
|
||||
VISUAL_STATUS_CHANGED = 0x11,
|
||||
DISABLED_STATUS_CHANGED = 0x12,
|
||||
|
||||
POPUP_DISMISSING = 0x13,
|
||||
|
||||
CONTENT_CHANGED = 0x15,
|
||||
|
||||
HYPERLINK_CLICK = 0x80,
|
||||
|
||||
ELEMENT_COLLAPSED = 0x90,
|
||||
ELEMENT_EXPANDED = 0x91,
|
||||
|
||||
ACTIVATE_CHILD = 0x92,
|
||||
|
||||
FORM_SUBMIT = 0x96,
|
||||
FORM_RESET = 0x97,
|
||||
|
||||
DOCUMENT_COMPLETE = 0x98,
|
||||
|
||||
HISTORY_PUSH = 0x99,
|
||||
HISTORY_DROP = 0x9A,
|
||||
HISTORY_PRIOR = 0x9B,
|
||||
HISTORY_NEXT = 0x9C,
|
||||
HISTORY_STATE_CHANGED = 0x9D,
|
||||
|
||||
CLOSE_POPUP = 0x9E,
|
||||
REQUEST_TOOLTIP = 0x9F,
|
||||
|
||||
ANIMATION = 0xA0,
|
||||
TRANSITION = 0xA1,
|
||||
SWIPE = 0xB0,
|
||||
|
||||
DOCUMENT_CREATED = 0xC0,
|
||||
DOCUMENT_CLOSE_REQUEST = 0xC1,
|
||||
DOCUMENT_CLOSE = 0xC2,
|
||||
DOCUMENT_READY = 0xC3,
|
||||
DOCUMENT_PARSED = 0xC4,
|
||||
//DOCUMENT_RELOAD = 0xC5,
|
||||
DOCUMENT_CLOSING = 0xC6,
|
||||
CONTAINER_CLOSE_REQUEST = 0xC7,
|
||||
CONTAINER_CLOSING = 0xC8,
|
||||
|
||||
VIDEO_INITIALIZED = 0xD1,
|
||||
VIDEO_STARTED = 0xD2,
|
||||
VIDEO_STOPPED = 0xD3,
|
||||
VIDEO_BIND_RQ = 0xD4,
|
||||
|
||||
VIDEO_FRAME_REQUEST = 0xD8,
|
||||
|
||||
PAGINATION_STARTS = 0xE0,
|
||||
PAGINATION_PAGE = 0xE1,
|
||||
PAGINATION_ENDS = 0xE2,
|
||||
|
||||
CUSTOM = 0xF0,
|
||||
|
||||
EGL_RENDER = 0x20,
|
||||
|
||||
/// All custom event codes shall be greater than this number. All codes
|
||||
/// below this will be used solely by application - Sciter will not
|
||||
/// intrepret it and will do just dispatching. To send event notifications
|
||||
/// with these codes use SciterSend/PostEvent API.
|
||||
FIRST_APPLICATION_EVENT_CODE = 0x100,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const Bool = enum(u32) {
|
||||
FALSE = 0,
|
||||
TRUE = 1,
|
||||
_,
|
||||
};
|
||||
|
||||
pub const DomResult = enum(i32) {
|
||||
OK = 0,
|
||||
INVALID_HWND = 1,
|
||||
INVALID_HANDLE = 2,
|
||||
PASSIVE_HANDLE = 3,
|
||||
INVALID_PARAMETER = 4,
|
||||
OPERATION_FAILED = 5,
|
||||
OK_NOT_HANDLED = -1,
|
||||
};
|
||||
|
||||
pub const CtlType = enum(u32) {
|
||||
NO = 0,
|
||||
UNKNOWN = 1,
|
||||
|
||||
EDIT = 2,
|
||||
NUMERIC = 3,
|
||||
CLICKABLE = 4,
|
||||
BUTTON = 5,
|
||||
CHECKBOX = 6,
|
||||
RADIO = 7,
|
||||
SELECT_SINGLE = 8,
|
||||
SELECT_MULTIPLE = 9,
|
||||
DD_SELECT = 10,
|
||||
TEXTAREA = 11,
|
||||
HTMLAREA = 12,
|
||||
PASSWORD = 13,
|
||||
PROGRESS = 14,
|
||||
SLIDER = 15,
|
||||
DECIMAL = 16,
|
||||
CURRENCY = 17,
|
||||
SCROLLBAR = 18,
|
||||
LIST = 19,
|
||||
RICHTEXT = 20,
|
||||
CALENDAR = 21,
|
||||
DATE = 22,
|
||||
TIME = 23,
|
||||
FILE = 24,
|
||||
PATH = 25,
|
||||
|
||||
HYPERLINK = 26,
|
||||
FORM = 27,
|
||||
|
||||
MENUBAR = 28,
|
||||
MENU = 29,
|
||||
MENUBUTTON = 30,
|
||||
|
||||
FRAME = 31,
|
||||
FRAMESET = 32,
|
||||
|
||||
TOOLTIP = 33,
|
||||
|
||||
HIDDEN = 34,
|
||||
URL = 35,
|
||||
TOOLBAR = 36,
|
||||
|
||||
WINDOW = 37,
|
||||
|
||||
LABEL = 38,
|
||||
IMAGE = 39,
|
||||
PLAINTEXT = 40,
|
||||
|
||||
SELECT_TREE = 41,
|
||||
};
|
||||
|
||||
pub const ElementAreas = packed struct {
|
||||
relative: enum(u4) {
|
||||
ROOT = 0x1,
|
||||
SELF = 0x2,
|
||||
CONTAINER = 0x3,
|
||||
VIEW = 0x4,
|
||||
_,
|
||||
} = @enumFromInt(0x0),
|
||||
|
||||
area: enum(u4) {
|
||||
CONTENT_BOX = 0x0,
|
||||
PADDING_BOX = 0x1,
|
||||
BORDER_BOX = 0x2,
|
||||
MARGIN_BOX = 0x3,
|
||||
|
||||
BACK_IMAGE_AREA = 0x4,
|
||||
FORE_IMAGE_AREA = 0x5,
|
||||
|
||||
SCROLLABLE_AREA = 0x6,
|
||||
_,
|
||||
} = .CONTENT_BOX,
|
||||
|
||||
_pad8: u8 = 0,
|
||||
|
||||
as_ppx: bool = false,
|
||||
|
||||
_pad17: u15 = 0,
|
||||
};
|
||||
|
||||
pub const MethodParams = extern struct {
|
||||
method_id: u32,
|
||||
};
|
||||
|
||||
pub const NodeType = enum(u32) { ELEMENT, TEXT, COMMENT };
|
||||
|
||||
pub const NodeInsTarget = enum(u32) { BEFORE, AFTER, APPEND, PREPEND };
|
||||
|
||||
pub const Point = extern struct {
|
||||
x: i32,
|
||||
y: i32,
|
||||
};
|
||||
|
||||
pub const Rect = extern struct {
|
||||
left: i32,
|
||||
top: i32,
|
||||
right: i32,
|
||||
bottom: u32,
|
||||
};
|
||||
|
||||
pub const RequestParam = extern struct {
|
||||
name: CWStr,
|
||||
value: CWStr,
|
||||
};
|
||||
|
||||
pub const RequestType = enum(u32) {
|
||||
GET_ASYNC,
|
||||
POST_ASYNC,
|
||||
GET_SYNC,
|
||||
POST_SYNC,
|
||||
};
|
||||
|
||||
pub const ResourceType = enum(u32) {
|
||||
HTML = 0,
|
||||
IMAGE = 1,
|
||||
STYLE = 2,
|
||||
CURSOR = 3,
|
||||
SCRIPT = 4,
|
||||
RAW = 5,
|
||||
FONT,
|
||||
SOUND,
|
||||
FORCE_DWORD = 0xFFFFFFFF,
|
||||
};
|
||||
|
||||
pub const SetElementHtml = enum(u32) {
|
||||
SIH_REPLACE_CONTENT = 0,
|
||||
SIH_INSERT_AT_START = 1,
|
||||
SIH_APPEND_AFTER_LAST = 2,
|
||||
SOH_REPLACE = 3,
|
||||
SOH_INSERT_BEFORE = 4,
|
||||
SOH_INSERT_AFTER = 5,
|
||||
};
|
||||
|
||||
pub const Size = extern struct {
|
||||
cx: i32,
|
||||
cy: i32,
|
||||
};
|
||||
|
||||
pub const Value = extern struct {
|
||||
type: ValueType,
|
||||
unit: extern union {
|
||||
value: ValueUnit,
|
||||
/// For when `Value.type == .OBJECT`
|
||||
object: ValueUnitObject,
|
||||
},
|
||||
data: u64,
|
||||
};
|
||||
|
||||
pub const ValueType = enum(u32) {
|
||||
UNDEFINED = 0,
|
||||
NULL = 1,
|
||||
BOOL = 2,
|
||||
i32 = 3,
|
||||
FLOAT = 4,
|
||||
STRING = 5,
|
||||
DATE = 6,
|
||||
BIG_INT = 7,
|
||||
LENGTH = 8,
|
||||
ARRAY = 9,
|
||||
MAP = 10,
|
||||
FUNCTION = 11,
|
||||
BYTES = 12,
|
||||
OBJECT = 13,
|
||||
RESOURCE = 15,
|
||||
DURATION = 17,
|
||||
ANGLE = 18,
|
||||
COLOR = 19,
|
||||
ASSET = 21,
|
||||
};
|
||||
|
||||
pub const ValueUnit = enum(u32) {
|
||||
EM = 1,
|
||||
EX = 2,
|
||||
PR = 3,
|
||||
SP = 4,
|
||||
|
||||
PX = 7,
|
||||
IN = 8,
|
||||
CM = 9,
|
||||
MM = 10,
|
||||
PT = 11,
|
||||
PC = 12,
|
||||
DIP = 13,
|
||||
|
||||
PR_WIDTH = 16,
|
||||
PR_HEIGHT = 17,
|
||||
PR_VIEW_WIDTH = 18,
|
||||
PR_VIEW_HEIGHT = 19,
|
||||
PR_VIEW_MIN = 20,
|
||||
PR_VIEW_MAX = 21,
|
||||
|
||||
REM = 22,
|
||||
PPX = 23,
|
||||
CH = 24,
|
||||
};
|
||||
|
||||
pub const ValueUnitObject = enum(u32) {
|
||||
ARRAY = 0,
|
||||
OBJECT = 1,
|
||||
CLASS = 2,
|
||||
NATIVE = 3,
|
||||
FUNCTION = 4,
|
||||
ERROR = 5,
|
||||
BUFFER = 6,
|
||||
};
|
||||
|
||||
pub const ValueResult = enum(i32) {
|
||||
OK_TRUE = -1,
|
||||
OK = 0,
|
||||
BAD_PARAMETER = 1,
|
||||
INCOMPATIBLE_TYPE = 2,
|
||||
};
|
||||
|
||||
pub const OutputSubsystems = enum(u32) {
|
||||
DOM = 0,
|
||||
CSSS,
|
||||
CSS,
|
||||
TIS,
|
||||
};
|
||||
|
||||
pub const CallbackNotification = extern struct {
|
||||
code: u32,
|
||||
hwnd: HWindow,
|
||||
};
|
||||
|
||||
pub const XMsgCode = enum(u32) {
|
||||
CREATE = 0,
|
||||
DESTROY = 1,
|
||||
SIZE = 2,
|
||||
PAINT = 3,
|
||||
RESOLUTION = 4,
|
||||
HEARTBIT = 5,
|
||||
MOUSE = 6,
|
||||
KEY = 7,
|
||||
FOCUS = 8,
|
||||
};
|
||||
|
||||
pub const XMsg = extern struct {
|
||||
msg: XMsgCode,
|
||||
};
|
||||
|
||||
pub const OmAsset = extern struct {
|
||||
isa: *OmAssetClass,
|
||||
};
|
||||
|
||||
pub const OmAssetClass = extern struct {
|
||||
asset_add_ref: *const fn (thing: *OmAsset) callconv(.c) c_long,
|
||||
asset_release: *const fn (thing: *OmAsset) callconv(.c) c_long,
|
||||
asset_get_interface: *const fn (thing: *OmAsset, name: CStr, out: *?*anyopaque) callconv(.c) c_long,
|
||||
asset_get_passport: *const fn (thins: *OmAsset) callconv(.c) ?*OmPassport,
|
||||
};
|
||||
|
||||
pub const OmPassport = opaque {};
|
||||
|
||||
pub const ValueStringCvtType = enum(u32) {
|
||||
CVT_SIMPLE,
|
||||
CVT_JSON_LITERAL,
|
||||
CVT_JSON_MAP,
|
||||
CVT_XJSON_LITERAL,
|
||||
};
|
||||
|
||||
const ByteReceiver = fn (str: [*]const u8, num_bytes: u32, param: ?*anyopaque) callconv(.c) void;
|
||||
const DebugOutputProc = fn (param: ?*anyopaque, subsystem: OutputSubsystems, severity: u32, text: [*]const u16, text_length: u32) callconv(.c) void;
|
||||
const ElementCallback = fn (he: HElement, param: ?*anyopaque) callconv(.c) Bool;
|
||||
const ElementComparator = fn (he1: HElement, he2: HElement, param: ?*anyopaque) callconv(.c) i32;
|
||||
const ElementEventProc = fn (tag: ?*anyopaque, he: HElement, evtg: u32, prms: ?*anyopaque) callconv(.c) Bool;
|
||||
const HostCallback = fn (pns: *CallbackNotification, callback_param: ?*anyopaque) callconv(.c) u32;
|
||||
const KeyValueCallback = fn (param: ?*anyopaque, pkey: *const Value, pval: *const Value) callconv(.c) Bool;
|
||||
const NativeFunctorInvoke = fn (tag: ?*anyopaque, argc: u32, argv: [*]const Value, retval: *Value) callconv(.c) void;
|
||||
const NativeFunctorRelease = fn (tag: ?*anyopaque) callconv(.c) void;
|
||||
const StrReceiver = fn (str: [*]const u8, num_bytes: u32, param: ?*anyopaque) callconv(.c) void;
|
||||
const WindowDelegate = fn (hwnd: HWindow, msg: u32, wParam: usize, lParam: isize, pbHandled: *Bool) callconv(.c) isize;
|
||||
const WStrReceiver = fn (str: [*]const u16, num_bytes: u32, param: ?*anyopaque) callconv(.c) void;
|
||||
|
||||
pub const API = extern struct {
|
||||
version: u32,
|
||||
|
||||
SciterClassName: *const fn () callconv(.c) CWStr,
|
||||
SciterVersion: *const fn (n: u32) callconv(.c) u32,
|
||||
SciterDataReady: *const fn (hwnd: HWindow, uri: CWStr, data: [*]const u8, dataLength: u32) callconv(.c) Bool,
|
||||
SciterDataReadyAsync: *const fn (hwnd: HWindow, uri: CWStr, data: [*]const u8, dataLength: u32, requestId: ?*anyopaque) callconv(.c) Bool,
|
||||
|
||||
SciterProc: *const fn (hwnd: HWindow, msg: u32, wParam: usize, lParam: isize) callconv(.c) isize,
|
||||
SciterProcND: *const fn (hwnd: HWindow, msg: u32, wParam: usize, lParam: isize, pbHandled: *Bool) callconv(.c) isize,
|
||||
|
||||
SciterLoadFile: *const fn (hwnd: HWindow, filename: CWStr) callconv(.c) Bool,
|
||||
SciterLoadHtml: *const fn (hwnd: HWindow, html: [*]const u8, htmlSize: u32, baseUrl: CWStr) callconv(.c) Bool,
|
||||
|
||||
SciterSetCallback: *const fn (hwnd: HWindow, cb: ?*HostCallback, cbParam: ?*anyopaque) callconv(.c) void,
|
||||
SciterSetMasterCSS: *const fn (utf8: [*]const u8, numBytes: u32) callconv(.c) Bool,
|
||||
SciterAppendMasterCSS: *const fn (utf8: [*]const u8, numBytes: u32) callconv(.c) Bool,
|
||||
SciterSetCSS: *const fn (hwnd: HWindow, utf8: [*]const u8, numBytes: u32, baseUrl: CWStr, mediaType: CWStr) callconv(.c) Bool,
|
||||
SciterSetMediaType: *const fn (hwnd: HWindow, mediaType: CWStr) callconv(.c) Bool,
|
||||
SciterSetMediaVars: *const fn (hwnd: HWindow, mediaVars: *const Value) callconv(.c) Bool,
|
||||
SciterGetMinWidth: *const fn (hwnd: HWindow) callconv(.c) u32,
|
||||
SciterGetMinHeight: *const fn (hwnd: HWindow, width: u32) callconv(.c) u32,
|
||||
SciterCall: *const fn (hWnd: HWindow, functionName: CStr, argc: u32, argv: [*]const Value, retval: *Value) callconv(.c) Bool,
|
||||
SciterEval: *const fn (hwnd: HWindow, script: [*]const u16, scriptLength: u32, pretval: *Value) callconv(.c) Bool,
|
||||
SciterUpdateWindow: *const fn (hwnd: HWindow) callconv(.c) void,
|
||||
/// Win32 MSG
|
||||
SciterTranslateMessage: *const fn (lpMsg: *anyopaque) callconv(.c) Bool,
|
||||
SciterSetOption: *const fn (hWnd: HWindow, option: u32, value: usize) callconv(.c) Bool,
|
||||
SciterGetPPI: *const fn (hwnd: HWindow, px: *u32, py: *u32) callconv(.c) void,
|
||||
SciterGetViewExpando: *const fn (hwnd: HWindow, pval: *Value) callconv(.c) Bool,
|
||||
/// ID2D1RenderTarget
|
||||
SciterRenderD2D: *const fn (hwnd: HWindow, prt: *anyopaque) callconv(.c) Bool,
|
||||
/// ID2D1Factory
|
||||
SciterD2DFactory: *const fn (ppf: *anyopaque) callconv(.c) Bool,
|
||||
/// IDWriteFactory
|
||||
SciterDWFactory: *const fn (ppf: *anyopaque) callconv(.c) Bool,
|
||||
SciterGraphicsCaps: *const fn (pcaps: *u32) callconv(.c) Bool,
|
||||
SciterSetHomeURL: *const fn (hwnd: HWindow, baseUrl: CWStr) callconv(.c) Bool,
|
||||
SciterCreateNSView: *anyopaque,
|
||||
SciterCreateWidget: *anyopaque,
|
||||
SciterCreateWindow: *const fn (creationFlags: u32, frame: *Rect, delegate: *const WindowDelegate, delegateParam: ?*anyopaque, parent: HWindow) callconv(.c) HWindow,
|
||||
|
||||
SciterSetupDebugOutput: *const fn (hwndOrNull: ?HWindow, param: ?*anyopaque, pfOutput: ?*DebugOutputProc) callconv(.c) void,
|
||||
|
||||
// --- DOM API -------------------------------------------------------------
|
||||
|
||||
Sciter_UseElement: *const fn (he: HElement) callconv(.c) DomResult,
|
||||
Sciter_UnuseElement: *const fn (he: HElement) callconv(.c) DomResult,
|
||||
SciterGetRootElement: *const fn (hwnd: HWindow, phe: *HElement) callconv(.c) DomResult,
|
||||
SciterGetFocusElement: *const fn (hwnd: HWindow, phe: *HElement) callconv(.c) DomResult,
|
||||
SciterFindElement: *const fn (hwnd: HWindow, pt: Point, phe: *HElement) callconv(.c) DomResult,
|
||||
SciterGetChildrenCount: *const fn (he: HElement, count: *u32) callconv(.c) DomResult,
|
||||
SciterGetNthChild: *const fn (he: HElement, n: u32, phe: *HElement) callconv(.c) DomResult,
|
||||
SciterGetParentElement: *const fn (he: HElement, p_parent_he: *HElement) callconv(.c) DomResult,
|
||||
SciterGetElementHtmlCB: *const fn (he: HElement, outer: Bool, rcv: *const ByteReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterGetElementTextCB: *const fn (he: HElement, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterSetElementText: *const fn (he: HElement, utf16: [*]const u16, length: u32) callconv(.c) DomResult,
|
||||
SciterGetAttributeCount: *const fn (he: HElement, p_count: *u32) callconv(.c) DomResult,
|
||||
SciterGetNthAttributeNameCB: *const fn (he: HElement, n: u32, rcv: *const StrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterGetNthAttributeValueCB: *const fn (he: HElement, n: u32, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterGetAttributeByNameCB: *const fn (he: HElement, name: CStr, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterSetAttributeByName: *const fn (he: HElement, name: CStr, value: CWStr) callconv(.c) DomResult,
|
||||
SciterClearAttributes: *const fn (he: HElement) callconv(.c) DomResult,
|
||||
SciterGetElementIndex: *const fn (he: HElement, p_index: *u32) callconv(.c) DomResult,
|
||||
SciterGetElementType: *const fn (he: HElement, p_type: *CStr) callconv(.c) DomResult,
|
||||
SciterGetElementTypeCB: *const fn (he: HElement, rcv: *const StrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterGetStyleAttributeCB: *const fn (he: HElement, name: CStr, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterSetStyleAttribute: *const fn (he: HElement, name: CStr, value: CWStr) callconv(.c) DomResult,
|
||||
SciterGetElementLocation: *const fn (he: HElement, p_location: *Rect, areas: ElementAreas) callconv(.c) DomResult,
|
||||
SciterScrollToView: *const fn (he: HElement, SciterScrollFlags: u32) callconv(.c) DomResult,
|
||||
SciterUpdateElement: *const fn (he: HElement, andForceRender: Bool) callconv(.c) DomResult,
|
||||
SciterRefreshElementArea: *const fn (he: HElement, rc: Rect) callconv(.c) DomResult,
|
||||
SciterSetCapture: *const fn (he: HElement) callconv(.c) DomResult,
|
||||
SciterReleaseCapture: *const fn (he: HElement) callconv(.c) DomResult,
|
||||
SciterGetElementHwnd: *const fn (he: HElement, p_hwnd: *HWindow, rootWindow: Bool) callconv(.c) DomResult,
|
||||
SciterCombineURL: *const fn (he: HElement, szUrlBuffer: [*]u16, UrlBufferSize: u32) callconv(.c) DomResult,
|
||||
SciterSelectElements: *const fn (he: HElement, CSS_selectors: CStr, callback: *const ElementCallback, param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterSelectElementsW: *const fn (he: HElement, CSS_selectors: CWStr, callback: *const ElementCallback, param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterSelectParent: *const fn (he: HElement, selector: CStr, depth: u32, heFound: *HElement) callconv(.c) DomResult,
|
||||
SciterSelectParentW: *const fn (he: HElement, selector: CWStr, depth: u32, heFound: *HElement) callconv(.c) DomResult,
|
||||
SciterSetElementHtml: *const fn (he: HElement, html: [*]const u8, htmlLength: u32, where: SetElementHtml) callconv(.c) DomResult,
|
||||
SciterGetElementUID: *const fn (he: HElement, puid: *u32) callconv(.c) DomResult,
|
||||
SciterGetElementByUID: *const fn (hwnd: HWindow, uid: u32, phe: *HElement) callconv(.c) DomResult,
|
||||
SciterShowPopup: *const fn (hePopup: HElement, heAnchor: HElement, placement: u32) callconv(.c) DomResult,
|
||||
SciterShowPopupAt: *const fn (hePopup: HElement, pos: Point, placement: u32) callconv(.c) DomResult,
|
||||
SciterHidePopup: *const fn (he: HElement) callconv(.c) DomResult,
|
||||
SciterGetElementState: *const fn (he: HElement, pstateBits: *u32) callconv(.c) DomResult,
|
||||
SciterSetElementState: *const fn (he: HElement, stateBitsToSet: u32, stateBitsToClear: u32, updateView: Bool) callconv(.c) DomResult,
|
||||
SciterCreateElement: *const fn (tagname: CStr, textOrNull: ?CWStr, phe: *HElement) callconv(.c) DomResult,
|
||||
SciterCloneElement: *const fn (he: HElement, phe: *HElement) callconv(.c) DomResult,
|
||||
SciterInsertElement: *const fn (he: HElement, hparent: HElement, index: u32) callconv(.c) DomResult,
|
||||
SciterDetachElement: *const fn (he: HElement) callconv(.c) DomResult,
|
||||
SciterDeleteElement: *const fn (he: HElement) callconv(.c) DomResult,
|
||||
SciterSetTimer: *const fn (he: HElement, milliseconds: u32, timer_id: usize) callconv(.c) DomResult,
|
||||
SciterDetachEventHandler: *const fn (he: HElement, pep: *const ElementEventProc, tag: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterAttachEventHandler: *const fn (he: HElement, pep: *const ElementEventProc, tag: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterWindowAttachEventHandler: *const fn (hwndLayout: HWindow, pep: *const ElementEventProc, tag: ?*anyopaque, subscription: u32) callconv(.c) DomResult,
|
||||
SciterWindowDetachEventHandler: *const fn (hwndLayout: HWindow, pep: *const ElementEventProc, tag: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterSendEvent: *const fn (he: HElement, appEventCode: u32, heSource: HElement, reason: usize, handled: *Bool) callconv(.c) DomResult,
|
||||
SciterPostEvent: *const fn (he: HElement, appEventCode: u32, heSource: HElement, reason: usize) callconv(.c) DomResult,
|
||||
SciterCallBehaviorMethod: *const fn (he: HElement, params: *MethodParams) callconv(.c) DomResult,
|
||||
SciterRequestElementData: *const fn (he: HElement, url: CWStr, dataType: u32, initiator: HElement) callconv(.c) DomResult,
|
||||
SciterHttpRequest: *const fn (he: HElement, url: CWStr, dataType: ResourceType, requestType: RequestType, requestParams: [*]RequestParam, nParams: u32) callconv(.c) DomResult,
|
||||
SciterGetScrollInfo: *const fn (he: HElement, scrollPos: *Point, viewRect: *Rect, contentSize: *Size) callconv(.c) DomResult,
|
||||
SciterSetScrollPos: *const fn (he: HElement, scrollPos: Point, smooth: Bool) callconv(.c) DomResult,
|
||||
SciterGetElementIntrinsicWidths: *const fn (he: HElement, pMinWidth: *i32, pMaxWidth: *i32) callconv(.c) DomResult,
|
||||
SciterGetElementIntrinsicHeight: *const fn (he: HElement, forWidth: i32, pHeight: *i32) callconv(.c) DomResult,
|
||||
SciterIsElementVisible: *const fn (he: HElement, pVisible: *Bool) callconv(.c) DomResult,
|
||||
SciterIsElementEnabled: *const fn (he: HElement, pEnabled: *Bool) callconv(.c) DomResult,
|
||||
SciterSortElements: *const fn (he: HElement, firstIndex: u32, lastIndex: u32, cmpFunc: *const ElementComparator, cmpFuncParam: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterSwapElements: *const fn (he1: HElement, he2: HElement) callconv(.c) DomResult,
|
||||
SciterTraverseUIEvent: *const fn (evt: u32, eventCtlStruct: ?*anyopaque, bOutProcessed: *Bool) callconv(.c) DomResult,
|
||||
SciterCallScriptingMethod: *const fn (he: HElement, name: CStr, argv: [*]const Value, argc: u32, retval: *Value) callconv(.c) DomResult,
|
||||
SciterCallScriptingFunction: *const fn (he: HElement, name: CStr, argv: [*]const Value, argc: u32, retval: *Value) callconv(.c) DomResult,
|
||||
SciterEvalElementScript: *const fn (he: HElement, script: [*]const u16, scriptLength: u32, retval: *Value) callconv(.c) DomResult,
|
||||
SciterAttachHwndToElement: *const fn (he: HElement, hwnd: HWindow) callconv(.c) DomResult,
|
||||
SciterControlGetType: *const fn (he: HElement, pType: *CtlType) callconv(.c) DomResult,
|
||||
SciterGetValue: *const fn (he: HElement, pval: *Value) callconv(.c) DomResult,
|
||||
SciterSetValue: *const fn (he: HElement, pval: *const Value) callconv(.c) DomResult,
|
||||
SciterGetExpando: *const fn (he: HElement, pval: *Value, forceCreation: Bool) callconv(.c) DomResult,
|
||||
SciterGetObject: *const fn (he: HElement, pval: *void, forceCreation: Bool) callconv(.c) DomResult,
|
||||
SciterGetElementNamespace: *const fn (he: HElement, pval: *void) callconv(.c) DomResult,
|
||||
SciterGetHighlightedElement: *const fn (hwnd: HWindow, phe: *HElement) callconv(.c) DomResult,
|
||||
SciterSetHighlightedElement: *const fn (hwnd: HWindow, he: HElement) callconv(.c) DomResult,
|
||||
|
||||
// --- DOM NODE API --------------------------------------------------------
|
||||
|
||||
SciterNodeAddRef: *const fn (hn: HNode) callconv(.c) DomResult,
|
||||
SciterNodeRelease: *const fn (hn: HNode) callconv(.c) DomResult,
|
||||
SciterNodeCastFromElement: *const fn (he: HElement, phn: *HNode) callconv(.c) DomResult,
|
||||
SciterNodeCastToElement: *const fn (hn: HNode, he: *HElement) callconv(.c) DomResult,
|
||||
SciterNodeFirstChild: *const fn (hn: HNode, phn: *HNode) callconv(.c) DomResult,
|
||||
SciterNodeLastChild: *const fn (hn: HNode, phn: *HNode) callconv(.c) DomResult,
|
||||
SciterNodeNextSibling: *const fn (hn: HNode, phn: *HNode) callconv(.c) DomResult,
|
||||
SciterNodePrevSibling: *const fn (hn: HNode, phn: *HNode) callconv(.c) DomResult,
|
||||
SciterNodeParent: *const fn (hnode: HNode, pheParent: *HElement) callconv(.c) DomResult,
|
||||
SciterNodeNthChild: *const fn (hnode: HNode, n: u32, phn: *HNode) callconv(.c) DomResult,
|
||||
SciterNodeChildrenCount: *const fn (hnode: HNode, pn: *u32) callconv(.c) DomResult,
|
||||
SciterNodeType: *const fn (hnode: HNode, pNodeType: *NodeType) callconv(.c) DomResult,
|
||||
SciterNodeGetText: *const fn (hnode: HNode, rcv: *const WStrReceiver, rcv_param: ?*anyopaque) callconv(.c) DomResult,
|
||||
SciterNodeSetText: *const fn (hnode: HNode, text: [*]const u16, textLength: u32) callconv(.c) DomResult,
|
||||
SciterNodeInsert: *const fn (hnode: HNode, where: NodeInsTarget, what: HNode) callconv(.c) DomResult,
|
||||
SciterNodeRemove: *const fn (hnode: HNode, finalize: Bool) callconv(.c) DomResult,
|
||||
SciterCreateTextNode: *const fn (text: [*]const u16, textLength: u32, phnode: *HNode) callconv(.c) DomResult,
|
||||
SciterCreateCommentNode: *const fn (text: [*]const u16, textLength: u32, phnode: *HNode) callconv(.c) DomResult,
|
||||
|
||||
// --- Value API -----------------------------------------------------------
|
||||
|
||||
ValueInit: *const fn (pval: *Value) callconv(.c) u32,
|
||||
ValueClear: *const fn (pval: *Value) callconv(.c) u32,
|
||||
ValueCompare: *const fn (pval1: *const Value, pval2: *const Value) callconv(.c) u32,
|
||||
ValueCopy: *const fn (pdst: *Value, psrc: *const Value) callconv(.c) u32,
|
||||
ValueIsolate: *const fn (pdst: *Value) callconv(.c) u32,
|
||||
ValueType: *const fn (pval: *const Value, pType: *u32, pUnits: *u32) callconv(.c) u32,
|
||||
ValueStringData: *const fn (pval: *const Value, pChars: *[*]const u16, pNumChars: *u32) callconv(.c) u32,
|
||||
ValueStringDataSet: *const fn (pval: *Value, chars: [*]const u16, numChars: u32, units: u32) callconv(.c) u32,
|
||||
ValueIntData: *const fn (pval: *const Value, pData: *i32) callconv(.c) u32,
|
||||
ValueIntDataSet: *const fn (pval: *Value, data: i32, type: u32, units: u32) callconv(.c) u32,
|
||||
ValueInt64Data: *const fn (pval: *const Value, pData: *i64) callconv(.c) u32,
|
||||
ValueInt64DataSet: *const fn (pval: *Value, data: i64, type: u32, units: u32) callconv(.c) u32,
|
||||
ValueFloatData: *const fn (pval: *const Value, pData: *f64) callconv(.c) u32,
|
||||
ValueFloatDataSet: *const fn (pval: *Value, data: f64, type: u32, units: u32) callconv(.c) u32,
|
||||
ValueBinaryData: *const fn (pval: *const Value, pBytes: *[*]const u8, pnBytes: *u32) callconv(.c) u32,
|
||||
ValueBinaryDataSet: *const fn (pval: *Value, pBytes: [*]const u8, nBytes: u32, type: u32, units: u32) callconv(.c) u32,
|
||||
ValueElementsCount: *const fn (pval: *const Value, pn: *i32) callconv(.c) u32,
|
||||
ValueNthElementValue: *const fn (pval: *const Value, n: i32, pretval: *Value) callconv(.c) u32,
|
||||
ValueNthElementValueSet: *const fn (pval: *Value, n: i32, pval_to_set: *const Value) callconv(.c) u32,
|
||||
ValueNthElementKey: *const fn (pval: *const Value, n: i32, pretval: *Value) callconv(.c) u32,
|
||||
ValueEnumElements: *const fn (pval: *const Value, penum: *const KeyValueCallback, param: ?*anyopaque) callconv(.c) u32,
|
||||
ValueSetValueToKey: *const fn (pval: *Value, pkey: *const Value, pval_to_set: *const Value) callconv(.c) u32,
|
||||
ValueGetValueOfKey: *const fn (pval: *const Value, pkey: *const Value, pretval: *Value) callconv(.c) u32,
|
||||
ValueToString: *const fn (pval: *Value, how: ValueStringCvtType) callconv(.c) u32,
|
||||
ValueFromString: *const fn (pval: *Value, str: [*]const u16, strLength: u32, how: ValueStringCvtType) callconv(.c) u32,
|
||||
ValueInvoke: *const fn (pval: *const Value, pthis: *Value, argc: u32, argv: *const Value, pretval: *Value, url: CWStr) callconv(.c) u32,
|
||||
ValueNativeFunctorSet: *const fn (pval: *Value, pinvoke: *const NativeFunctorInvoke, prelease: *const NativeFunctorRelease, tag: *void) callconv(.c) u32,
|
||||
ValueIsNativeFunctor: *const fn (pval: *const Value) callconv(.c) Bool,
|
||||
|
||||
// Used to be script VM API
|
||||
|
||||
reserved1: *anyopaque,
|
||||
reserved2: *anyopaque,
|
||||
reserved3: *anyopaque,
|
||||
reserved4: *anyopaque,
|
||||
|
||||
SciterOpenArchive: *const fn (archiveData: [*]const u8, archiveDataLength: u32) callconv(.c) HSArchive,
|
||||
SciterGetArchiveItem: *const fn (harc: HSArchive, path: CWStr, pdata: *[*]const u8, pdataLength: *u32) callconv(.c) Bool,
|
||||
SciterCloseArchive: *const fn (harc: HSArchive) callconv(.c) Bool,
|
||||
|
||||
SciterFireEvent: *const fn (evt: *const BehaviorEventParams, post: Bool, handled: *Bool) callconv(.c) DomResult,
|
||||
|
||||
SciterGetCallbackParam: *const fn (hwnd: HWindow) callconv(.c) ?*anyopaque,
|
||||
SciterPostCallback: *const fn (hwnd: HWindow, wparam: isize, lparam: usize, timeoutms: u32) callconv(.c) isize,
|
||||
|
||||
GetSciterGraphicsAPI: *const fn () callconv(.c) *GraphicsAPI,
|
||||
GetSciterRequestAPI: *const fn () callconv(.c) *RequestAPI,
|
||||
|
||||
/// IDXGISwapChain
|
||||
SciterCreateOnDirectXWindow: *const fn (hwnd: HWindow, pSwapChain: *anyopaque) callconv(.c) Bool,
|
||||
SciterRenderOnDirectXWindow: *const fn (hwnd: HWindow, elementToRenderOrNull: HElement, frontLayer: Bool) callconv(.c) Bool,
|
||||
/// IDXGISurface
|
||||
SciterRenderOnDirectXTexture: *const fn (hwnd: HWindow, elementToRenderOrNull: HElement, surface: *anyopaque) callconv(.c) Bool,
|
||||
|
||||
SciterProcX: *const fn (hwnd: HWindow, pMsg: *XMsg) callconv(.c) Bool,
|
||||
|
||||
SciterAtomValue: *const fn (name: CStr) callconv(.c) u64,
|
||||
SciterAtomNameCB: *const fn (atomv: u64, rcv: *const StrReceiver, rcv_param: ?*anyopaque) callconv(.c) Bool,
|
||||
|
||||
SciterSetGlobalAsset: *const fn (pass: *OmAsset) callconv(.c) Bool,
|
||||
SciterGetElementAsset: *const fn (el: HElement, nameAtom: u64, ppass: *?*OmAsset) callconv(.c) DomResult,
|
||||
|
||||
SciterSetVariable: *const fn (hwndOrNull: HWindow, name: CStr, pvalToSet: *const Value) callconv(.c) u32,
|
||||
SciterGetVariable: *const fn (hwndOrNull: HWindow, name: CStr, pvalToGet: *Value) callconv(.c) u32,
|
||||
|
||||
SciterElementUnwrap: *const fn (pval: *const Value, ppElement: *HElement) callconv(.c) u32,
|
||||
SciterElementWrap: *const fn (pval: *Value, pElement: HElement) callconv(.c) u32,
|
||||
|
||||
SciterNodeUnwrap: *const fn (pval: *const Value, ppNode: *HNode) callconv(.c) u32,
|
||||
SciterNodeWrap: *const fn (pval: *Value, pNode: HNode) callconv(.c) u32,
|
||||
|
||||
SciterReleaseGlobalAsset: *const fn (pass: *OmAsset) callconv(.c) Bool,
|
||||
|
||||
SciterExec: *const fn (appCmd: u32, p1: usize, p2: usize) callconv(.c) isize,
|
||||
SciterWindowExec: *const fn (hwnd: HWindow, windowCmd: u32, p1: usize, p2: usize) callconv(.c) isize,
|
||||
|
||||
SciterEGLGetProcAddress: *const fn (procName: CStr) callconv(.c) ?*anyopaque,
|
||||
SciterEGLSendEvent: *const fn (he: HElement, eventCode: u32, reason: usize) callconv(.c) DomResult,
|
||||
SciterRequestAnimationFrameEvent: *const fn (he: HElement, eventCode: u32, reason: usize) callconv(.c) DomResult,
|
||||
};
|
||||
|
||||
// TODO These APIs are actually well-known structs with function pointers in
|
||||
// them, which may provide useful functionality
|
||||
|
||||
pub const GraphicsAPI = opaque {};
|
||||
pub const RequestAPI = opaque {};
|
||||
BIN
packages/sciter/vendor/sciter.dll
(Stored with Git LFS)
vendored
BIN
packages/sciter/vendor/sciter.dll
(Stored with Git LFS)
vendored
Binary file not shown.
@@ -1,7 +1,19 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
_ = b.addModule("vecmath", .{
|
||||
const target = b.standardTargetOptions(.{});
|
||||
|
||||
const mod = b.addModule("vecmath", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.{
|
||||
.name = .vecmath,
|
||||
.version = "0.0.0",
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.minimum_zig_version = "0.16.0",
|
||||
.paths = .{
|
||||
"src",
|
||||
"build.zig",
|
||||
|
||||
@@ -63,33 +63,41 @@ pub const Color = extern struct {
|
||||
@compileError("Invalid color literal: " ++ literal);
|
||||
}
|
||||
|
||||
pub inline fn asArray(self: Color) Array {
|
||||
return @bitCast(self);
|
||||
}
|
||||
};
|
||||
|
||||
test "l" {
|
||||
const i: Color = .l("#012");
|
||||
test l {
|
||||
const i = Color.l("#012");
|
||||
try std.testing.expectEqual(0x00, i.r);
|
||||
try std.testing.expectEqual(0x11, i.g);
|
||||
try std.testing.expectEqual(0x22, i.b);
|
||||
try std.testing.expectEqual(0xFF, i.a);
|
||||
|
||||
const j: Color = .l("#3456");
|
||||
const j = Color.l("#3456");
|
||||
try std.testing.expectEqual(0x33, j.r);
|
||||
try std.testing.expectEqual(0x44, j.g);
|
||||
try std.testing.expectEqual(0x55, j.b);
|
||||
try std.testing.expectEqual(0x66, j.a);
|
||||
|
||||
const k: Color = .l("#F08040");
|
||||
const k = Color.l("#F08040");
|
||||
try std.testing.expectEqual(0xF0, k.r);
|
||||
try std.testing.expectEqual(0x80, k.g);
|
||||
try std.testing.expectEqual(0x40, k.b);
|
||||
try std.testing.expectEqual(0xFF, k.a);
|
||||
|
||||
const l: Color = .l("#20304050");
|
||||
try std.testing.expectEqual(0x20, l.r);
|
||||
try std.testing.expectEqual(0x30, l.g);
|
||||
try std.testing.expectEqual(0x40, l.b);
|
||||
try std.testing.expectEqual(0x50, l.a);
|
||||
}
|
||||
const m = Color.l("#20304050");
|
||||
try std.testing.expectEqual(0x20, m.r);
|
||||
try std.testing.expectEqual(0x30, m.g);
|
||||
try std.testing.expectEqual(0x40, m.b);
|
||||
try std.testing.expectEqual(0x50, m.a);
|
||||
}
|
||||
|
||||
pub inline fn asArray(self: Color) Array {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
pub fn format(self: Color, w: *std.Io.Writer) !void {
|
||||
try w.print("#{X:0>2}{X:0>2}{X:0>2}{X:0>2}", .{ self.r, self.g, self.b, self.a });
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
34
packages/vecmath/src/colors/ColorHdr.zig
Normal file
34
packages/vecmath/src/colors/ColorHdr.zig
Normal file
@@ -0,0 +1,34 @@
|
||||
const std = @import("std");
|
||||
const vm = @import("../root.zig");
|
||||
|
||||
pub const ColorHdr = extern struct {
|
||||
r: f16,
|
||||
g: f16,
|
||||
b: f16,
|
||||
a: f16,
|
||||
|
||||
pub const Array = [4]f16;
|
||||
|
||||
pub const zero = init(0, 0, 0, 0);
|
||||
pub const one = init(1, 1, 1, 1);
|
||||
|
||||
pub inline fn init(r: f16, g: f16, b: f16, a: f16) ColorHdr {
|
||||
return .{ .r = r, .g = g, .b = b, .a = a };
|
||||
}
|
||||
|
||||
pub inline fn initArray(array: Array) ColorHdr {
|
||||
return @bitCast(array);
|
||||
}
|
||||
|
||||
pub inline fn asArray(self: ColorHdr) Array {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
pub fn format(self: ColorHdr, w: *std.Io.Writer) !void {
|
||||
try w.print("ColorHdr[{d}, {d}, {d}, {d}]", .{ self.r, self.g, self.b, self.a });
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
@@ -176,40 +176,6 @@ pub const Matrix3x2 = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
// --- TRANSFORM -----------------------------------------------------------
|
||||
|
||||
// TODO Move to vm.Vector2
|
||||
pub inline fn transformPoint(self: Matrix3x2, p: vm.Vector2) vm.Vector2 {
|
||||
return .{
|
||||
.x = p.x * self.ix + p.y * self.jx + self.tx,
|
||||
.y = p.x * self.iy + p.y * self.jy + self.ty,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector2x8
|
||||
pub inline fn transformPoint_x8(self: Matrix3x2, p: vm.Vector2x8) vm.Vector2x8 {
|
||||
return .{
|
||||
.x = p.x * vm.ps(self.ix) + p.y * vm.ps(self.jx) + vm.ps(self.tx),
|
||||
.y = p.x * vm.ps(self.iy) + p.y * vm.ps(self.jy) + vm.ps(self.ty),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector2
|
||||
pub inline fn transformVector(self: Matrix3x2, v: vm.Vector2) vm.Vector2 {
|
||||
return .{
|
||||
.x = v.x * self.ix + v.y * self.jx,
|
||||
.y = v.x * self.iy + v.y * self.jy,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector2x8
|
||||
pub inline fn transformVector_x8(self: Matrix3x2, v: vm.Vector2x8) vm.Vector2x8 {
|
||||
return .{
|
||||
.x = v.x * vm.ps(self.ix) + v.y * vm.ps(self.jx),
|
||||
.y = v.x * vm.ps(self.iy) + v.y * vm.ps(self.jy),
|
||||
};
|
||||
}
|
||||
|
||||
// --- COMPOSE -------------------------------------------------------------
|
||||
|
||||
pub inline fn mulMatrix(self: Matrix3x2, other: Matrix3x2) Matrix3x2 {
|
||||
@@ -223,15 +189,14 @@ pub const Matrix3x2 = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn mulMatrix_x8(self: Matrix3x2, other: vm.Matrix3x2x8) vm.Matrix3x2 {
|
||||
return .{
|
||||
.ix = other.ix * vm.ps(self.ix) + other.iy * vm.ps(self.jx),
|
||||
.iy = other.ix * vm.ps(self.iy) + other.iy * vm.ps(self.jy),
|
||||
.jx = other.jx * vm.ps(self.ix) + other.jy * vm.ps(self.jx),
|
||||
.jy = other.jx * vm.ps(self.iy) + other.jy * vm.ps(self.jy),
|
||||
.tx = other.tx * vm.ps(self.ix) + other.ty * vm.ps(self.jx) + vm.ps(self.tx),
|
||||
.ty = other.tx * vm.ps(self.iy) + other.ty * vm.ps(self.jy) + vm.ps(self.ty),
|
||||
};
|
||||
test mulMatrix {
|
||||
try std.testing.expectEqual(
|
||||
init(121, 438, 131, 474, 162, 581),
|
||||
mulMatrix(
|
||||
init(2, 7, 3, 11, 5, 13),
|
||||
init(17, 29, 19, 31, 23, 37),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// INVERSION DERIVATION
|
||||
@@ -287,4 +252,8 @@ pub const Matrix3x2 = extern struct {
|
||||
.ty = -inv_det * (self.tx * iy + self.ty * jy),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -340,40 +340,6 @@ pub const Matrix3x2x8 = struct {
|
||||
};
|
||||
}
|
||||
|
||||
// --- TRANSFORM -----------------------------------------------------------
|
||||
|
||||
// TODO Move to vm.Vector2x8
|
||||
pub inline fn transformPoint(self: Matrix3x2x8, p: vm.Vector2x8) vm.Vector2x8 {
|
||||
return .{
|
||||
.x = p.x * self.ix + p.y * self.jx + self.tx,
|
||||
.y = p.x * self.iy + p.y * self.jy + self.ty,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector2x8
|
||||
pub inline fn transformPointSingle(self: Matrix3x2x8, p: vm.Vector2) vm.Vector2x8 {
|
||||
return .{
|
||||
.x = vm.ps(p.x) * self.ix + vm.ps(p.y) * self.jx + self.tx,
|
||||
.y = vm.ps(p.x) * self.iy + vm.ps(p.y) * self.jy + self.ty,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector2x8
|
||||
pub inline fn transformVector(self: Matrix3x2x8, v: vm.Vector2x8) vm.Vector2x8 {
|
||||
return .{
|
||||
.x = v.x * self.ix + v.y * self.jx,
|
||||
.y = v.x * self.iy + v.y * self.jy,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector2x8
|
||||
pub inline fn transformVectorSingle(self: Matrix3x2x8, v: vm.Vector2) vm.Vector2x8 {
|
||||
return .{
|
||||
.x = vm.ps(v.x) * self.ix + vm.ps(v.y) * self.jx,
|
||||
.y = vm.ps(v.x) * self.iy + vm.ps(v.y) * self.jy,
|
||||
};
|
||||
}
|
||||
|
||||
// --- COMPOSE -------------------------------------------------------------
|
||||
|
||||
pub inline fn mulMatrix(self: Matrix3x2x8, other: Matrix3x2x8) Matrix3x2x8 {
|
||||
@@ -440,4 +406,8 @@ pub const Matrix3x2x8 = struct {
|
||||
.ty = -inv_det * (self.tx * iy + self.ty * jy),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -75,14 +75,28 @@ pub const Matrix4x4 = extern struct {
|
||||
|
||||
return .{
|
||||
// zig fmt: off
|
||||
.ix = 1 - 2 * (yy + zz), .iy = 2 * (xy - zw), .iz = 2 * (xz + yw), .iw = 0,
|
||||
.jx = 2 * (xy + zw), .jy = 1 - 2 * (xx + zz), .jz = 2 * (yz - xw), .jw = 0,
|
||||
.kx = 2 * (xz - yw), .ky = 2 * (yz + xw), .kz = 1 - 2 * (xx + yy), .kw = 0,
|
||||
.ix = 1 - 2 * (yy + zz), .iy = 2 * (xy + zw), .iz = 2 * (xz - yw), .iw = 0,
|
||||
.jx = 2 * (xy - zw), .jy = 1 - 2 * (xx + zz), .jz = 2 * (yz + xw), .jw = 0,
|
||||
.kx = 2 * (xz + yw), .ky = 2 * (yz - xw), .kz = 1 - 2 * (xx + yy), .kw = 0,
|
||||
.tx = 0, .ty = 0, .tz = 0, .tw = 1,
|
||||
// zig fmt: on
|
||||
};
|
||||
}
|
||||
|
||||
test initRotation {
|
||||
const q = vm.Quaternion.initRotation(.XY, 0.5);
|
||||
const m = initRotation(q);
|
||||
|
||||
try std.testing.expectEqual(init(
|
||||
// zig fmt: off
|
||||
-1, 0, 0, 0,
|
||||
0, -1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1,
|
||||
// zig fmt: on
|
||||
), m);
|
||||
}
|
||||
|
||||
pub inline fn initScale(s: vm.Vector3) Matrix4x4 {
|
||||
return .{
|
||||
// zig fmt: off
|
||||
@@ -107,9 +121,9 @@ pub const Matrix4x4 = extern struct {
|
||||
|
||||
return .{
|
||||
// zig fmt: off
|
||||
.ix = 1 - 2 * (yy + zz), .iy = 2 * (xy - zw), .iz = 2 * (xz + yw), .iw = 0,
|
||||
.jx = 2 * (xy + zw), .jy = 1 - 2 * (xx + zz), .jz = 2 * (yz - xw), .jw = 0,
|
||||
.kx = 2 * (xz - yw), .ky = 2 * (yz + xw), .kz = 1 - 2 * (xx + yy), .kw = 0,
|
||||
.ix = 1 - 2 * (yy + zz), .iy = 2 * (xy + zw), .iz = 2 * (xz - yw), .iw = 0,
|
||||
.jx = 2 * (xy - zw), .jy = 1 - 2 * (xx + zz), .jz = 2 * (yz + xw), .jw = 0,
|
||||
.kx = 2 * (xz + yw), .ky = 2 * (yz - xw), .kz = 1 - 2 * (xx + yy), .kw = 0,
|
||||
.tx = t.x, .ty = t.y, .tz = t.z, .tw = 1,
|
||||
// zig fmt: on
|
||||
};
|
||||
@@ -139,9 +153,9 @@ pub const Matrix4x4 = extern struct {
|
||||
|
||||
return .{
|
||||
// zig fmt: off
|
||||
.ix = s.x * (1 - 2 * (yy + zz)), .iy = s.x * 2 * (xy - zw), .iz = s.x * 2 * (xz + yw), .iw = 0,
|
||||
.jx = s.y * 2 * (xy + zw), .jy = s.y * (1 - 2 * (xx + zz)), .jz = s.y * 2 * (yz - xw), .jw = 0,
|
||||
.kx = s.z * 2 * (xz - yw), .ky = s.z * 2 * (yz + xw), .kz = s.z * (1 - 2 * (xx + yy)), .kw = 0,
|
||||
.ix = s.x * (1 - 2 * (yy + zz)), .iy = s.x * 2 * (xy + zw), .iz = s.x * 2 * (xz - yw), .iw = 0,
|
||||
.jx = s.y * 2 * (xy - zw), .jy = s.y * (1 - 2 * (xx + zz)), .jz = s.y * 2 * (yz + xw), .jw = 0,
|
||||
.kx = s.z * 2 * (xz + yw), .ky = s.z * 2 * (yz - xw), .kz = s.z * (1 - 2 * (xx + yy)), .kw = 0,
|
||||
.tx = t.x, .ty = t.y, .tz = t.z, .tw = 1,
|
||||
// zig fmt: on
|
||||
};
|
||||
@@ -261,64 +275,6 @@ pub const Matrix4x4 = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
// --- TRANSFORM -----------------------------------------------------------
|
||||
|
||||
// TODO Move to vm.Vector3
|
||||
pub inline fn transformPoint(self: Matrix4x4, p: vm.Vector3) vm.Vector3 {
|
||||
return .{
|
||||
.x = p.x * self.ix + p.y * self.jx + p.z * self.kx + self.tx,
|
||||
.y = p.x * self.iy + p.y * self.jy + p.z * self.ky + self.ty,
|
||||
.z = p.x * self.iz + p.y * self.jz + p.z * self.kz + self.tz,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector3x8
|
||||
pub inline fn transformPoint_x8(self: Matrix4x4, p: vm.Vector3x8) vm.Vector3x8 {
|
||||
return .{
|
||||
.x = p.x * vm.ps(self.ix) + p.y * vm.ps(self.jx) + p.z * vm.ps(self.kx) + vm.ps(self.tx),
|
||||
.y = p.x * vm.ps(self.iy) + p.y * vm.ps(self.jy) + p.z * vm.ps(self.ky) + vm.ps(self.ty),
|
||||
.z = p.x * vm.ps(self.iz) + p.y * vm.ps(self.jz) + p.z * vm.ps(self.kz) + vm.ps(self.tz),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector3
|
||||
pub inline fn transformVector(self: Matrix4x4, v: vm.Vector3) vm.Vector3 {
|
||||
return .{
|
||||
.x = v.x * self.ix + v.y * self.jx + v.z * self.kx,
|
||||
.y = v.x * self.iy + v.y * self.jy + v.z * self.ky,
|
||||
.z = v.x * self.iz + v.y * self.jz + v.z * self.kz,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector3x8
|
||||
pub inline fn transformVector_x8(self: Matrix4x4, v: vm.Vector3x8) vm.Vector3x8 {
|
||||
return .{
|
||||
.x = v.x * vm.ps(self.ix) + v.y * vm.ps(self.jx) + v.z * vm.ps(self.kx),
|
||||
.y = v.x * vm.ps(self.iy) + v.y * vm.ps(self.jy) + v.z * vm.ps(self.ky),
|
||||
.z = v.x * vm.ps(self.iz) + v.y * vm.ps(self.jz) + v.z * vm.ps(self.kz),
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector4
|
||||
pub inline fn transformHomogeneous(self: Matrix4x4, h: vm.Vector4) vm.Vector4 {
|
||||
return .{
|
||||
.x = h.x * self.ix + h.y * self.jx + h.z * self.kx + h.w * self.tx,
|
||||
.y = h.x * self.iy + h.y * self.jy + h.z * self.ky + h.w * self.ty,
|
||||
.z = h.x * self.iz + h.y * self.jz + h.z * self.kz + h.w * self.tz,
|
||||
.w = h.x * self.iw + h.y * self.jw + h.z * self.kw + h.w * self.tw,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector4x8
|
||||
pub inline fn transformHomogeneous_x8(self: Matrix4x4, h: vm.Vector4x8) vm.Vector4x8 {
|
||||
return .{
|
||||
.x = h.x * vm.ps(self.ix) + h.y * vm.ps(self.jx) + h.z * vm.ps(self.kx) + h.w * vm.ps(self.tx),
|
||||
.y = h.x * vm.ps(self.iy) + h.y * vm.ps(self.jy) + h.z * vm.ps(self.ky) + h.w * vm.ps(self.ty),
|
||||
.z = h.x * vm.ps(self.iz) + h.y * vm.ps(self.jz) + h.z * vm.ps(self.kz) + h.w * vm.ps(self.tz),
|
||||
.w = h.x * vm.ps(self.iw) + h.y * vm.ps(self.jw) + h.z * vm.ps(self.kw) + h.w * vm.ps(self.tw),
|
||||
};
|
||||
}
|
||||
|
||||
// --- COMPOSE -------------------------------------------------------------
|
||||
|
||||
/// The caller asserts that W rows of all matrices are equal to [0 0 0 1].
|
||||
@@ -338,38 +294,13 @@ pub const Matrix4x4 = extern struct {
|
||||
.ky = other.kx * self.iy + other.ky * self.jy + other.kz * self.ky,
|
||||
.kz = other.kx * self.iz + other.ky * self.jz + other.kz * self.kz,
|
||||
.kw = 0,
|
||||
.tx = other.tx * self.tx + other.ty * self.jx + other.tz * self.kx + self.tx,
|
||||
.ty = other.tx * self.ty + other.ty * self.jy + other.tz * self.ky + self.ty,
|
||||
.tz = other.tx * self.tz + other.ty * self.jz + other.tz * self.kz + self.tz,
|
||||
.tx = other.tx * self.ix + other.ty * self.jx + other.tz * self.kx + self.tx,
|
||||
.ty = other.tx * self.iy + other.ty * self.jy + other.tz * self.ky + self.ty,
|
||||
.tz = other.tx * self.iz + other.ty * self.jz + other.tz * self.kz + self.tz,
|
||||
.tw = 1,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Matrix4x4x8
|
||||
/// The caller asserts that W rows of all matrices are equal to [0 0 0 1].
|
||||
pub fn mulMatrixAffine_x8(self: Matrix4x4, other: vm.Matrix4x4x8) vm.Matrix4x4x8 {
|
||||
std.debug.assert(self.iw == 0 and self.jw == 0 and self.kw == 0 and self.tw == 1);
|
||||
std.debug.assert(@reduce(.And, (other.iw == vm.ps(0)) & (other.jw == vm.ps(0)) & (other.kw == vm.ps(0)) & (other.tw == vm.ps(1))));
|
||||
return .{
|
||||
.ix = other.ix * vm.ps(self.ix) + other.iy * vm.ps(self.jx) + other.iz * vm.ps(self.kx),
|
||||
.iy = other.ix * vm.ps(self.iy) + other.iy * vm.ps(self.jy) + other.iz * vm.ps(self.ky),
|
||||
.iz = other.ix * vm.ps(self.iz) + other.iy * vm.ps(self.jz) + other.iz * vm.ps(self.kz),
|
||||
.iw = vm.ps(0),
|
||||
.jx = other.jx * vm.ps(self.ix) + other.jy * vm.ps(self.jx) + other.jz * vm.ps(self.kx),
|
||||
.jy = other.jx * vm.ps(self.iy) + other.jy * vm.ps(self.jy) + other.jz * vm.ps(self.ky),
|
||||
.jz = other.jx * vm.ps(self.iz) + other.jy * vm.ps(self.jz) + other.jz * vm.ps(self.kz),
|
||||
.jw = vm.ps(0),
|
||||
.kx = other.kx * vm.ps(self.ix) + other.ky * vm.ps(self.jx) + other.kz * vm.ps(self.kx),
|
||||
.ky = other.kx * vm.ps(self.iy) + other.ky * vm.ps(self.jy) + other.kz * vm.ps(self.ky),
|
||||
.kz = other.kx * vm.ps(self.iz) + other.ky * vm.ps(self.jz) + other.kz * vm.ps(self.kz),
|
||||
.kw = vm.ps(0),
|
||||
.tx = other.tx * vm.ps(self.tx) + other.ty * vm.ps(self.jx) + other.tz * vm.ps(self.kx) + vm.ps(self.tx),
|
||||
.ty = other.tx * vm.ps(self.ty) + other.ty * vm.ps(self.jy) + other.tz * vm.ps(self.ky) + vm.ps(self.ty),
|
||||
.tz = other.tx * vm.ps(self.tz) + other.ty * vm.ps(self.jz) + other.tz * vm.ps(self.kz) + vm.ps(self.tz),
|
||||
.tw = vm.ps(1),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn mulMatrixFull(self: Matrix4x4, other: Matrix4x4) Matrix4x4 {
|
||||
return .{
|
||||
.ix = other.ix * self.ix + other.iy * self.jx + other.iz * self.kx + other.iw * self.tx,
|
||||
@@ -391,28 +322,6 @@ pub const Matrix4x4 = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Matrix4x4x8
|
||||
pub fn mulMatrixFull_x8(self: Matrix4x4, other: vm.Matrix4x4x8) vm.Matrix4x4x8 {
|
||||
return .{
|
||||
.ix = other.ix * vm.ps(self.ix) + other.iy * vm.ps(self.jx) + other.iz * vm.ps(self.kx) + other.iw * vm.ps(self.tx),
|
||||
.iy = other.ix * vm.ps(self.iy) + other.iy * vm.ps(self.jy) + other.iz * vm.ps(self.ky) + other.iw * vm.ps(self.ty),
|
||||
.iz = other.ix * vm.ps(self.iz) + other.iy * vm.ps(self.jz) + other.iz * vm.ps(self.kz) + other.iw * vm.ps(self.tz),
|
||||
.iw = other.ix * vm.ps(self.iw) + other.iy * vm.ps(self.jw) + other.iz * vm.ps(self.kw) + other.iw * vm.ps(self.tw),
|
||||
.jx = other.jx * vm.ps(self.ix) + other.jy * vm.ps(self.jx) + other.jz * vm.ps(self.kx) + other.jw * vm.ps(self.tx),
|
||||
.jy = other.jx * vm.ps(self.iy) + other.jy * vm.ps(self.jy) + other.jz * vm.ps(self.ky) + other.jw * vm.ps(self.ty),
|
||||
.jz = other.jx * vm.ps(self.iz) + other.jy * vm.ps(self.jz) + other.jz * vm.ps(self.kz) + other.jw * vm.ps(self.tz),
|
||||
.jw = other.jx * vm.ps(self.iw) + other.jy * vm.ps(self.jw) + other.jz * vm.ps(self.kw) + other.jw * vm.ps(self.tw),
|
||||
.kx = other.kx * vm.ps(self.ix) + other.ky * vm.ps(self.jx) + other.kz * vm.ps(self.kx) + other.kw * vm.ps(self.tx),
|
||||
.ky = other.kx * vm.ps(self.iy) + other.ky * vm.ps(self.jy) + other.kz * vm.ps(self.ky) + other.kw * vm.ps(self.ty),
|
||||
.kz = other.kx * vm.ps(self.iz) + other.ky * vm.ps(self.jz) + other.kz * vm.ps(self.kz) + other.kw * vm.ps(self.tz),
|
||||
.kw = other.kx * vm.ps(self.iw) + other.ky * vm.ps(self.jw) + other.kz * vm.ps(self.kw) + other.kw * vm.ps(self.tw),
|
||||
.tx = other.tx * vm.ps(self.ix) + other.ty * vm.ps(self.jx) + other.tz * vm.ps(self.kx) + other.tw * vm.ps(self.tx),
|
||||
.ty = other.tx * vm.ps(self.iy) + other.ty * vm.ps(self.jy) + other.tz * vm.ps(self.ky) + other.tw * vm.ps(self.ty),
|
||||
.tz = other.tx * vm.ps(self.iz) + other.ty * vm.ps(self.jz) + other.tz * vm.ps(self.kz) + other.tw * vm.ps(self.tz),
|
||||
.tw = other.tx * vm.ps(self.iw) + other.ty * vm.ps(self.jw) + other.tz * vm.ps(self.kw) + other.tw * vm.ps(self.tw),
|
||||
};
|
||||
}
|
||||
|
||||
// INVERSION DERIVATION (affine and orthonormal case)
|
||||
//
|
||||
// We assume the matrix looks like so:
|
||||
@@ -598,4 +507,8 @@ pub const Matrix4x4 = extern struct {
|
||||
// zig fmt: on
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,9 +113,9 @@ pub const Matrix4x4x8 = extern struct {
|
||||
|
||||
return .{
|
||||
// zig fmt: off
|
||||
.ix = vm.ps(1) - vm.ps(2) * (yy + zz), .iy = vm.ps(2) * (xy - zw), .iz = vm.ps(2) * (xz + yw), .iw = vm.ps(0),
|
||||
.jx = vm.ps(2) * (xy + zw), .jy = vm.ps(1) - vm.ps(2) * (xx + zz), .jz = vm.ps(2) * (yz - xw), .jw = vm.ps(0),
|
||||
.kx = vm.ps(2) * (xz - yw), .ky = vm.ps(2) * (yz + xw), .kz = vm.ps(1) - vm.ps(2) * (xx + yy), .kw = vm.ps(0),
|
||||
.ix = vm.ps(1) - vm.ps(2) * (yy + zz), .iy = vm.ps(2) * (xy + zw), .iz = vm.ps(2) * (xz - yw), .iw = vm.ps(0),
|
||||
.jx = vm.ps(2) * (xy - zw), .jy = vm.ps(1) - vm.ps(2) * (xx + zz), .jz = vm.ps(2) * (yz + xw), .jw = vm.ps(0),
|
||||
.kx = vm.ps(2) * (xz + yw), .ky = vm.ps(2) * (yz - xw), .kz = vm.ps(1) - vm.ps(2) * (xx + yy), .kw = vm.ps(0),
|
||||
.tx = vm.ps(0), .ty = vm.ps(0), .tz = vm.ps(0), .tw = vm.ps(1),
|
||||
// zig fmt: on
|
||||
};
|
||||
@@ -134,9 +134,9 @@ pub const Matrix4x4x8 = extern struct {
|
||||
|
||||
return .{
|
||||
// zig fmt: off
|
||||
.ix = vm.ps(1 - 2 * (yy + zz)), .iy = vm.ps(2 * (xy - zw)), .iz = vm.ps(2 * (xz + yw)), .iw = vm.ps(0),
|
||||
.jx = vm.ps(2 * (xy + zw)), .jy = vm.ps(1 - 2 * (xx + zz)), .jz = vm.ps(2 * (yz - xw)), .jw = vm.ps(0),
|
||||
.kx = vm.ps(2 * (xz - yw)), .ky = vm.ps(2 * (yz + xw)), .kz = vm.ps(1 - 2 * (xx + yy)), .kw = vm.ps(0),
|
||||
.ix = vm.ps(1 - 2 * (yy + zz)), .iy = vm.ps(2 * (xy + zw)), .iz = vm.ps(2 * (xz - yw)), .iw = vm.ps(0),
|
||||
.jx = vm.ps(2 * (xy - zw)), .jy = vm.ps(1 - 2 * (xx + zz)), .jz = vm.ps(2 * (yz + xw)), .jw = vm.ps(0),
|
||||
.kx = vm.ps(2 * (xz + yw)), .ky = vm.ps(2 * (yz - xw)), .kz = vm.ps(1 - 2 * (xx + yy)), .kw = vm.ps(0),
|
||||
.tx = vm.ps(0), .ty = vm.ps(0), .tz = vm.ps(0), .tw = vm.ps(1),
|
||||
// zig fmt: on
|
||||
};
|
||||
@@ -177,9 +177,9 @@ pub const Matrix4x4x8 = extern struct {
|
||||
|
||||
return .{
|
||||
// zig fmt: off
|
||||
.ix = vm.ps(1) - vm.ps(2) * (yy + zz), .iy = vm.ps(2) * (xy - zw), .iz = vm.ps(2) * (xz + yw), .iw = vm.ps(0),
|
||||
.jx = vm.ps(2) * (xy + zw), .jy = vm.ps(1) - vm.ps(2) * (xx + zz), .jz = vm.ps(2) * (yz - xw), .jw = vm.ps(0),
|
||||
.kx = vm.ps(2) * (xz - yw), .ky = vm.ps(2) * (yz + xw), .kz = vm.ps(1) - vm.ps(2) * (xx + yy), .kw = vm.ps(0),
|
||||
.ix = vm.ps(1) - vm.ps(2) * (yy + zz), .iy = vm.ps(2) * (xy + zw), .iz = vm.ps(2) * (xz - yw), .iw = vm.ps(0),
|
||||
.jx = vm.ps(2) * (xy - zw), .jy = vm.ps(1) - vm.ps(2) * (xx + zz), .jz = vm.ps(2) * (yz + xw), .jw = vm.ps(0),
|
||||
.kx = vm.ps(2) * (xz + yw), .ky = vm.ps(2) * (yz - xw), .kz = vm.ps(1) - vm.ps(2) * (xx + yy), .kw = vm.ps(0),
|
||||
.tx = t.x, .ty = t.y, .tz = t.z, .tw = vm.ps(1),
|
||||
// zig fmt: on
|
||||
};
|
||||
@@ -198,9 +198,9 @@ pub const Matrix4x4x8 = extern struct {
|
||||
|
||||
return .{
|
||||
// zig fmt: off
|
||||
.ix = vm.ps(1 - 2 * (yy + zz)), .iy = vm.ps(2 * (xy - zw)), .iz = vm.ps(2 * (xz + yw)), .iw = vm.ps(0),
|
||||
.jx = vm.ps(2 * (xy + zw)), .jy = vm.ps(1 - 2 * (xx + zz)), .jz = vm.ps(2 * (yz - xw)), .jw = vm.ps(0),
|
||||
.kx = vm.ps(2 * (xz - yw)), .ky = vm.ps(2 * (yz + xw)), .kz = vm.ps(1 - 2 * (xx + yy)), .kw = vm.ps(0),
|
||||
.ix = vm.ps(1 - 2 * (yy + zz)), .iy = vm.ps(2 * (xy + zw)), .iz = vm.ps(2 * (xz - yw)), .iw = vm.ps(0),
|
||||
.jx = vm.ps(2 * (xy - zw)), .jy = vm.ps(1 - 2 * (xx + zz)), .jz = vm.ps(2 * (yz + xw)), .jw = vm.ps(0),
|
||||
.kx = vm.ps(2 * (xz + yw)), .ky = vm.ps(2 * (yz - xw)), .kz = vm.ps(1 - 2 * (xx + yy)), .kw = vm.ps(0),
|
||||
.tx = vm.ps(t.x), .ty = vm.ps(t.y), .tz = vm.ps(t.z), .tw = vm.ps(1),
|
||||
// zig fmt: on
|
||||
};
|
||||
@@ -241,9 +241,9 @@ pub const Matrix4x4x8 = extern struct {
|
||||
|
||||
return .{
|
||||
// zig fmt: off
|
||||
.ix = s.x * (vm.ps(1) - vm.ps(2) * (yy + zz)), .iy = s.x * vm.ps(2) * (xy - zw), .iz = s.x * vm.ps(2) * (xz + yw), .iw = vm.ps(0),
|
||||
.jx = s.y * vm.ps(2) * (xy + zw), .jy = s.y * (vm.ps(1) - vm.ps(2) * (xx + zz)), .jz = s.y * vm.ps(2) * (yz - xw), .jw = vm.ps(0),
|
||||
.kx = s.z * vm.ps(2) * (xz - yw), .ky = s.z * vm.ps(2) * (yz + xw), .kz = s.z * (vm.ps(1) - vm.ps(2) * (xx + yy)), .kw = vm.ps(0),
|
||||
.ix = s.x * (vm.ps(1) - vm.ps(2) * (yy + zz)), .iy = s.x * vm.ps(2) * (xy + zw), .iz = s.x * vm.ps(2) * (xz - yw), .iw = vm.ps(0),
|
||||
.jx = s.y * vm.ps(2) * (xy - zw), .jy = s.y * (vm.ps(1) - vm.ps(2) * (xx + zz)), .jz = s.y * vm.ps(2) * (yz + xw), .jw = vm.ps(0),
|
||||
.kx = s.z * vm.ps(2) * (xz + yw), .ky = s.z * vm.ps(2) * (yz - xw), .kz = s.z * (vm.ps(1) - vm.ps(2) * (xx + yy)), .kw = vm.ps(0),
|
||||
.tx = t.x, .ty = t.y, .tz = t.z, .tw = vm.ps(1),
|
||||
// zig fmt: on
|
||||
};
|
||||
@@ -262,9 +262,9 @@ pub const Matrix4x4x8 = extern struct {
|
||||
|
||||
return .{
|
||||
// zig fmt: off
|
||||
.ix = vm.ps(s.x * (1 - 2 * (yy + zz))), .iy = vm.ps(s.x * 2 * (xy - zw)), .iz = vm.ps(s.x * 2 * (xz + yw)), .iw = vm.ps(0),
|
||||
.jx = vm.ps(s.y * 2 * (xy + zw)), .jy = vm.ps(s.y * (1 - 2 * (xx + zz))), .jz = vm.ps(s.y * 2 * (yz - xw)), .jw = vm.ps(0),
|
||||
.kx = vm.ps(s.z * 2 * (xz - yw)), .ky = vm.ps(s.z * 2 * (yz + xw)), .kz = vm.ps(s.z * (1 - 2 * (xx + yy))), .kw = vm.ps(0),
|
||||
.ix = vm.ps(s.x * (1 - 2 * (yy + zz))), .iy = vm.ps(s.x * 2 * (xy + zw)), .iz = vm.ps(s.x * 2 * (xz - yw)), .iw = vm.ps(0),
|
||||
.jx = vm.ps(s.y * 2 * (xy - zw)), .jy = vm.ps(s.y * (1 - 2 * (xx + zz))), .jz = vm.ps(s.y * 2 * (yz + xw)), .jw = vm.ps(0),
|
||||
.kx = vm.ps(s.z * 2 * (xz + yw)), .ky = vm.ps(s.z * 2 * (yz - xw)), .kz = vm.ps(s.z * (1 - 2 * (xx + yy))), .kw = vm.ps(0),
|
||||
.tx = vm.ps(t.x), .ty = vm.ps(t.y), .tz = vm.ps(t.z), .tw = vm.ps(1),
|
||||
// zig fmt: on
|
||||
};
|
||||
@@ -505,64 +505,6 @@ pub const Matrix4x4x8 = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
// --- TRANSFORM -----------------------------------------------------------
|
||||
|
||||
// TODO Move to vm.Vector3x8
|
||||
pub inline fn transformPoint(self: Matrix4x4x8, p: vm.Vector3x8) vm.Vector3x8 {
|
||||
return .{
|
||||
.x = p.x * self.ix + p.y * self.jx + p.z * self.kx + self.tx,
|
||||
.y = p.x * self.iy + p.y * self.jy + p.z * self.ky + self.ty,
|
||||
.z = p.x * self.iz + p.y * self.jz + p.z * self.kz + self.tz,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector3x8
|
||||
pub inline fn transformPointSingle(self: Matrix4x4x8, p: vm.Vector3) vm.Vector3x8 {
|
||||
return .{
|
||||
.x = vm.ps(p.x) * self.ix + vm.ps(p.y) * self.jx + vm.ps(p.z) * self.kx + self.tx,
|
||||
.y = vm.ps(p.x) * self.iy + vm.ps(p.y) * self.jy + vm.ps(p.z) * self.ky + self.ty,
|
||||
.z = vm.ps(p.x) * self.iz + vm.ps(p.y) * self.jz + vm.ps(p.z) * self.kz + self.tz,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector3x8
|
||||
pub inline fn transformVector(self: Matrix4x4x8, v: vm.Vector3x8) vm.Vector3x8 {
|
||||
return .{
|
||||
.x = v.x * self.ix + v.y * self.jx + v.z * self.kx,
|
||||
.y = v.x * self.iy + v.y * self.jy + v.z * self.ky,
|
||||
.z = v.x * self.iz + v.y * self.jz + v.z * self.kz,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector3x8
|
||||
pub inline fn transformVectorSingle(self: Matrix4x4x8, v: vm.Vector3) vm.Vector3x8 {
|
||||
return .{
|
||||
.x = vm.ps(v.x) * self.ix + vm.ps(v.y) * self.jx + vm.ps(v.z) * self.kx,
|
||||
.y = vm.ps(v.x) * self.iy + vm.ps(v.y) * self.jy + vm.ps(v.z) * self.ky,
|
||||
.z = vm.ps(v.x) * self.iz + vm.ps(v.y) * self.jz + vm.ps(v.z) * self.kz,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector4x8
|
||||
pub inline fn transformHomogeneous(self: Matrix4x4x8, h: vm.Vector4x8) vm.Vector4x8 {
|
||||
return .{
|
||||
.x = h.x * self.ix + h.y * self.jx + h.z * self.kx + h.w * self.tx,
|
||||
.y = h.x * self.iy + h.y * self.jy + h.z * self.ky + h.w * self.ty,
|
||||
.z = h.x * self.iz + h.y * self.jz + h.z * self.kz + h.w * self.tz,
|
||||
.w = h.x * self.iw + h.y * self.jw + h.z * self.kw + h.w * self.tw,
|
||||
};
|
||||
}
|
||||
|
||||
// TODO Move to vm.Vector4x8
|
||||
pub inline fn transformHomogeneousSingle(self: Matrix4x4x8, h: vm.Vector4) vm.Vector4x8 {
|
||||
return .{
|
||||
.x = vm.ps(h.x) * self.ix + vm.ps(h.y) * self.jx + vm.ps(h.z) * self.kx + vm.ps(h.w) * self.tx,
|
||||
.y = vm.ps(h.x) * self.iy + vm.ps(h.y) * self.jy + vm.ps(h.z) * self.ky + vm.ps(h.w) * self.ty,
|
||||
.z = vm.ps(h.x) * self.iz + vm.ps(h.y) * self.jz + vm.ps(h.z) * self.kz + vm.ps(h.w) * self.tz,
|
||||
.w = vm.ps(h.x) * self.iw + vm.ps(h.y) * self.jw + vm.ps(h.z) * self.kw + vm.ps(h.w) * self.tw,
|
||||
};
|
||||
}
|
||||
|
||||
// --- COMPOSE -------------------------------------------------------------
|
||||
|
||||
/// The caller asserts that W rows of all matrices are equal to [0 0 0 1].
|
||||
@@ -590,7 +532,7 @@ pub const Matrix4x4x8 = extern struct {
|
||||
}
|
||||
|
||||
/// The caller asserts that W rows of all matrices are equal to [0 0 0 1].
|
||||
pub fn mulMatrixAffineSingle(self: Matrix4x4x8, other: vm.Matrix4x4) vm.Matrix4x4x8 {
|
||||
pub fn mulMatrixAffineSingle(self: Matrix4x4x8, other: vm.Matrix4x4) Matrix4x4x8 {
|
||||
std.debug.assert(@reduce(.And, (self.iw == vm.ps(0)) & (self.jw == vm.ps(0)) & (self.kw == vm.ps(0)) & (self.tw == vm.ps(1))));
|
||||
std.debug.assert(other.iw == 0 and other.jw == 0 and other.kw == 0 and other.tw == 1);
|
||||
return .{
|
||||
@@ -613,6 +555,30 @@ pub const Matrix4x4x8 = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
/// The caller asserts that W rows of all matrices are equal to [0 0 0 1].
|
||||
pub fn premulMatrixAffineSingle(self: Matrix4x4x8, other: vm.Matrix4x4) Matrix4x4x8 {
|
||||
std.debug.assert(@reduce(.And, (self.iw == vm.ps(0)) & (self.jw == vm.ps(0)) & (self.kw == vm.ps(0)) & (self.tw == vm.ps(1))));
|
||||
std.debug.assert(other.iw == 0 and other.jw == 0 and other.kw == 0 and other.tw == 1);
|
||||
return .{
|
||||
.ix = self.ix * vm.ps(other.ix) + self.iy * vm.ps(other.jx) + self.iz * vm.ps(other.kx),
|
||||
.iy = self.ix * vm.ps(other.iy) + self.iy * vm.ps(other.jy) + self.iz * vm.ps(other.ky),
|
||||
.iz = self.ix * vm.ps(other.iz) + self.iy * vm.ps(other.jz) + self.iz * vm.ps(other.kz),
|
||||
.iw = vm.ps(0),
|
||||
.jx = self.jx * vm.ps(other.ix) + self.jy * vm.ps(other.jx) + self.jz * vm.ps(other.kx),
|
||||
.jy = self.jx * vm.ps(other.iy) + self.jy * vm.ps(other.jy) + self.jz * vm.ps(other.ky),
|
||||
.jz = self.jx * vm.ps(other.iz) + self.jy * vm.ps(other.jz) + self.jz * vm.ps(other.kz),
|
||||
.jw = vm.ps(0),
|
||||
.kx = self.kx * vm.ps(other.ix) + self.ky * vm.ps(other.jx) + self.kz * vm.ps(other.kx),
|
||||
.ky = self.kx * vm.ps(other.iy) + self.ky * vm.ps(other.jy) + self.kz * vm.ps(other.ky),
|
||||
.kz = self.kx * vm.ps(other.iz) + self.ky * vm.ps(other.jz) + self.kz * vm.ps(other.kz),
|
||||
.kw = vm.ps(0),
|
||||
.tx = self.tx * vm.ps(other.ix) + self.ty * vm.ps(other.jx) + self.tz * vm.ps(other.kx) + vm.ps(other.tx),
|
||||
.ty = self.tx * vm.ps(other.iy) + self.ty * vm.ps(other.jy) + self.tz * vm.ps(other.ky) + vm.ps(other.ty),
|
||||
.tz = self.tx * vm.ps(other.iz) + self.ty * vm.ps(other.jz) + self.tz * vm.ps(other.kz) + vm.ps(other.tz),
|
||||
.tw = vm.ps(1),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn mulMatrixFull(self: Matrix4x4x8, other: Matrix4x4x8) Matrix4x4x8 {
|
||||
return .{
|
||||
.ix = other.ix * self.ix + other.iy * self.jx + other.iz * self.kx + other.iw * self.tx,
|
||||
@@ -655,6 +621,27 @@ pub const Matrix4x4x8 = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn premulMatrixFull(self: Matrix4x4x8, other: vm.Matrix4x4) Matrix4x4x8 {
|
||||
return .{
|
||||
.ix = self.ix * vm.ps(other.ix) + self.iy * vm.ps(other.jx) + self.iz * vm.ps(other.kx) + self.iw * vm.ps(other.tx),
|
||||
.iy = self.ix * vm.ps(other.iy) + self.iy * vm.ps(other.jy) + self.iz * vm.ps(other.ky) + self.iw * vm.ps(other.ty),
|
||||
.iz = self.ix * vm.ps(other.iz) + self.iy * vm.ps(other.jz) + self.iz * vm.ps(other.kz) + self.iw * vm.ps(other.tz),
|
||||
.iw = self.ix * vm.ps(other.iw) + self.iy * vm.ps(other.jw) + self.iz * vm.ps(other.kw) + self.iw * vm.ps(other.tw),
|
||||
.jx = self.jx * vm.ps(other.ix) + self.jy * vm.ps(other.jx) + self.jz * vm.ps(other.kx) + self.jw * vm.ps(other.tx),
|
||||
.jy = self.jx * vm.ps(other.iy) + self.jy * vm.ps(other.jy) + self.jz * vm.ps(other.ky) + self.jw * vm.ps(other.ty),
|
||||
.jz = self.jx * vm.ps(other.iz) + self.jy * vm.ps(other.jz) + self.jz * vm.ps(other.kz) + self.jw * vm.ps(other.tz),
|
||||
.jw = self.jx * vm.ps(other.iw) + self.jy * vm.ps(other.jw) + self.jz * vm.ps(other.kw) + self.jw * vm.ps(other.tw),
|
||||
.kx = self.kx * vm.ps(other.ix) + self.ky * vm.ps(other.jx) + self.kz * vm.ps(other.kx) + self.kw * vm.ps(other.tx),
|
||||
.ky = self.kx * vm.ps(other.iy) + self.ky * vm.ps(other.jy) + self.kz * vm.ps(other.ky) + self.kw * vm.ps(other.ty),
|
||||
.kz = self.kx * vm.ps(other.iz) + self.ky * vm.ps(other.jz) + self.kz * vm.ps(other.kz) + self.kw * vm.ps(other.tz),
|
||||
.kw = self.kx * vm.ps(other.iw) + self.ky * vm.ps(other.jw) + self.kz * vm.ps(other.kw) + self.kw * vm.ps(other.tw),
|
||||
.tx = self.tx * vm.ps(other.ix) + self.ty * vm.ps(other.jx) + self.tz * vm.ps(other.kx) + self.tw * vm.ps(other.tx),
|
||||
.ty = self.tx * vm.ps(other.iy) + self.ty * vm.ps(other.jy) + self.tz * vm.ps(other.ky) + self.tw * vm.ps(other.ty),
|
||||
.tz = self.tx * vm.ps(other.iz) + self.ty * vm.ps(other.jz) + self.tz * vm.ps(other.kz) + self.tw * vm.ps(other.tz),
|
||||
.tw = self.tx * vm.ps(other.iw) + self.ty * vm.ps(other.jw) + self.tz * vm.ps(other.kw) + self.tw * vm.ps(other.tw),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn inverseOrthonormal(self: Matrix4x4x8) Matrix4x4x8 {
|
||||
std.debug.assert(@reduce(.And, (self.iw == vm.ps(0)) & (self.jw == vm.ps(0)) & (self.kw == vm.ps(0)) & (self.tw == vm.ps(1))));
|
||||
const ix = self.ix;
|
||||
@@ -776,4 +763,8 @@ pub const Matrix4x4x8 = extern struct {
|
||||
// zig fmt: on
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ const std = @import("std");
|
||||
// --- COLORS ------------------------------------------------------------------
|
||||
|
||||
pub const Color = @import("colors/Color.zig").Color;
|
||||
pub const ColorHdr = @import("colors/ColorHdr.zig").ColorHdr;
|
||||
|
||||
// --- MATRICES ----------------------------------------------------------------
|
||||
|
||||
@@ -78,10 +79,36 @@ pub const cossin_x8 = trig.cossin_x8;
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
pub inline fn lerp(comptime T: type, a: T, b: T, t: T) T {
|
||||
return @mulAdd(T, t, b, @mulAdd(T, -t, a, a));
|
||||
pub inline fn lerp(a: f32, b: f32, t: f32) f32 {
|
||||
return @mulAdd(f32, t, b, @mulAdd(f32, -t, a, a));
|
||||
}
|
||||
|
||||
pub inline fn lerpInt(a: i32, b: i32, t: f32) i32 {
|
||||
const ab = b - a;
|
||||
const ab_float: f32 = @floatFromInt(ab);
|
||||
const d: i32 = @intFromFloat(@round(t * ab_float));
|
||||
return a + d;
|
||||
}
|
||||
|
||||
pub inline fn lerpInt64(a: i64, b: i64, t: f32) i64 {
|
||||
const ab = b - a;
|
||||
const ab_float: f32 = @floatFromInt(ab);
|
||||
const d: i64 = @intFromFloat(@round(t * ab_float));
|
||||
return a + d;
|
||||
}
|
||||
|
||||
pub inline fn unlerp(a: f32, b: f32, x: f32) f32 {
|
||||
return (x - a) / (b - a);
|
||||
}
|
||||
|
||||
pub inline fn unlerpInt(a: i32, b: i32, x: i32) f32 {
|
||||
return @as(f32, @floatFromInt(x - a)) / @as(f32, @floatFromInt(b - a));
|
||||
}
|
||||
|
||||
pub inline fn unlerpInt64(a: i64, b: i64, x: i64) f32 {
|
||||
return @as(f32, @floatFromInt(x - a)) / @as(f32, @floatFromInt(b - a));
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDeclsRecursive(@This());
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ pub const Complex = extern struct {
|
||||
}
|
||||
|
||||
pub inline fn inverseUnit(self: Complex) Complex {
|
||||
std.debug.assert(@abs(self.mag() - 1.0) <= 0x1p-10);
|
||||
std.debug.assert(@abs(self.mag() - 1.0) <= 0x1p-5);
|
||||
return .{
|
||||
.re = self.re,
|
||||
.im = -self.im,
|
||||
@@ -122,4 +122,12 @@ pub const Complex = extern struct {
|
||||
.im = @mulAdd(f32, t, b.im, @mulAdd(f32, -t, a.im, a.im)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Complex, w: *std.Io.Writer) !void {
|
||||
try w.print("Complex[{d:.3}, {d:.3}]", .{ self.re, self.im });
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,7 +155,7 @@ pub const Complex_x8 = struct {
|
||||
}
|
||||
|
||||
pub inline fn inverseUnit(self: Complex_x8) Complex_x8 {
|
||||
std.debug.assert(@reduce(.And, @abs(self.mag() - vm.ps(1.0)) <= vm.ps(0x1p-10)));
|
||||
std.debug.assert(@reduce(.And, @abs(self.mag() - vm.ps(1.0)) <= vm.ps(0x1p-5)));
|
||||
return .{
|
||||
.re = self.re,
|
||||
.im = -self.im,
|
||||
@@ -193,4 +193,8 @@ pub const Complex_x8 = struct {
|
||||
.im = @mulAdd(vm.f32x8, vm.ps(t), b.im, @mulAdd(vm.f32x8, -vm.ps(t), a.im, a.im)),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -130,8 +130,18 @@ pub const Quaternion = extern struct {
|
||||
};
|
||||
}
|
||||
|
||||
test mulQuaternion {
|
||||
try std.testing.expectEqual(
|
||||
init(101, 169, 207, -13),
|
||||
mulQuaternion(
|
||||
init(2, 3, 5, 7),
|
||||
init(11, 13, 17, 19),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn inverseUnit(self: Quaternion) Quaternion {
|
||||
std.debug.assert(@abs(self.mag() - 1.0) <= 0x1p-10);
|
||||
std.debug.assert(@abs(self.mag() - 1.0) <= 0x1p-5);
|
||||
return .{
|
||||
.x = -self.x,
|
||||
.y = -self.y,
|
||||
@@ -168,4 +178,12 @@ pub const Quaternion = extern struct {
|
||||
.w = @mulAdd(f32, t, b.w, @mulAdd(f32, -t, a.w, a.w)),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Quaternion, w: *std.Io.Writer) !void {
|
||||
try w.print("Quaternion[{d:.3}, {d:.3}, {d:.3}, {d:.3}]", .{ self.x, self.y, self.z, self.w });
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -232,7 +232,7 @@ pub const Quaternion_x8 = struct {
|
||||
}
|
||||
|
||||
pub inline fn inverseUnit(self: Quaternion_x8) Quaternion_x8 {
|
||||
std.debug.assert(@reduce(.And, @abs(self.mag() - vm.ps(1.0)) <= vm.ps(0x1p-10)));
|
||||
std.debug.assert(@reduce(.And, @abs(self.mag() - vm.ps(1.0)) <= vm.ps(0x1p-5)));
|
||||
return .{
|
||||
.x = -self.x,
|
||||
.y = -self.y,
|
||||
@@ -278,4 +278,8 @@ pub const Quaternion_x8 = struct {
|
||||
.w = @mulAdd(vm.f32x8, vm.ps(t), b.w, @mulAdd(vm.f32x8, -vm.ps(t), a.w, a.w)),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -31,3 +31,7 @@ pub inline fn epi64(value: i64) i64x4 {
|
||||
pub inline fn epu64(value: u64) u64x4 {
|
||||
return @splat(value);
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
@@ -27,10 +27,10 @@ pub fn turnsToRadians(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_f
|
||||
|
||||
test turnsToRadians {
|
||||
try std.testing.expectEqual(0, turnsToRadians(@as(f32, 0)));
|
||||
try std.testing.expectApproxEqAbs(0.25 * std.math.tau, turnsToRadians(@as(f32, 0.25)), 0x1p-10);
|
||||
try std.testing.expectApproxEqAbs(0.5 * std.math.tau, turnsToRadians(@as(f32, 0.5)), 0x1p-10);
|
||||
try std.testing.expectApproxEqAbs(0.75 * std.math.tau, turnsToRadians(@as(f32, 0.75)), 0x1p-10);
|
||||
try std.testing.expectApproxEqAbs(std.math.tau, turnsToRadians(@as(f32, 1)), 0x1p-10);
|
||||
try std.testing.expectApproxEqAbs(0.25 * std.math.tau, turnsToRadians(@as(f32, 0.25)), 0x1p-5);
|
||||
try std.testing.expectApproxEqAbs(0.5 * std.math.tau, turnsToRadians(@as(f32, 0.5)), 0x1p-5);
|
||||
try std.testing.expectApproxEqAbs(0.75 * std.math.tau, turnsToRadians(@as(f32, 0.75)), 0x1p-5);
|
||||
try std.testing.expectApproxEqAbs(std.math.tau, turnsToRadians(@as(f32, 1)), 0x1p-5);
|
||||
}
|
||||
|
||||
/// Converts an angle in turns to degrees. `@TypeOf(ang)` must be a float or
|
||||
@@ -67,10 +67,10 @@ pub fn radiansToTurns(ang: anytype) if (@TypeOf(ang) == comptime_int) comptime_f
|
||||
|
||||
test radiansToTurns {
|
||||
try std.testing.expectEqual(0, radiansToTurns(@as(f32, 0)));
|
||||
try std.testing.expectApproxEqAbs(0.25, radiansToTurns(@as(f32, 0.25 * std.math.tau)), 0x1p-10);
|
||||
try std.testing.expectApproxEqAbs(0.5, radiansToTurns(@as(f32, 0.5 * std.math.tau)), 0x1p-10);
|
||||
try std.testing.expectApproxEqAbs(0.75, radiansToTurns(@as(f32, 0.75 * std.math.tau)), 0x1p-10);
|
||||
try std.testing.expectApproxEqAbs(1, radiansToTurns(@as(f32, std.math.tau)), 0x1p-10);
|
||||
try std.testing.expectApproxEqAbs(0.25, radiansToTurns(@as(f32, 0.25 * std.math.tau)), 0x1p-5);
|
||||
try std.testing.expectApproxEqAbs(0.5, radiansToTurns(@as(f32, 0.5 * std.math.tau)), 0x1p-5);
|
||||
try std.testing.expectApproxEqAbs(0.75, radiansToTurns(@as(f32, 0.75 * std.math.tau)), 0x1p-5);
|
||||
try std.testing.expectApproxEqAbs(1, radiansToTurns(@as(f32, std.math.tau)), 0x1p-5);
|
||||
}
|
||||
|
||||
/// Converts an angle in degrees to turns. `@TypeOf(ang)` must be a float or
|
||||
@@ -235,3 +235,7 @@ test cossin_x8 {
|
||||
cossin_x8(.{ -1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75 }),
|
||||
);
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
|
||||
@@ -34,6 +34,10 @@ pub const Vector2 = extern struct {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
pub inline fn asInt(self: Vector2) vm.Vector2Int {
|
||||
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y) };
|
||||
}
|
||||
|
||||
pub inline fn withZ(self: Vector2, z: f32) vm.Vector3 {
|
||||
return .{ .x = self.x, .y = self.y, .z = z };
|
||||
}
|
||||
@@ -127,4 +131,47 @@ pub const Vector2 = extern struct {
|
||||
.y = self.x * complex.im + self.y * complex.re,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn rotate_x8(self: Vector2, complex: vm.Complex_x8) vm.Vector2x8 {
|
||||
return .{
|
||||
.x = vm.ps(self.x) * complex.re - vm.ps(self.y) * complex.im,
|
||||
.y = vm.ps(self.x) * complex.im + vm.ps(self.y) * complex.re,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformPoint(self: Vector2, m: vm.Matrix3x2) Vector2 {
|
||||
return .{
|
||||
.x = self.x * m.ix + self.y * m.jx + m.tx,
|
||||
.y = self.x * m.iy + self.y * m.jy + m.ty,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformPoint_x8(self: Vector2, m: vm.Matrix3x2x8) vm.Vector2x8 {
|
||||
return .{
|
||||
.x = vm.ps(self.x) * m.ix + vm.ps(self.y) * m.jx + m.tx,
|
||||
.y = vm.ps(self.x) * m.iy + vm.ps(self.y) * m.jy + m.ty,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformVector(self: Vector2, m: vm.Matrix3x2) Vector2 {
|
||||
return .{
|
||||
.x = self.x * m.ix + self.y * m.jx,
|
||||
.y = self.x * m.iy + self.y * m.jy,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformVector_x8(self: Vector2, m: vm.Matrix3x2x8) vm.Vector2x8 {
|
||||
return .{
|
||||
.x = vm.ps(self.x) * m.ix + vm.ps(self.y) * m.jx,
|
||||
.y = vm.ps(self.x) * m.iy + vm.ps(self.y) * m.jy,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Vector2, w: *std.Io.Writer) !void {
|
||||
try w.print("[{d}, {d}]", .{ self.x, self.y });
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -34,6 +34,10 @@ pub const Vector2Int = extern struct {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
pub inline fn asFloat(self: Vector2Int) vm.Vector2 {
|
||||
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y) };
|
||||
}
|
||||
|
||||
pub inline fn withZ(self: Vector2Int, z: i32) vm.Vector3Int {
|
||||
return .{ .x = self.x, .y = self.y, .z = z };
|
||||
}
|
||||
@@ -105,4 +109,15 @@ pub const Vector2Int = extern struct {
|
||||
pub inline fn cross(self: Vector2Int, other: Vector2Int) i32 {
|
||||
return self.x * other.y - self.y * other.x;
|
||||
}
|
||||
|
||||
pub fn format(self: Vector2Int, w: *std.Io.Writer) !void {
|
||||
try w.print("[{X:0>8}, {X:0>8}]", .{
|
||||
@as(u32, @bitCast(self.x)),
|
||||
@as(u32, @bitCast(self.y)),
|
||||
});
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,6 +44,10 @@ pub const Vector2Int_x8 = struct {
|
||||
|
||||
// --- CONVERSION ----------------------------------------------------------
|
||||
|
||||
pub inline fn asFloat(self: Vector2Int_x8) vm.Vector2x8 {
|
||||
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y) };
|
||||
}
|
||||
|
||||
pub inline fn withZ(self: Vector2Int_x8, z: vm.i32x8) vm.Vector3Int_x8 {
|
||||
return .{ .x = self.x, .y = self.y, .z = z };
|
||||
}
|
||||
@@ -167,4 +171,8 @@ pub const Vector2Int_x8 = struct {
|
||||
pub inline fn cross(self: Vector2Int_x8, other: Vector2Int_x8) vm.i32x8 {
|
||||
return self.x * other.y - self.y * other.x;
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -44,6 +44,10 @@ pub const Vector2x8 = struct {
|
||||
|
||||
// --- CONVERSION ----------------------------------------------------------
|
||||
|
||||
pub inline fn asInt(self: Vector2x8) vm.Vector2Int_x8 {
|
||||
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y) };
|
||||
}
|
||||
|
||||
pub inline fn withZ(self: Vector2x8, z: vm.f32x8) vm.Vector3x8 {
|
||||
return .{ .x = self.x, .y = self.y, .z = z };
|
||||
}
|
||||
@@ -199,4 +203,36 @@ pub const Vector2x8 = struct {
|
||||
.y = self.x * vm.ps(complex.im) + self.y * vm.ps(complex.re),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformPoint(self: Vector2x8, m: vm.Matrix3x2x8) Vector2x8 {
|
||||
return .{
|
||||
.x = self.x * m.ix + self.y * m.jx + m.tx,
|
||||
.y = self.x * m.iy + self.y * m.jy + m.ty,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformPointSingle(self: Vector2x8, m: vm.Matrix3x2) Vector2x8 {
|
||||
return .{
|
||||
.x = self.x * vm.ps(m.ix) + self.y * vm.ps(m.jx) + vm.ps(m.tx),
|
||||
.y = self.x * vm.ps(m.iy) + self.y * vm.ps(m.jy) + vm.ps(m.ty),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformVector(self: Vector2x8, m: vm.Matrix3x2x8) Vector2x8 {
|
||||
return .{
|
||||
.x = self.x * m.ix + self.y * m.jx,
|
||||
.y = self.x * m.iy + self.y * m.jy,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformVectorSingle(self: Vector2x8, m: vm.Matrix3x2) Vector2x8 {
|
||||
return .{
|
||||
.x = self.x * vm.ps(m.ix) + self.y * vm.ps(m.jx),
|
||||
.y = self.x * vm.ps(m.iy) + self.y * vm.ps(m.jy),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,6 +37,10 @@ pub const Vector3 = extern struct {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
pub inline fn asInt(self: Vector3) vm.Vector3Int {
|
||||
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y), .z = @intFromFloat(self.z) };
|
||||
}
|
||||
|
||||
pub inline fn dropZ(self: Vector3) vm.Vector2 {
|
||||
return .{ .x = self.x, .y = self.y };
|
||||
}
|
||||
@@ -141,4 +145,58 @@ pub const Vector3 = extern struct {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn rotate_x8(self: Vector3, quaternion: vm.Quaternion_x8) vm.Vector3x8 {
|
||||
const w = quaternion.getScalar();
|
||||
const xyz = quaternion.getVector();
|
||||
const self_x8 = vm.Vector3x8.splat(self);
|
||||
|
||||
return .add(
|
||||
self_x8,
|
||||
.cross(
|
||||
.add(xyz, xyz),
|
||||
.add(.cross(xyz, self_x8), self_x8.mulScalar(w)),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn transformPoint(self: Vector3, m: vm.Matrix4x4) Vector3 {
|
||||
return .{
|
||||
.x = self.x * m.ix + self.y * m.jx + self.z * m.kx + m.tx,
|
||||
.y = self.x * m.iy + self.y * m.jy + self.z * m.ky + m.ty,
|
||||
.z = self.x * m.iz + self.y * m.jz + self.z * m.kz + m.tz,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformPoint_x8(self: Vector3, m: vm.Matrix4x4x8) vm.Vector3x8 {
|
||||
return .{
|
||||
.x = vm.ps(self.x) * m.ix + vm.ps(self.y) * m.jx + vm.ps(self.z) * m.kx + m.tx,
|
||||
.y = vm.ps(self.x) * m.iy + vm.ps(self.y) * m.jy + vm.ps(self.z) * m.ky + m.ty,
|
||||
.z = vm.ps(self.x) * m.iz + vm.ps(self.y) * m.jz + vm.ps(self.z) * m.kz + m.tz,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformVector(self: Vector3, m: vm.Matrix4x4) Vector3 {
|
||||
return .{
|
||||
.x = self.x * m.ix + self.y * m.jx + self.z * m.kx,
|
||||
.y = self.x * m.iy + self.y * m.jy + self.z * m.ky,
|
||||
.z = self.x * m.iz + self.y * m.jz + self.z * m.kz,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformVector_x8(self: Vector3, m: vm.Matrix4x4x8) vm.Vector3x8 {
|
||||
return .{
|
||||
.x = vm.ps(self.x) * m.ix + vm.ps(self.y) * m.jx + vm.ps(self.z) * m.kx,
|
||||
.y = vm.ps(self.x) * m.iy + vm.ps(self.y) * m.jy + vm.ps(self.z) * m.ky,
|
||||
.z = vm.ps(self.x) * m.iz + vm.ps(self.y) * m.jz + vm.ps(self.z) * m.kz,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Vector3, w: *std.Io.Writer) !void {
|
||||
try w.print("[{d}, {d}, {d}]", .{ self.x, self.y, self.z });
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,6 +37,10 @@ pub const Vector3Int = extern struct {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
pub inline fn asFloat(self: Vector3Int) vm.Vector3 {
|
||||
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y), .z = @floatFromInt(self.z) };
|
||||
}
|
||||
|
||||
pub inline fn dropZ(self: Vector3Int) vm.Vector2Int {
|
||||
return .{ .x = self.x, .y = self.y };
|
||||
}
|
||||
@@ -112,4 +116,16 @@ pub const Vector3Int = extern struct {
|
||||
.z = self.x * other.y - self.y * other.x,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Vector3Int, w: *std.Io.Writer) !void {
|
||||
try w.print("[{X:0>8}, {X:0>8}, {X:0>8}]", .{
|
||||
@as(u32, @bitCast(self.x)),
|
||||
@as(u32, @bitCast(self.y)),
|
||||
@as(u32, @bitCast(self.z)),
|
||||
});
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,6 +48,10 @@ pub const Vector3Int_x8 = struct {
|
||||
|
||||
// --- CONVERSION ----------------------------------------------------------
|
||||
|
||||
pub inline fn asFloat(self: Vector3Int_x8) vm.Vector3x8 {
|
||||
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y), .z = @floatFromInt(self.z) };
|
||||
}
|
||||
|
||||
pub inline fn dropZ(self: Vector3Int_x8) vm.Vector2Int_x8 {
|
||||
return .{ .x = self.x, .y = self.y };
|
||||
}
|
||||
@@ -176,4 +180,8 @@ pub const Vector3Int_x8 = struct {
|
||||
.z = self.x * other.y - self.y * other.x,
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,6 +48,10 @@ pub const Vector3x8 = struct {
|
||||
|
||||
// --- CONVERSION ----------------------------------------------------------
|
||||
|
||||
pub inline fn asInt(self: Vector3x8) vm.Vector3Int_x8 {
|
||||
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y), .z = @intFromFloat(self.z) };
|
||||
}
|
||||
|
||||
pub inline fn dropZ(self: Vector3x8) vm.Vector2x8 {
|
||||
return .{ .x = self.x, .y = self.y };
|
||||
}
|
||||
@@ -222,4 +226,40 @@ pub const Vector3x8 = struct {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
pub inline fn transformPoint(self: vm.Matrix4x4x8, p: Vector3x8) Vector3x8 {
|
||||
return .{
|
||||
.x = p.x * self.ix + p.y * self.jx + p.z * self.kx + self.tx,
|
||||
.y = p.x * self.iy + p.y * self.jy + p.z * self.ky + self.ty,
|
||||
.z = p.x * self.iz + p.y * self.jz + p.z * self.kz + self.tz,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformPointSingle(self: vm.Matrix4x4, p: Vector3x8) Vector3x8 {
|
||||
return .{
|
||||
.x = p.x * vm.ps(self.ix) + p.y * vm.ps(self.jx) + p.z * vm.ps(self.kx) + vm.ps(self.tx),
|
||||
.y = p.x * vm.ps(self.iy) + p.y * vm.ps(self.jy) + p.z * vm.ps(self.ky) + vm.ps(self.ty),
|
||||
.z = p.x * vm.ps(self.iz) + p.y * vm.ps(self.jz) + p.z * vm.ps(self.kz) + vm.ps(self.tz),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformVector(self: vm.Matrix4x4x8, p: Vector3x8) Vector3x8 {
|
||||
return .{
|
||||
.x = p.x * self.ix + p.y * self.jx + p.z * self.kx,
|
||||
.y = p.x * self.iy + p.y * self.jy + p.z * self.ky,
|
||||
.z = p.x * self.iz + p.y * self.jz + p.z * self.kz,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformVectorSingle(self: vm.Matrix4x4, v: Vector3x8) Vector3x8 {
|
||||
return .{
|
||||
.x = v.x * vm.ps(self.ix) + v.y * vm.ps(self.jx) + v.z * vm.ps(self.kx),
|
||||
.y = v.x * vm.ps(self.iy) + v.y * vm.ps(self.jy) + v.z * vm.ps(self.ky),
|
||||
.z = v.x * vm.ps(self.iz) + v.y * vm.ps(self.jz) + v.z * vm.ps(self.kz),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,6 +36,10 @@ pub const Vector4 = extern struct {
|
||||
|
||||
// --- CONVERSION ----------------------------------------------------------
|
||||
|
||||
pub inline fn asInt(self: Vector4) vm.Vector4Int {
|
||||
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y), .z = @intFromFloat(self.z), .w = @intFromFloat(self.w) };
|
||||
}
|
||||
|
||||
pub inline fn asArray(self: Vector4) Array {
|
||||
return @bitCast(self);
|
||||
}
|
||||
@@ -124,4 +128,30 @@ pub const Vector4 = extern struct {
|
||||
.w = @mulAdd(f32, t, b.w, @mulAdd(f32, -t, a.w, a.w)),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transform(self: Vector4, m: vm.Matrix4x4) Vector4 {
|
||||
return .{
|
||||
.x = self.x * m.ix + self.y * m.jx + self.z * m.kx + self.w * m.tx,
|
||||
.y = self.x * m.iy + self.y * m.jy + self.z * m.ky + self.w * m.ty,
|
||||
.z = self.x * m.iz + self.y * m.jz + self.z * m.kz + self.w * m.tz,
|
||||
.w = self.x * m.iw + self.y * m.jw + self.z * m.kw + self.w * m.tw,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transform_x8(self: Vector4, m: vm.Matrix4x4x8) vm.Vector4x8 {
|
||||
return .{
|
||||
.x = vm.ps(self.x) * m.ix + vm.ps(self.y) * m.jx + vm.ps(self.z) * m.kx + vm.ps(self.w) * m.tx,
|
||||
.y = vm.ps(self.x) * m.iy + vm.ps(self.y) * m.jy + vm.ps(self.z) * m.ky + vm.ps(self.w) * m.ty,
|
||||
.z = vm.ps(self.x) * m.iz + vm.ps(self.y) * m.jz + vm.ps(self.z) * m.kz + vm.ps(self.w) * m.tz,
|
||||
.w = vm.ps(self.x) * m.iw + vm.ps(self.y) * m.jw + vm.ps(self.z) * m.kw + vm.ps(self.w) * m.tw,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn format(self: Vector4, w: *std.Io.Writer) !void {
|
||||
try w.print("[{d}, {d}, {d}, {d}]", .{ self.x, self.y, self.z, self.w });
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -40,6 +40,10 @@ pub const Vector4Int = extern struct {
|
||||
return @bitCast(self);
|
||||
}
|
||||
|
||||
pub inline fn asFloat(self: Vector4Int) vm.Vector4 {
|
||||
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y), .z = @floatFromInt(self.z), .w = @floatFromInt(self.w) };
|
||||
}
|
||||
|
||||
pub inline fn dropW(self: Vector4Int) vm.Vector3Int {
|
||||
return .{ .x = self.x, .y = self.y, .z = self.z };
|
||||
}
|
||||
@@ -107,4 +111,17 @@ pub const Vector4Int = extern struct {
|
||||
pub inline fn dot(self: Vector4Int, other: Vector4Int) i32 {
|
||||
return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
|
||||
}
|
||||
|
||||
pub fn format(self: Vector4Int, w: *std.Io.Writer) !void {
|
||||
try w.print("[{X:0>8}, {X:0>8}, {X:0>8}, {X:0>8}]", .{
|
||||
@as(u32, @bitCast(self.x)),
|
||||
@as(u32, @bitCast(self.y)),
|
||||
@as(u32, @bitCast(self.z)),
|
||||
@as(u32, @bitCast(self.w)),
|
||||
});
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,6 +52,10 @@ pub const Vector4Int_x8 = struct {
|
||||
|
||||
// --- CONVERSION ----------------------------------------------------------
|
||||
|
||||
pub inline fn asFloat(self: Vector4Int_x8) vm.Vector4x8 {
|
||||
return .{ .x = @floatFromInt(self.x), .y = @floatFromInt(self.y), .z = @floatFromInt(self.z), .w = @floatFromInt(self.w) };
|
||||
}
|
||||
|
||||
pub inline fn dropW(self: Vector4Int_x8) vm.Vector3Int_x8 {
|
||||
return .{ .x = self.x, .y = self.y, .z = self.z };
|
||||
}
|
||||
@@ -173,4 +177,8 @@ pub const Vector4Int_x8 = struct {
|
||||
pub inline fn dot(self: Vector4Int_x8, other: Vector4Int_x8) vm.i32x8 {
|
||||
return self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w;
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -52,6 +52,10 @@ pub const Vector4x8 = struct {
|
||||
|
||||
// --- CONVERSION ----------------------------------------------------------
|
||||
|
||||
pub inline fn asInt(self: Vector4x8) vm.Vector4Int_x8 {
|
||||
return .{ .x = @intFromFloat(self.x), .y = @intFromFloat(self.y), .z = @intFromFloat(self.z), .w = @intFromFloat(self.w) };
|
||||
}
|
||||
|
||||
pub inline fn dropW(self: Vector4x8) vm.Vector3x8 {
|
||||
return .{ .x = self.x, .y = self.y, .z = self.z };
|
||||
}
|
||||
@@ -195,4 +199,26 @@ pub const Vector4x8 = struct {
|
||||
.w = @mulAdd(vm.f32x8, vm.ps(t), b.w, @mulAdd(vm.f32x8, -vm.ps(t), a.w, a.w)),
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transform(self: Vector4x8, m: vm.Matrix4x4x8) Vector4x8 {
|
||||
return .{
|
||||
.x = self.x * m.ix + self.y * m.jx + self.z * m.kx + self.w * m.tx,
|
||||
.y = self.x * m.iy + self.y * m.jy + self.z * m.ky + self.w * m.ty,
|
||||
.z = self.x * m.iz + self.y * m.jz + self.z * m.kz + self.w * m.tz,
|
||||
.w = self.x * m.iw + self.y * m.jw + self.z * m.kw + self.w * m.tw,
|
||||
};
|
||||
}
|
||||
|
||||
pub inline fn transformSingle(self: Vector4x8, m: vm.Matrix4x4) Vector4x8 {
|
||||
return .{
|
||||
.x = self.x * vm.ps(m.ix) + self.y * vm.ps(m.jx) + self.z * vm.ps(m.kx) + self.w * vm.ps(m.tx),
|
||||
.y = self.x * vm.ps(m.iy) + self.y * vm.ps(m.jy) + self.z * vm.ps(m.ky) + self.w * vm.ps(m.ty),
|
||||
.z = self.x * vm.ps(m.iz) + self.y * vm.ps(m.jz) + self.z * vm.ps(m.kz) + self.w * vm.ps(m.tz),
|
||||
.w = self.x * vm.ps(m.iw) + self.y * vm.ps(m.jw) + self.z * vm.ps(m.kw) + self.w * vm.ps(m.tw),
|
||||
};
|
||||
}
|
||||
|
||||
test "refAllDecls" {
|
||||
std.testing.refAllDecls(@This());
|
||||
}
|
||||
};
|
||||
|
||||
52
packages/web/build.zig
Normal file
52
packages/web/build.zig
Normal file
@@ -0,0 +1,52 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub fn build(b: *std.Build) void {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
const module = b.addModule("web", .{
|
||||
.root_source_file = b.path("src/root.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.link_libc = true,
|
||||
});
|
||||
|
||||
module.linkSystemLibrary("ssl", .{});
|
||||
module.linkSystemLibrary("crypto", .{});
|
||||
|
||||
const tests = b.addTest(.{
|
||||
.root_module = module,
|
||||
});
|
||||
|
||||
const exe_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.imports = &.{
|
||||
.{ .name = "web", .module = module },
|
||||
},
|
||||
});
|
||||
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "web",
|
||||
.root_module = exe_module,
|
||||
});
|
||||
|
||||
b.installArtifact(exe);
|
||||
|
||||
const run_step = b.step("run", "Run the app");
|
||||
|
||||
const run_cmd = b.addRunArtifact(exe);
|
||||
run_step.dependOn(&run_cmd.step);
|
||||
|
||||
run_cmd.step.dependOn(b.getInstallStep());
|
||||
|
||||
if (b.args) |args| {
|
||||
run_cmd.addArgs(args);
|
||||
}
|
||||
|
||||
const run_tests = b.addRunArtifact(tests);
|
||||
|
||||
const test_step = b.step("test", "Run tests");
|
||||
test_step.dependOn(&run_tests.step);
|
||||
}
|
||||
11
packages/web/build.zig.zon
Normal file
11
packages/web/build.zig.zon
Normal file
@@ -0,0 +1,11 @@
|
||||
.{
|
||||
.name = .web,
|
||||
.version = "0.0.0",
|
||||
.minimum_zig_version = "0.15.2",
|
||||
.paths = .{
|
||||
"src",
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
},
|
||||
.fingerprint = 0x15c938517148d390,
|
||||
}
|
||||
9
packages/web/cert.pem
Normal file
9
packages/web/cert.pem
Normal file
@@ -0,0 +1,9 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIBPDCB76ADAgECAhRuWLL9k0QOIt4+BPNlFBzRcmvREDAFBgMrZXAwFDESMBAG
|
||||
A1UEAwwJbG9jYWxob3N0MB4XDTI2MDMwODIwNDczNFoXDTM2MDMwNTIwNDczNFow
|
||||
FDESMBAGA1UEAwwJbG9jYWxob3N0MCowBQYDK2VwAyEAdgB1CRIUYLCPclWp2+5c
|
||||
X3I0aqoY7yuhZBE9NxKKbZ+jUzBRMB0GA1UdDgQWBBQXJ3/C7HMVOjXcnqvDKdWo
|
||||
PWhzsTAfBgNVHSMEGDAWgBQXJ3/C7HMVOjXcnqvDKdWoPWhzsTAPBgNVHRMBAf8E
|
||||
BTADAQH/MAUGAytlcANBAFirv6F5+TamfddV1sElbfI8jfhPLxWU+z6/mxxbzooX
|
||||
IKdOh/FrCrPfeUavKH9C5Vr+ztPgoTdCaFqo5mT7MAw=
|
||||
-----END CERTIFICATE-----
|
||||
3
packages/web/key.pem
Normal file
3
packages/web/key.pem
Normal file
@@ -0,0 +1,3 @@
|
||||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VwBCIEIMDUsa31l2xEhX1gyB5w2WABgSbne3GHyUe0RWomq9C5
|
||||
-----END PRIVATE KEY-----
|
||||
147
packages/web/src/Connection.zig
Normal file
147
packages/web/src/Connection.zig
Normal file
@@ -0,0 +1,147 @@
|
||||
const std = @import("std");
|
||||
const Connection = @This();
|
||||
|
||||
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,
|
||||
fd: FileDescriptor,
|
||||
ssl: ?*openssl.Ssl,
|
||||
node: std.DoublyLinkedList.Node = .{},
|
||||
|
||||
// TODO Consider proper usage of `send` syscall with `MSG_MORE` flag and setting
|
||||
// the `TCP_CORK` option.
|
||||
|
||||
pub fn reinit(
|
||||
self: *Connection,
|
||||
address: std.net.Address,
|
||||
fd: FileDescriptor,
|
||||
ssl: ?*openssl.Ssl,
|
||||
) void {
|
||||
self.address = address;
|
||||
self.fd = fd;
|
||||
self.ssl = ssl;
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Connection) void {
|
||||
if (self.ssl) |x| x.free();
|
||||
self.fd.close();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn read(self: *const Connection, buf: []u8) !usize {
|
||||
if (self.ssl) |ssl| {
|
||||
const bytes_read = try ssl.read(buf);
|
||||
return bytes_read;
|
||||
} else {
|
||||
const bytes_read = try self.fd.read(buf);
|
||||
return bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readAll(self: *const Connection, buf: []u8) !void {
|
||||
if (self.ssl) |ssl| {
|
||||
try ssl.readAll(buf);
|
||||
} else {
|
||||
try self.fd.readAll(buf);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readv(self: *const Connection, iov: []const iovec) !usize {
|
||||
if (self.ssl) |ssl| {
|
||||
var total_bytes_read: usize = 0;
|
||||
for (iov) |io| {
|
||||
const bytes_read = try ssl.read(io.base[0..io.len]);
|
||||
total_bytes_read += bytes_read;
|
||||
|
||||
if (bytes_read < io.len) {
|
||||
return total_bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
return total_bytes_read;
|
||||
} else {
|
||||
const bytes_read = try self.fd.readv(iov);
|
||||
return bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
/// Might modify `iov` when `readv` syscall reads partially.
|
||||
pub fn readvAll(self: *const Connection, iov: []iovec) !void {
|
||||
if (self.ssl) |ssl| {
|
||||
for (iov) |io| {
|
||||
try ssl.readAll(io.base[0..io.len]);
|
||||
}
|
||||
} else {
|
||||
try self.fd.readvAll(iov);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write(self: *const Connection, buf: []const u8) !usize {
|
||||
if (self.ssl) |ssl| {
|
||||
const bytes_written = try ssl.write(buf);
|
||||
return bytes_written;
|
||||
} else {
|
||||
const bytes_written = try self.fd.write(buf);
|
||||
return bytes_written;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeAll(self: *const Connection, buf: []const u8) !void {
|
||||
if (self.ssl) |ssl| {
|
||||
try ssl.writeAll(buf);
|
||||
} else {
|
||||
try self.fd.writeAll(buf);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writev(self: *const Connection, iov: []const iovec_const) !usize {
|
||||
if (self.ssl) |ssl| {
|
||||
var total_bytes_written: usize = 0;
|
||||
for (iov) |io| {
|
||||
const bytes_written = try ssl.write(io.base[0..io.len]);
|
||||
total_bytes_written += bytes_written;
|
||||
|
||||
if (bytes_written < io.len) {
|
||||
return total_bytes_written;
|
||||
}
|
||||
}
|
||||
|
||||
return total_bytes_written;
|
||||
} else {
|
||||
const bytes_written = try self.fd.writev(iov);
|
||||
return bytes_written;
|
||||
}
|
||||
}
|
||||
|
||||
/// Might modify `iov` when `writev` syscall writes partially.
|
||||
pub fn writevAll(self: *const Connection, iov: []iovec_const) !void {
|
||||
if (self.ssl) |ssl| {
|
||||
for (iov) |io| {
|
||||
try ssl.writeAll(io.base[0..io.len]);
|
||||
}
|
||||
} else {
|
||||
try self.fd.writevAll(iov);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendfile(self: *const Connection, fd: FileDescriptor, offset: usize, len: usize) !usize {
|
||||
if (self.ssl) |ssl| {
|
||||
const bytes_written = try ssl.sendfile(fd, offset, len);
|
||||
return bytes_written;
|
||||
} else {
|
||||
const bytes_written = try self.fd.sendfile(fd, offset, len);
|
||||
return bytes_written;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendfileAll(self: *const Connection, fd: FileDescriptor, offset: usize, len: usize) !void {
|
||||
if (self.ssl) |ssl| {
|
||||
try ssl.sendfileAll(fd, offset, len);
|
||||
} else {
|
||||
try self.fd.sendfileAll(fd, offset, len);
|
||||
}
|
||||
}
|
||||
238
packages/web/src/FileDescriptor.zig
Normal file
238
packages/web/src/FileDescriptor.zig
Normal file
@@ -0,0 +1,238 @@
|
||||
const std = @import("std");
|
||||
|
||||
const linux = std.os.linux;
|
||||
const errno = linux.E.init;
|
||||
|
||||
const iovec = std.posix.iovec;
|
||||
const iovec_const = std.posix.iovec_const;
|
||||
|
||||
pub const FileDescriptor = enum(i32) {
|
||||
stdin = 0,
|
||||
stdout = 1,
|
||||
stderr = 2,
|
||||
_,
|
||||
|
||||
pub fn memfd_create(name: [*:0]const u8, flags: u32) !FileDescriptor {
|
||||
const rc = linux.memfd_create(name, flags);
|
||||
return switch (errno(rc)) {
|
||||
.SUCCESS => @enumFromInt(@as(i32, @intCast(rc))),
|
||||
else => error.SystemError,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn socket(domain: u32, socket_type: u32, protocol: u32) !FileDescriptor {
|
||||
const rc = linux.socket(domain, socket_type, protocol);
|
||||
return switch (errno(rc)) {
|
||||
.SUCCESS => @enumFromInt(@as(i32, @intCast(rc))),
|
||||
else => error.SystemError,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn close(self: FileDescriptor) void {
|
||||
_ = linux.close(@intFromEnum(self));
|
||||
}
|
||||
|
||||
pub fn accept(self: FileDescriptor, noalias addr: ?*linux.sockaddr, noalias len: ?*linux.socklen_t) !FileDescriptor {
|
||||
while (true) {
|
||||
const rc = linux.accept(@intFromEnum(self), addr, len);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return @enumFromInt(@as(i32, @intCast(rc))),
|
||||
.INPROGRESS, .AGAIN => continue,
|
||||
.CONNABORTED => return error.ConnectionAborted,
|
||||
.PERM => return error.BlockedByFirewall,
|
||||
else => return error.SystemError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind(self: FileDescriptor, addr: *const linux.sockaddr, len: linux.socklen_t) !void {
|
||||
const rc = linux.bind(@intFromEnum(self), addr, len);
|
||||
return switch (errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
.ACCES, .PERM => error.AccessDenied,
|
||||
.ADDRINUSE => error.AddressInUse,
|
||||
.ADDRNOTAVAIL => error.AddressNotAvailable,
|
||||
.AFNOSUPPORT => error.AddressFamilyNotSupported,
|
||||
.LOOP => error.SymLinkLoop,
|
||||
.NAMETOOLONG => error.NameTooLong,
|
||||
.NOENT => error.FileNotFound,
|
||||
.NOTDIR => error.NotDir,
|
||||
.ROFS => error.ReadOnlyFileSystem,
|
||||
else => error.SystemError,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn ftruncate(self: FileDescriptor, length: i64) !void {
|
||||
const rc = linux.ftruncate(@intFromEnum(self), length);
|
||||
return switch (errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
else => error.SystemError,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn getsockname(self: FileDescriptor, noalias addr: *linux.sockaddr, noalias len: *linux.socklen_t) !void {
|
||||
const rc = linux.getsockname(@intFromEnum(self), addr, len);
|
||||
return switch (errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
else => error.SystemError,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn listen(self: FileDescriptor, backlog: u32) !void {
|
||||
const rc = linux.listen(@intFromEnum(self), backlog);
|
||||
return switch (errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
.ADDRINUSE => error.AddressInUse,
|
||||
else => error.SystemError,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn read(self: FileDescriptor, buf: []u8) !usize {
|
||||
while (true) {
|
||||
const rc = linux.read(@intFromEnum(self), buf.ptr, buf.len);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return rc,
|
||||
.INTR => continue,
|
||||
.AGAIN => return error.Timeout,
|
||||
else => return error.SystemError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readAll(self: FileDescriptor, buf: []u8) !void {
|
||||
var total_bytes_read: usize = 0;
|
||||
|
||||
while (total_bytes_read < buf.len) {
|
||||
const chunk = buf[total_bytes_read..];
|
||||
const bytes_read = try self.read(chunk);
|
||||
total_bytes_read += bytes_read;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn readv(self: FileDescriptor, iov: []const iovec) !usize {
|
||||
while (true) {
|
||||
const rc = linux.readv(@intFromEnum(self), iov.ptr, iov.len);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return rc,
|
||||
.INTR => continue,
|
||||
.AGAIN, .WOULDBLOCK => error.Timeout,
|
||||
else => return error.SystemError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Might modify `iov` when `readv` syscall reads partially.
|
||||
pub fn readvAll(self: FileDescriptor, iov: []iovec) !void {
|
||||
var total_bytes_read: usize = 0;
|
||||
var i: usize = 0;
|
||||
while (i < iov.len) {
|
||||
var bytes_read = try self.readv(iov[i..]);
|
||||
total_bytes_read += bytes_read;
|
||||
|
||||
// skip whole buffers
|
||||
while (i < iov.len and bytes_read >= iov[i].len) {
|
||||
bytes_read -= iov[i].len;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// skip part of a buffer
|
||||
if (bytes_read > 0) {
|
||||
iov[i].base += bytes_read;
|
||||
iov[i].len -= bytes_read;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setsockopt(self: FileDescriptor, level: i32, optname: u32, opt: []const u8) !void {
|
||||
const rc = linux.setsockopt(@intFromEnum(self), level, optname, opt.ptr, @intCast(opt.len));
|
||||
return switch (errno(rc)) {
|
||||
.SUCCESS => {},
|
||||
else => error.SystemError,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn write(self: FileDescriptor, buf: []const u8) !usize {
|
||||
while (true) {
|
||||
const rc = linux.write(@intFromEnum(self), buf.ptr, buf.len);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return rc,
|
||||
.INTR => continue,
|
||||
else => return error.SystemError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writeAll(self: FileDescriptor, buf: []const u8) !void {
|
||||
var total_bytes_written: usize = 0;
|
||||
|
||||
while (total_bytes_written < buf.len) {
|
||||
const chunk = buf[total_bytes_written..];
|
||||
const bytes_written = try self.write(chunk);
|
||||
total_bytes_written += bytes_written;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn writev(self: FileDescriptor, iov: []const iovec_const) !usize {
|
||||
while (true) {
|
||||
const rc = linux.writev(@intFromEnum(self), iov.ptr, iov.len);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return rc,
|
||||
.INTR => continue,
|
||||
else => return error.SystemError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Might modify `iov` when `writev` syscall writes partially.
|
||||
pub fn writevAll(self: FileDescriptor, iov: []iovec_const) !void {
|
||||
var total_bytes_written: usize = 0;
|
||||
var i: usize = 0;
|
||||
while (i < iov.len) {
|
||||
var bytes_written = try self.writev(iov[i..]);
|
||||
total_bytes_written += bytes_written;
|
||||
|
||||
// skip whole buffers
|
||||
while (i < iov.len and bytes_written >= iov[i].len) {
|
||||
bytes_written -= iov[i].len;
|
||||
i += 1;
|
||||
}
|
||||
|
||||
// skip part of a buffer
|
||||
if (bytes_written > 0) {
|
||||
iov[i].base += bytes_written;
|
||||
iov[i].len -= bytes_written;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Calls `fstat` and returns total size in bytes.
|
||||
pub fn size(self: FileDescriptor) !usize {
|
||||
var stat = std.mem.zeroes(linux.Stat);
|
||||
const rc = linux.fstat(@intFromEnum(self), &stat);
|
||||
return switch (errno(rc)) {
|
||||
.SUCCESS => @intCast(stat.size),
|
||||
else => error.SystemError,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sendfile(self: FileDescriptor, fd: FileDescriptor, offset: usize, len: usize) !usize {
|
||||
var offset_mut = offset;
|
||||
while (true) {
|
||||
const rc = linux.sendfile(@intFromEnum(self), @intFromEnum(fd), @ptrCast(&offset_mut), len);
|
||||
switch (errno(rc)) {
|
||||
.SUCCESS => return rc,
|
||||
.INTR => continue,
|
||||
else => return error.SystemError,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendfileAll(self: FileDescriptor, fd: FileDescriptor, offset: usize, len: usize) !void {
|
||||
var total_bytes_sent: usize = 0;
|
||||
|
||||
while (total_bytes_sent < len) {
|
||||
const bytes_sent = try self.sendfile(fd, offset + total_bytes_sent, len - total_bytes_sent);
|
||||
total_bytes_sent += bytes_sent;
|
||||
}
|
||||
}
|
||||
};
|
||||
41
packages/web/src/Id.zig
Normal file
41
packages/web/src/Id.zig
Normal file
@@ -0,0 +1,41 @@
|
||||
const std = @import("std");
|
||||
|
||||
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 {
|
||||
return struct {
|
||||
pub const tag = _tag;
|
||||
|
||||
uuid: UUID,
|
||||
|
||||
pub fn init(uuid: UUID) @This() {
|
||||
return .{ .uuid = uuid };
|
||||
}
|
||||
|
||||
pub fn eql(a: @This(), b: @This()) bool {
|
||||
return std.mem.eql(u8, &a.bytes, &b.bytes);
|
||||
}
|
||||
|
||||
pub fn decode(encoded: *const [22]u8) !@This() {
|
||||
var bytes: [16]u8 = undefined;
|
||||
decoder.decode(&bytes, encoded) catch |err| switch (err) {
|
||||
error.InvalidCharacter => return error.InvalidId,
|
||||
error.InvalidPadding, error.NoSpaceLeft => unreachable,
|
||||
};
|
||||
return .{ .bytes = bytes };
|
||||
}
|
||||
|
||||
pub fn encode(self: @This()) [22]u8 {
|
||||
var text: [22]u8 = undefined;
|
||||
self.encodeInto(&text);
|
||||
return text;
|
||||
}
|
||||
|
||||
pub fn encodeInto(self: @This(), text: *[22]u8) void {
|
||||
encoder.encode(text, self.bytes);
|
||||
}
|
||||
};
|
||||
}
|
||||
66
packages/web/src/Request.zig
Normal file
66
packages/web/src/Request.zig
Normal file
@@ -0,0 +1,66 @@
|
||||
const std = @import("std");
|
||||
const Request = @This();
|
||||
|
||||
const http = @import("http.zig");
|
||||
|
||||
method: http.Method,
|
||||
pathname: []const u8,
|
||||
headers: *HeaderHashMap,
|
||||
body: []const u8,
|
||||
|
||||
pub const HeaderHashMap = std.HashMapUnmanaged(
|
||||
http.FieldName,
|
||||
HeaderList,
|
||||
http.FieldName.HashMapContext,
|
||||
std.hash_map.default_max_load_percentage,
|
||||
);
|
||||
|
||||
pub const HeaderList = struct {
|
||||
list: std.SinglyLinkedList,
|
||||
len: usize,
|
||||
};
|
||||
|
||||
pub const HeaderValue = struct {
|
||||
value: []const u8,
|
||||
node: std.SinglyLinkedList.Node,
|
||||
};
|
||||
|
||||
/// Gets a header field value of a given `name`. When there is no such header,
|
||||
/// `null` is returned. When there is more than one header with the same name,
|
||||
/// the value of the field that came later will be returned.
|
||||
pub fn getHeader(self: *const Request, name: http.FieldName) ?[]const u8 {
|
||||
if (self.headers.get(name)) |list| {
|
||||
if (list.list.first) |head| {
|
||||
const header_value: *HeaderValue = @fieldParentPtr("node", head);
|
||||
return header_value.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn getHeaderKnown(self: *const Request, known: http.KnownFieldName) ?[]const u8 {
|
||||
return self.getHeader(.initKnonw(known));
|
||||
}
|
||||
|
||||
pub fn getHeaders(self: *const Request, name: http.FieldName, values: [][]const u8) []const []const u8 {
|
||||
var i: usize = 0;
|
||||
var node: ?*std.SinglyLinkedList.Node = if (self.headers.get(name)) |list| list.list else null;
|
||||
|
||||
while (i < values.len) {
|
||||
if (node) |n| {
|
||||
const header_value: *HeaderValue = @fieldParentPtr("node", n);
|
||||
values[i] = header_value.value;
|
||||
|
||||
i += 1;
|
||||
node = n.next;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return values[0..i];
|
||||
}
|
||||
|
||||
pub fn getHeaderCount(self: *const Request, name: http.FieldName) usize {
|
||||
const list = self.headers.get(name) orelse return 0;
|
||||
return list.len;
|
||||
}
|
||||
16
packages/web/src/RequestHandler.zig
Normal file
16
packages/web/src/RequestHandler.zig
Normal file
@@ -0,0 +1,16 @@
|
||||
const std = @import("std");
|
||||
const RequestHandler = @This();
|
||||
|
||||
const Request = @import("Request.zig");
|
||||
const Response = @import("Response.zig");
|
||||
|
||||
ptr: *anyopaque,
|
||||
vtable: *const VTable,
|
||||
|
||||
pub const VTable = struct {
|
||||
handle: *const fn (self: *anyopaque, request: *Request, response: *Response) anyerror!void,
|
||||
};
|
||||
|
||||
pub inline fn handle(self: RequestHandler, request: *Request, response: *Response) anyerror!void {
|
||||
try self.vtable.handle(self.ptr, request, response);
|
||||
}
|
||||
85
packages/web/src/Response.zig
Normal file
85
packages/web/src/Response.zig
Normal file
@@ -0,0 +1,85 @@
|
||||
const std = @import("std");
|
||||
const Response = @This();
|
||||
|
||||
const Connection = @import("Connection.zig");
|
||||
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||
const http = @import("http.zig");
|
||||
|
||||
const iovec = std.posix.iovec;
|
||||
const iovec_const = std.posix.iovec_const;
|
||||
|
||||
pub const State = union(enum) {
|
||||
init: void,
|
||||
sent: void,
|
||||
errored: anyerror,
|
||||
|
||||
pub fn initErrored(err: anyerror) State {
|
||||
return .{ .errored = err };
|
||||
}
|
||||
};
|
||||
|
||||
connection: *Connection,
|
||||
header_writer: std.Io.Writer,
|
||||
body_writer: std.Io.Writer,
|
||||
state: State,
|
||||
|
||||
pub fn init(connection: *Connection, header_write_buffer: []u8, body_write_buffer: []u8) Response {
|
||||
return .{
|
||||
.connection = connection,
|
||||
.header_writer = .fixed(header_write_buffer),
|
||||
.body_writer = .fixed(body_write_buffer),
|
||||
.state = .init,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn sendHeadersOnly(self: *Response) void {
|
||||
std.debug.assert(self.state == .init);
|
||||
|
||||
const headers_slice = self.header_writer.buffered();
|
||||
std.debug.assert(headers_slice.len > 0);
|
||||
|
||||
if (self.connection.writeAll(headers_slice)) {
|
||||
self.state = .sent;
|
||||
} else |err| {
|
||||
self.state = .initErrored(err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendHeadersAndBody(self: *Response) void {
|
||||
std.debug.assert(self.state == .init);
|
||||
|
||||
const headers_slice = self.header_writer.buffered();
|
||||
const body_slice = self.body_writer.buffered();
|
||||
std.debug.assert(headers_slice.len > 0);
|
||||
std.debug.assert(body_slice.len > 0);
|
||||
|
||||
var iov = [_]iovec_const{
|
||||
.{ .base = headers_slice.ptr, .len = headers_slice.len },
|
||||
.{ .base = body_slice.ptr, .len = body_slice.len },
|
||||
};
|
||||
|
||||
if (self.connection.writevAll(&iov)) {
|
||||
self.state = .sent;
|
||||
} else |err| {
|
||||
self.state = .initErrored(err);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn sendHeadersAndFile(self: *Response, fd: FileDescriptor, offset: usize, maybe_len: ?usize) void {
|
||||
std.debug.assert(self.state == .init);
|
||||
|
||||
const headers_slice = self.header_writer.buffered();
|
||||
std.debug.assert(headers_slice.len > 0);
|
||||
|
||||
const len = maybe_len orelse (try fd.size()) - offset;
|
||||
|
||||
if (self.connection.writeAll(headers_slice)) {
|
||||
if (self.connection.sendfileAll(fd, offset, len)) {
|
||||
self.state = .sent;
|
||||
} else |err2| {
|
||||
self.state = .initErrored(err2);
|
||||
}
|
||||
} else |err1| {
|
||||
self.state = .initErrored(err1);
|
||||
}
|
||||
}
|
||||
375
packages/web/src/Server.zig
Normal file
375
packages/web/src/Server.zig
Normal file
@@ -0,0 +1,375 @@
|
||||
const std = @import("std");
|
||||
const Server = @This();
|
||||
|
||||
const Connection = @import("Connection.zig");
|
||||
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||
const http = @import("http.zig");
|
||||
const openssl = @import("openssl.zig");
|
||||
const Request = @import("Request.zig");
|
||||
const RequestHandler = @import("RequestHandler.zig");
|
||||
const Worker = @import("Worker.zig");
|
||||
|
||||
const log = std.log.scoped(.Server);
|
||||
const linux = std.os.linux;
|
||||
const errno = linux.E.init;
|
||||
|
||||
fd: FileDescriptor,
|
||||
address: std.net.Address,
|
||||
ssl_ctx: ?*openssl.SslContext,
|
||||
workers: []Worker,
|
||||
threads: []std.Thread,
|
||||
request_handler: RequestHandler,
|
||||
read_timeout_us: u64,
|
||||
|
||||
connection_queue: std.DoublyLinkedList,
|
||||
// NOTE Connection pool has no need for being doubly-linked, but the queue has
|
||||
// (as it's FIFO) and we want a single intrusive Node to be able to participate
|
||||
// in both lists. This is possible because a connection will never belong to
|
||||
// both lists at the same time.
|
||||
connection_pool: std.DoublyLinkedList,
|
||||
connection_buffer: []Connection,
|
||||
|
||||
mutex: std.Thread.Mutex,
|
||||
cond_connection_queued: std.Thread.Condition,
|
||||
cond_connection_freed: std.Thread.Condition,
|
||||
|
||||
/// 4 kiB
|
||||
const page_size = 4 * 1024;
|
||||
/// 2 MiB
|
||||
const huge_page_size = 2 * 1024 * 1024;
|
||||
|
||||
pub const Options = struct {
|
||||
request_handler: RequestHandler,
|
||||
address: std.net.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,
|
||||
max_connections: u32 = 128,
|
||||
/// The number of worker threads. If set to `0`, the number of worker
|
||||
/// threads will be equal to the number of logical CPU cores.
|
||||
worker_count: u32 = 0,
|
||||
/// The maximum number of header fields the `Request` object will be able to
|
||||
/// store. An HTTP request will be rejected if it has more header fields
|
||||
/// than the capacity.
|
||||
max_header_fields: u32 = 256,
|
||||
/// The number of 2 MiB pages reserved for a single read buffer. Each worker
|
||||
/// has its own read buffer. An HTTP request (headers and content combined)
|
||||
/// will be rejected if it is larger than the read buffer.
|
||||
read_buffer_huge_pages: u32 = 1,
|
||||
/// The number of 4 kiB pages reserved for a single header write buffer.
|
||||
/// Each worker has its own header write buffer. The HTTP status line, all
|
||||
/// header fields and the CRLF terminator must all fit in the header write
|
||||
/// buffer.
|
||||
header_write_buffer_pages: u32 = 1,
|
||||
/// The number of 2 MiB pages reserved for a single body write buffer. Each
|
||||
/// worker has its own body write buffer. The HTTP response body must fit
|
||||
/// entirely within the body write buffer. This restriction only applies to
|
||||
/// bodies generated with the body writer and not to bodies sent with
|
||||
/// `sendfile`.
|
||||
body_write_buffer_huge_pages: u32 = 1,
|
||||
/// How much time should a worker wait on an idle connection before closing
|
||||
/// it. Specifically, how much time can a `read` syscall block for, before
|
||||
/// the connection is forcefully closed.
|
||||
read_timeout_us: u64 = 1 * std.time.us_per_s,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: Options) !Server {
|
||||
const worker_count = if (options.worker_count > 0) options.worker_count else try std.Thread.getCpuCount();
|
||||
|
||||
// Create socket fd
|
||||
|
||||
const fd: FileDescriptor = try .socket(
|
||||
options.address.any.family,
|
||||
linux.SOCK.STREAM | linux.SOCK.CLOEXEC,
|
||||
if (options.address.any.family == linux.AF.UNIX) 0 else linux.IPPROTO.TCP,
|
||||
);
|
||||
errdefer fd.close();
|
||||
|
||||
const opt = std.mem.toBytes(@as(c_int, 1));
|
||||
try fd.setsockopt(linux.SOL.SOCKET, linux.SO.REUSEADDR, &opt);
|
||||
try fd.setsockopt(linux.SOL.SOCKET, linux.SO.REUSEPORT, &opt);
|
||||
|
||||
var socklen = options.address.getOsSockLen();
|
||||
try fd.bind(&options.address.any, socklen);
|
||||
try fd.listen(options.max_connections);
|
||||
|
||||
var listen_address = options.address;
|
||||
try fd.getsockname(&listen_address.any, &socklen);
|
||||
|
||||
// Allocate arrays
|
||||
|
||||
const workers = try allocator.alloc(Worker, worker_count);
|
||||
errdefer allocator.free(workers);
|
||||
|
||||
const connection_buffer = try allocator.alloc(Connection, options.max_connections);
|
||||
errdefer allocator.free(connection_buffer);
|
||||
|
||||
const threads = try allocator.alloc(std.Thread, worker_count);
|
||||
errdefer allocator.free(threads);
|
||||
|
||||
// Allocate and remap read buffers
|
||||
|
||||
const single_read_buffer_size = @as(usize, options.read_buffer_huge_pages) * huge_page_size;
|
||||
const all_read_buffers_size = worker_count * single_read_buffer_size;
|
||||
|
||||
const double_single_read_buffer_size = 2 * single_read_buffer_size;
|
||||
const double_all_read_buffers_size = 2 * all_read_buffers_size;
|
||||
|
||||
const read_buffer_fd: FileDescriptor = try .memfd_create("read_buffer", 0);
|
||||
defer read_buffer_fd.close();
|
||||
|
||||
try read_buffer_fd.ftruncate(@intCast(all_read_buffers_size));
|
||||
|
||||
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,
|
||||
));
|
||||
errdefer _ = linux.munmap(read_buffer_ptr, double_all_read_buffers_size);
|
||||
_ = linux.madvise(read_buffer_ptr, double_all_read_buffers_size, linux.MADV.HUGEPAGE);
|
||||
|
||||
for (0..worker_count) |i| {
|
||||
const offset = i * single_read_buffer_size;
|
||||
const double_offset = i * double_single_read_buffer_size;
|
||||
|
||||
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 },
|
||||
@intFromEnum(read_buffer_fd),
|
||||
@intCast(offset),
|
||||
));
|
||||
|
||||
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 },
|
||||
@intFromEnum(read_buffer_fd),
|
||||
@intCast(offset),
|
||||
));
|
||||
}
|
||||
|
||||
// Allocate header write buffer
|
||||
|
||||
const single_header_write_buffer_size = @as(usize, options.header_write_buffer_pages) * page_size;
|
||||
const all_header_write_buffers_size = worker_count * single_header_write_buffer_size;
|
||||
|
||||
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 },
|
||||
-1,
|
||||
0,
|
||||
));
|
||||
errdefer _ = linux.munmap(header_write_buffer_ptr, all_header_write_buffers_size);
|
||||
|
||||
// Allocate body write buffer
|
||||
|
||||
const single_body_write_buffer_size = @as(usize, options.body_write_buffer_huge_pages) * huge_page_size;
|
||||
const all_body_write_buffers_size = worker_count * single_body_write_buffer_size;
|
||||
|
||||
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 },
|
||||
-1,
|
||||
0,
|
||||
));
|
||||
errdefer _ = linux.munmap(body_write_buffer_ptr, all_body_write_buffers_size);
|
||||
_ = linux.madvise(body_write_buffer_ptr, all_body_write_buffers_size, linux.MADV.HUGEPAGE);
|
||||
|
||||
// Initialize workers
|
||||
|
||||
var workers_initialized: usize = 0;
|
||||
errdefer {
|
||||
for (workers[0..workers_initialized]) |*worker| {
|
||||
worker.deinit(allocator);
|
||||
}
|
||||
}
|
||||
|
||||
for (workers, 0..) |*worker, i| {
|
||||
const read_offset = i * double_single_read_buffer_size;
|
||||
const header_write_offset = i * single_header_write_buffer_size;
|
||||
const body_write_offset = i * single_body_write_buffer_size;
|
||||
worker.* = try Worker.init(allocator, .{
|
||||
.worker_id = i,
|
||||
|
||||
.max_header_fields = options.max_header_fields,
|
||||
|
||||
.read_buffer_ptr = read_buffer_ptr + read_offset,
|
||||
.read_buffer_size = single_read_buffer_size,
|
||||
|
||||
.header_write_buffer = (header_write_buffer_ptr + header_write_offset)[0..single_header_write_buffer_size],
|
||||
.body_write_buffer = (body_write_buffer_ptr + body_write_offset)[0..single_body_write_buffer_size],
|
||||
});
|
||||
workers_initialized += 1;
|
||||
}
|
||||
|
||||
// Fill connection pool
|
||||
|
||||
var connection_pool: std.DoublyLinkedList = .{};
|
||||
for (connection_buffer) |*c| {
|
||||
connection_pool.prepend(&c.node);
|
||||
}
|
||||
|
||||
return .{
|
||||
.fd = fd,
|
||||
.address = listen_address,
|
||||
.ssl_ctx = options.ssl_ctx,
|
||||
.workers = workers,
|
||||
.threads = threads,
|
||||
.request_handler = options.request_handler,
|
||||
.read_timeout_us = options.read_timeout_us,
|
||||
|
||||
.connection_queue = .{},
|
||||
.connection_pool = connection_pool,
|
||||
.connection_buffer = connection_buffer,
|
||||
|
||||
.mutex = .{},
|
||||
.cond_connection_queued = .{},
|
||||
.cond_connection_freed = .{},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Server, allocator: std.mem.Allocator) void {
|
||||
log.debug("Deinitializing Server.", .{});
|
||||
const worker_count = self.workers.len;
|
||||
|
||||
const single_read_buffers_size = self.workers[0].read_buffer_size;
|
||||
const all_read_buffers_size = worker_count * single_read_buffers_size;
|
||||
const double_all_read_buffers_size = 2 * all_read_buffers_size;
|
||||
|
||||
const single_header_write_buffer_size = self.workers[0].header_write_buffer.len;
|
||||
const all_header_write_buffers_size = worker_count * single_header_write_buffer_size;
|
||||
|
||||
const single_body_write_buffer_size = self.workers[0].body_write_buffer.len;
|
||||
const all_body_write_buffers_size = worker_count * single_body_write_buffer_size;
|
||||
|
||||
const read_buffer_ptr = self.workers[0].read_buffer_ptr;
|
||||
const header_write_buffer_ptr = self.workers[0].header_write_buffer.ptr;
|
||||
const body_write_buffer_ptr = self.workers[0].body_write_buffer.ptr;
|
||||
|
||||
for (self.workers) |*worker| {
|
||||
worker.deinit(allocator);
|
||||
}
|
||||
|
||||
_ = linux.munmap(body_write_buffer_ptr, all_body_write_buffers_size);
|
||||
_ = linux.munmap(header_write_buffer_ptr, all_header_write_buffers_size);
|
||||
_ = linux.munmap(read_buffer_ptr, double_all_read_buffers_size);
|
||||
|
||||
allocator.free(self.threads);
|
||||
allocator.free(self.connection_buffer);
|
||||
allocator.free(self.workers);
|
||||
|
||||
self.fd.close();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
/// This method block until the server is stopped, which is achieved by storing
|
||||
/// `false` into `running`. You should use another thread or interruption
|
||||
/// handler to be able to stop the server.
|
||||
pub fn listen(self: *Server, running: *const std.atomic.Value(bool)) !void {
|
||||
var worker_running: std.atomic.Value(bool) = .init(running.load(.acquire));
|
||||
|
||||
var spawned: usize = 0;
|
||||
defer {
|
||||
log.debug("Storing `false` into worker_running.", .{});
|
||||
worker_running.store(false, .release);
|
||||
log.debug("Broadcasting connection queued condition variable.", .{});
|
||||
self.cond_connection_queued.broadcast();
|
||||
for (self.threads[0..spawned], 0..) |*thread, i| {
|
||||
log.debug("Joining the thread of worker #{d}.", .{i});
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
for (self.workers, 0..) |*worker, i| {
|
||||
log.debug("Spawning thread for worker #{d}.", .{i});
|
||||
self.threads[i] = try std.Thread.spawn(.{}, Worker.worker, .{ worker, self, &worker_running });
|
||||
spawned += 1;
|
||||
}
|
||||
|
||||
while (running.load(.acquire)) {
|
||||
var address: std.net.Address = undefined;
|
||||
var address_size: u32 = @sizeOf(std.net.Address);
|
||||
|
||||
log.debug("Accepting connection.", .{});
|
||||
const fd = self.fd.accept(&address.any, &address_size) catch |e| {
|
||||
log.err("Error while accepting connection: {}", .{e});
|
||||
continue;
|
||||
};
|
||||
log.debug("Accepted connection from {f}", .{address});
|
||||
|
||||
const timeout: linux.timeval = .{
|
||||
.sec = @intCast(self.read_timeout_us / std.time.us_per_s),
|
||||
.usec = @intCast(self.read_timeout_us % std.time.us_per_s),
|
||||
};
|
||||
try fd.setsockopt(linux.SOL.SOCKET, linux.SO.RCVTIMEO, std.mem.asBytes(&timeout));
|
||||
|
||||
const ssl: ?*openssl.Ssl = self.maybeInitSsl(fd) catch |e| {
|
||||
log.err("Error while estabilishing SSL connection: {}", .{e});
|
||||
fd.close();
|
||||
continue;
|
||||
};
|
||||
|
||||
{
|
||||
log.debug("Acquiring mutex.", .{});
|
||||
self.mutex.lock();
|
||||
log.debug("Acquired mutex.", .{});
|
||||
defer {
|
||||
log.debug("Unlocking mutex.", .{});
|
||||
self.mutex.unlock();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
if (self.connection_pool.pop()) |node| {
|
||||
const connection: *Connection = @fieldParentPtr("node", node);
|
||||
connection.reinit(address, fd, ssl);
|
||||
log.debug("Adding connection to {f} to the connection queue.", .{connection.address});
|
||||
self.connection_queue.prepend(node);
|
||||
break;
|
||||
}
|
||||
|
||||
log.debug("Waiting on connection freed condition variable.", .{});
|
||||
self.cond_connection_freed.wait(&self.mutex);
|
||||
log.debug("Woken up on connection freed condition variable.", .{});
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Signaling connection queued condition variable.", .{});
|
||||
self.cond_connection_queued.signal();
|
||||
} else {
|
||||
log.debug("Loaded `false` from running, the accept loop exited.", .{});
|
||||
}
|
||||
}
|
||||
|
||||
fn maybeInitSsl(self: *const Server, fd: FileDescriptor) !?*openssl.Ssl {
|
||||
if (self.ssl_ctx) |ssl_ctx| {
|
||||
const ssl = try openssl.Ssl.new(ssl_ctx);
|
||||
try ssl.setFd(fd);
|
||||
log.debug("Accepting SSL layer.", .{});
|
||||
try ssl.accept();
|
||||
log.debug("Accepted SSL layer.", .{});
|
||||
return ssl;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
fn err(rc: usize) !void {
|
||||
const e = errno(rc);
|
||||
return if (e != .SUCCESS) error.SystemError else {};
|
||||
}
|
||||
|
||||
fn errOrPtr(rc: usize) ![*]u8 {
|
||||
const e = errno(rc);
|
||||
return if (e != .SUCCESS) error.SystemError else @ptrFromInt(rc);
|
||||
}
|
||||
87
packages/web/src/UUID.zig
Normal file
87
packages/web/src/UUID.zig
Normal file
@@ -0,0 +1,87 @@
|
||||
const std = @import("std");
|
||||
const UUID = @This();
|
||||
|
||||
bytes: [16]u8,
|
||||
|
||||
pub const zero: UUID = .{ .bytes = @splat(0) };
|
||||
|
||||
pub fn v4() UUID {
|
||||
var bytes: [16]u8 = undefined;
|
||||
std.crypto.random.bytes(&bytes);
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||
return .{ .bytes = bytes };
|
||||
}
|
||||
|
||||
pub const namespaces = struct {
|
||||
/// Name string is a fully-qualified domain name
|
||||
///
|
||||
/// `6ba7b810-9dad-11d1-80b4-00c04fd430c8`
|
||||
pub const dns: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x10, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
|
||||
/// Name string is a URL
|
||||
///
|
||||
/// `6ba7b811-9dad-11d1-80b4-00c04fd430c8`
|
||||
pub const url: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x11, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
|
||||
/// Name string is an ISO OID
|
||||
///
|
||||
/// `6ba7b812-9dad-11d1-80b4-00c04fd430c8`
|
||||
pub const oid: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x12, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
|
||||
/// Name string is an X.500 DN (in DER or a text output format)
|
||||
///
|
||||
/// `6ba7b814-9dad-11d1-80b4-00c04fd430c8`
|
||||
pub const x500: UUID = .{ .bytes = .{ 0x6B, 0xA7, 0xB8, 0x14, 0x9D, 0xAD, 0x11, 0xD1, 0x80, 0xB4, 0x00, 0xC0, 0x4F, 0xD4, 0x30, 0xC8 } };
|
||||
};
|
||||
|
||||
pub fn v5(namespace: UUID, name: []const u8) UUID {
|
||||
const hash = blk: {
|
||||
var hasher = std.crypto.hash.Sha1.init(.{});
|
||||
hasher.update(namespace);
|
||||
hasher.update(name);
|
||||
break :blk hasher.finalResult();
|
||||
};
|
||||
|
||||
var bytes: [16]u8 = hash[0..16].*;
|
||||
bytes[6] = (bytes[6] & 0x0F) | 0x50;
|
||||
bytes[8] = (bytes[8] & 0x3F) | 0x80;
|
||||
|
||||
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 };
|
||||
|
||||
fn getCount(timestamp: u64) u32 {
|
||||
v7_lock.lock();
|
||||
defer v7_lock.unlock();
|
||||
|
||||
if (v7_last_timestamp.swap(timestamp, .monotonic) != timestamp) {
|
||||
v7_counter.store(0, .monotonic);
|
||||
}
|
||||
|
||||
return v7_counter.fetchAdd(1, .monotonic) % 4096;
|
||||
}
|
||||
|
||||
pub fn v7() 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);
|
||||
break :blk bytes;
|
||||
};
|
||||
|
||||
var bytes: [16]u8 = undefined;
|
||||
bytes[0] = @truncate(timestamp >> 40);
|
||||
bytes[1] = @truncate(timestamp >> 32);
|
||||
bytes[2] = @truncate(timestamp >> 24);
|
||||
bytes[3] = @truncate(timestamp >> 16);
|
||||
bytes[4] = @truncate(timestamp >> 8);
|
||||
bytes[5] = @truncate(timestamp);
|
||||
bytes[6] = (@as(u8, 7) << 4) | @as(u8, @truncate((count >> 8) & 0x0F));
|
||||
bytes[7] = @truncate(count);
|
||||
bytes[8] = (random[0] & 0x3F) | 0x80;
|
||||
@memcpy(bytes[9..16], random[1..8]);
|
||||
|
||||
return .{ .bytes = bytes };
|
||||
}
|
||||
321
packages/web/src/Worker.zig
Normal file
321
packages/web/src/Worker.zig
Normal file
@@ -0,0 +1,321 @@
|
||||
const std = @import("std");
|
||||
const Worker = @This();
|
||||
|
||||
const Connection = @import("Connection.zig");
|
||||
const FileDescriptor = @import("FileDescriptor.zig").FileDescriptor;
|
||||
const http = @import("http.zig");
|
||||
const Request = @import("Request.zig");
|
||||
const RequestHandler = @import("RequestHandler.zig");
|
||||
const Response = @import("Response.zig");
|
||||
const Server = @import("Server.zig");
|
||||
|
||||
const log = std.log.scoped(.Worker);
|
||||
|
||||
/// Integer unique for this worker. Has no functional meaning. Can be used for
|
||||
/// debugging and profiling.
|
||||
worker_id: usize,
|
||||
|
||||
read_buffer_ptr: [*]u8,
|
||||
read_buffer_size: usize,
|
||||
read_head: usize,
|
||||
read_tail: usize,
|
||||
|
||||
header_write_buffer: []u8,
|
||||
body_write_buffer: []u8,
|
||||
|
||||
header_hash_map: Request.HeaderHashMap,
|
||||
header_value_buffer: []Request.HeaderValue,
|
||||
|
||||
pub const Options = struct {
|
||||
worker_id: usize,
|
||||
|
||||
max_header_fields: u32,
|
||||
|
||||
read_buffer_ptr: [*]u8,
|
||||
read_buffer_size: usize,
|
||||
|
||||
header_write_buffer: []u8,
|
||||
body_write_buffer: []u8,
|
||||
};
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, options: Options) !Worker {
|
||||
var header_hash_map: Request.HeaderHashMap = .empty;
|
||||
try header_hash_map.ensureTotalCapacity(allocator, options.max_header_fields);
|
||||
errdefer header_hash_map.deinit(allocator);
|
||||
|
||||
const header_value_buffer = try allocator.alloc(Request.HeaderValue, options.max_header_fields);
|
||||
errdefer allocator.free(header_value_buffer);
|
||||
|
||||
return .{
|
||||
.worker_id = options.worker_id,
|
||||
|
||||
.read_buffer_ptr = options.read_buffer_ptr,
|
||||
.read_buffer_size = options.read_buffer_size,
|
||||
.read_head = 0,
|
||||
.read_tail = 0,
|
||||
|
||||
.header_write_buffer = options.header_write_buffer,
|
||||
.body_write_buffer = options.body_write_buffer,
|
||||
|
||||
.header_hash_map = header_hash_map,
|
||||
.header_value_buffer = header_value_buffer,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Worker, allocator: std.mem.Allocator) void {
|
||||
log.debug("[#{d}] Deinitializing Worker.", .{self.worker_id});
|
||||
|
||||
allocator.free(self.header_value_buffer);
|
||||
self.header_hash_map.deinit(allocator);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn worker(
|
||||
self: *Worker,
|
||||
server: *Server,
|
||||
running: *const std.atomic.Value(bool),
|
||||
) void {
|
||||
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id});
|
||||
server.mutex.lock();
|
||||
log.debug("[#{d}] Acquired mutex.", .{self.worker_id});
|
||||
defer {
|
||||
log.debug("[#{d}] Unlocking mutex.", .{self.worker_id});
|
||||
server.mutex.unlock();
|
||||
}
|
||||
|
||||
while (running.load(.acquire)) {
|
||||
if (server.connection_queue.pop()) |node| {
|
||||
const connection: *Connection = @fieldParentPtr("node", node);
|
||||
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();
|
||||
defer {
|
||||
log.debug("[#{d}] Acquiring mutex.", .{self.worker_id});
|
||||
server.mutex.lock();
|
||||
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();
|
||||
}
|
||||
|
||||
log.debug("[#{d}] Handling connection to {f}.", .{ self.worker_id, connection.address });
|
||||
self.handleConnection(server.request_handler, connection, running) catch |err| {
|
||||
log.err("[#{d}] Error while handling connection: {}", .{ self.worker_id, err });
|
||||
};
|
||||
} else {
|
||||
log.debug("[#{d}] Waiting on connection queued condition variable.", .{self.worker_id});
|
||||
server.cond_connection_queued.wait(&server.mutex);
|
||||
log.debug("[#{d}] Woken up on connection queued condition variable.", .{self.worker_id});
|
||||
}
|
||||
} else {
|
||||
log.debug("[#{d}] Loaded `false` from running, the worker loop exited.", .{self.worker_id});
|
||||
}
|
||||
}
|
||||
|
||||
fn handleConnection(
|
||||
self: *Worker,
|
||||
request_handler: RequestHandler,
|
||||
connection: *Connection,
|
||||
running: *const std.atomic.Value(bool),
|
||||
) !void {
|
||||
defer connection.deinit();
|
||||
|
||||
while (running.load(.acquire)) {
|
||||
const res = self.handleRequest(request_handler, connection) catch |err| {
|
||||
log.err("[#{d}] Error while handling request: {}", .{ self.worker_id, err });
|
||||
return err;
|
||||
};
|
||||
|
||||
if (!res) {
|
||||
log.debug("[#{d}] Request handler indicated to stop handling the connection to {f}.", .{ self.worker_id, connection.address });
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
log.debug("[#{d}] Loaded `false` from running, the connection handler loop exited.", .{self.worker_id});
|
||||
}
|
||||
}
|
||||
|
||||
fn handleRequest(
|
||||
self: *Worker,
|
||||
request_handler: RequestHandler,
|
||||
connection: *Connection,
|
||||
) !bool {
|
||||
self.header_hash_map.clearRetainingCapacity();
|
||||
|
||||
var request: Request = .{
|
||||
.method = undefined,
|
||||
.pathname = undefined,
|
||||
.headers = &self.header_hash_map,
|
||||
.body = undefined,
|
||||
};
|
||||
var response: Response = .init(connection, self.header_write_buffer, self.body_write_buffer);
|
||||
var parser: http.Parser = .init();
|
||||
|
||||
var next_header_index: usize = 0;
|
||||
var ignore: bool = false;
|
||||
var client_closed: bool = false;
|
||||
|
||||
var leftover_bytes = self.read_tail - self.read_head;
|
||||
const max_read_tail = self.read_head + self.read_buffer_size;
|
||||
|
||||
while (true) {
|
||||
var bytes_read: usize = undefined;
|
||||
var chunk: []const u8 = undefined;
|
||||
|
||||
if (leftover_bytes > 0) {
|
||||
bytes_read = leftover_bytes;
|
||||
chunk = self.read_buffer_ptr[self.read_tail - leftover_bytes .. self.read_tail];
|
||||
leftover_bytes = 0;
|
||||
} else {
|
||||
const read_tail = self.read_tail;
|
||||
bytes_read = connection.read(self.read_buffer_ptr[read_tail..max_read_tail]) catch |err| switch (err) {
|
||||
error.Timeout => {
|
||||
log.debug("[#{d}] Connection to {f} timed out.", .{ self.worker_id, connection.address });
|
||||
return false;
|
||||
},
|
||||
else => return err,
|
||||
};
|
||||
if (bytes_read == 0) {
|
||||
log.debug("[#{d}] Read zero bytes from connection to {f}.", .{ self.worker_id, connection.address });
|
||||
return false;
|
||||
}
|
||||
chunk = self.read_buffer_ptr[read_tail .. read_tail + bytes_read];
|
||||
self.read_tail += bytes_read;
|
||||
}
|
||||
|
||||
const res = parser.consume(chunk) catch |err| {
|
||||
switch (err) {
|
||||
error.MethodNotSupported => try closeWith(&response, http.status.method_not_allowed),
|
||||
error.HttpVersionNotSupported => try closeWith(&response, http.status.http_version_not_supported),
|
||||
error.SyntaxError => try closeWith(&response, http.status.bad_request),
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const done = if (res.result) |result| std.meta.activeTag(result) == .body else false;
|
||||
|
||||
if (self.read_tail - self.read_head >= self.read_buffer_size and !done) {
|
||||
if (parser.state == .body) {
|
||||
try closeWith(&response, http.status.content_too_large);
|
||||
} else {
|
||||
try closeWith(&response, http.status.request_header_fields_too_large);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (res.result) |result| {
|
||||
switch (result) {
|
||||
.method => |method| request.method = method,
|
||||
.pathname => |pathname| request.pathname = pathname,
|
||||
.header => |header| blk: {
|
||||
if (header.isNamedKnown(.Connection) and std.mem.eql(u8, header.value, "close")) {
|
||||
client_closed = true;
|
||||
}
|
||||
|
||||
if (ignore) {
|
||||
break :blk;
|
||||
}
|
||||
|
||||
if (next_header_index >= self.header_value_buffer.len or self.header_hash_map.available == 0) {
|
||||
// TODO Here, we could ignore, but make sure this does
|
||||
// not clash with the other "request too long" checks
|
||||
// (i.e. be careful not to double respond).
|
||||
_ = &ignore;
|
||||
|
||||
try closeWith(&response, http.status.request_header_fields_too_large);
|
||||
return false;
|
||||
} else {
|
||||
const entry = self.header_hash_map.getOrPutAssumeCapacity(header.name);
|
||||
const header_value = &self.header_value_buffer[next_header_index];
|
||||
header_value.* = .{ .node = .{}, .value = header.value };
|
||||
next_header_index += 1;
|
||||
|
||||
if (!entry.found_existing) {
|
||||
entry.value_ptr.* = .{
|
||||
.len = 0,
|
||||
.list = .{},
|
||||
};
|
||||
}
|
||||
|
||||
entry.value_ptr.list.prepend(&header_value.node);
|
||||
entry.value_ptr.len += 1;
|
||||
}
|
||||
},
|
||||
.end_of_headers => {},
|
||||
.body => |body| {
|
||||
request.body = body;
|
||||
|
||||
if (!ignore) {
|
||||
request_handler.handle(&request, &response) catch |err| {
|
||||
if (response.state == .init) {
|
||||
response.header_writer.end = 0;
|
||||
response.body_writer.end = 0;
|
||||
|
||||
const error_name = @errorName(err);
|
||||
|
||||
try response.body_writer.print("Internal Server Error\n{s}\n", .{error_name});
|
||||
|
||||
try response.header_writer.writeAll(http.status.internal_server_error);
|
||||
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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (response.state == .init) {
|
||||
const no_headers = response.header_writer.end > 0;
|
||||
const no_body = response.body_writer.end > 0;
|
||||
|
||||
if (no_headers) {
|
||||
if (no_body) {
|
||||
try response.header_writer.writeAll(http.status.no_content);
|
||||
try response.header_writer.writeAll("\r\n");
|
||||
|
||||
response.sendHeadersOnly();
|
||||
} else {
|
||||
try response.header_writer.writeAll(http.status.ok);
|
||||
try response.header_writer.writeAll("Content-Type: application/octet-stream");
|
||||
try response.header_writer.print("Content-Length: {d}\r\n", .{response.body_writer.end});
|
||||
try response.header_writer.writeAll("\r\n");
|
||||
|
||||
response.sendHeadersAndBody();
|
||||
}
|
||||
} else {
|
||||
if (no_body) {
|
||||
response.sendHeadersOnly();
|
||||
} else {
|
||||
response.sendHeadersAndBody();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
leftover_bytes = bytes_read - res.consumed;
|
||||
self.read_head = (self.read_tail - leftover_bytes) & ~(self.read_buffer_size - 1);
|
||||
self.read_tail = self.read_head + leftover_bytes;
|
||||
return !client_closed;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
leftover_bytes = bytes_read - res.consumed;
|
||||
}
|
||||
}
|
||||
|
||||
fn closeWith(response: *Response, status_line: []const u8) !void {
|
||||
// This function is meant to be called before a request handler gets to do
|
||||
// anything.
|
||||
std.debug.assert(response.header_writer.end == 0);
|
||||
std.debug.assert(response.body_writer.end == 0);
|
||||
std.debug.assert(response.state == .init);
|
||||
|
||||
try response.header_writer.writeAll(status_line);
|
||||
try response.header_writer.writeAll("Connection: close\r\n");
|
||||
try response.header_writer.writeAll("\r\n");
|
||||
}
|
||||
8
packages/web/src/http.zig
Normal file
8
packages/web/src/http.zig
Normal file
@@ -0,0 +1,8 @@
|
||||
const std = @import("std");
|
||||
|
||||
pub const FieldName = @import("http/FieldName.zig").FieldName;
|
||||
pub const Header = @import("http/Header.zig");
|
||||
pub const KnownFieldName = @import("http/KnownFieldName.zig").KnownFieldName;
|
||||
pub const Method = @import("http/Method.zig").Method;
|
||||
pub const Parser = @import("http/Parser.zig");
|
||||
pub const status = @import("http/status.zig");
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user