Add experimental shader compiler and foundations of SPIRV backend
This commit is contained in:
parent
d1c823df41
commit
3339845d57
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +0,0 @@
|
|||||||
[submodule "ext/spirv-cross"]
|
|
||||||
path = ext/spirv-cross
|
|
||||||
url = https://github.com/KhronosGroup/SPIRV-Cross/
|
|
20
.vscode/launch.json
vendored
20
.vscode/launch.json
vendored
@ -2,13 +2,23 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Runner",
|
"name": "Debug Test",
|
||||||
"type": "gdb",
|
"type": "gdb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"target": "${workspaceRoot}/demos/canvas.out",
|
"target": "${workspaceRoot}/zig-out/bin/test",
|
||||||
"cwd": "${workspaceRoot}/demos/",
|
"cwd": "${workspaceRoot}/src/demos/",
|
||||||
"valuesFormatting": "prettyPrinters",
|
"valuesFormatting": "parseText",
|
||||||
"preLaunchTask": "Build All"
|
"preLaunchTask": "Build"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "Debug Graphics Demo",
|
||||||
|
"type": "gdb",
|
||||||
|
"request": "launch",
|
||||||
|
"target": "${workspaceRoot}/zig-out/bin/graphics_demo",
|
||||||
|
"cwd": "${workspaceRoot}/src/demos/",
|
||||||
|
"valuesFormatting": "parseText",
|
||||||
|
"preLaunchTask": "Build"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
25
.vscode/settings.json
vendored
25
.vscode/settings.json
vendored
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"files.insertFinalNewline": true,
|
|
||||||
"files.trimTrailingWhitespace": true,
|
|
||||||
"debug.console.collapseIdenticalLines": false,
|
|
||||||
|
|
||||||
"[zig]": {
|
|
||||||
"editor.formatOnSave": false,
|
|
||||||
"files.eol": "\n",
|
|
||||||
"editor.minimap.maxColumn": 120,
|
|
||||||
"editor.detectIndentation": false,
|
|
||||||
"editor.insertSpaces": false,
|
|
||||||
"editor.rulers": [120],
|
|
||||||
},
|
|
||||||
|
|
||||||
"files.exclude": {
|
|
||||||
"**/.git": true,
|
|
||||||
"**/.svn": true,
|
|
||||||
"**/.hg": true,
|
|
||||||
"**/CVS": true,
|
|
||||||
"**/.DS_Store": true,
|
|
||||||
"**/Thumbs.db": true,
|
|
||||||
"**/.zig-cache": true,
|
|
||||||
"zig-out": true,
|
|
||||||
},
|
|
||||||
}
|
|
8
.vscode/tasks.json
vendored
8
.vscode/tasks.json
vendored
@ -2,14 +2,16 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"tasks": [
|
"tasks": [
|
||||||
{
|
{
|
||||||
"label": "Build All",
|
"label": "Build",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "zig build",
|
"command": "zig build",
|
||||||
|
"problemMatcher": "$zig",
|
||||||
|
|
||||||
"group": {
|
"group": {
|
||||||
"kind": "build",
|
"kind": "build",
|
||||||
"isDefault": true
|
"isDefault": true
|
||||||
},
|
},
|
||||||
"problemMatcher": "$zig",
|
|
||||||
"presentation": {
|
"presentation": {
|
||||||
"echo": true,
|
"echo": true,
|
||||||
"reveal": "silent",
|
"reveal": "silent",
|
||||||
@ -19,6 +21,6 @@
|
|||||||
"clear": true,
|
"clear": true,
|
||||||
"revealProblems": "onProblem",
|
"revealProblems": "onProblem",
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
1
.zigversion
Normal file
1
.zigversion
Normal file
@ -0,0 +1 @@
|
|||||||
|
0.14.0
|
355
build.zig
355
build.zig
@ -1,273 +1,132 @@
|
|||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const ImportList = std.ArrayList(struct {
|
const CommonArgs = struct {
|
||||||
name: []const u8,
|
|
||||||
module: *std.Build.Module
|
|
||||||
});
|
|
||||||
|
|
||||||
const Project = struct {
|
|
||||||
target: std.Build.ResolvedTarget,
|
target: std.Build.ResolvedTarget,
|
||||||
optimize: std.builtin.OptimizeMode,
|
optimize: std.builtin.OptimizeMode,
|
||||||
imports: ImportList,
|
ona_module: *std.Build.Module,
|
||||||
|
|
||||||
pub fn find_demos(self: Project, step: *std.Build.Step) void {
|
|
||||||
const absolute_path = step.owner.path("demos/").getPath(step.owner);
|
|
||||||
|
|
||||||
var dir = std.fs.openDirAbsolute(absolute_path, .{.iterate = true}) catch |open_error| {
|
|
||||||
std.log.warn("failed to open demos directory at {s} with error {s}, skipping...", .{
|
|
||||||
absolute_path,
|
|
||||||
@errorName(open_error),
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
};
|
|
||||||
|
|
||||||
defer {
|
|
||||||
dir.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
var entries = dir.walk(step.owner.allocator) catch @panic("OOM");
|
|
||||||
|
|
||||||
defer {
|
|
||||||
entries.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (entries.next() catch @panic("I/O failure")) |entry| {
|
|
||||||
if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const source_path = step.owner.pathJoin(&.{"demos", entry.basename});
|
|
||||||
var path_buffer = [_:0]u8{0} ** 255;
|
|
||||||
|
|
||||||
const demo = step.owner.addExecutable(.{
|
|
||||||
.name = std.fmt.bufPrint(&path_buffer, "{s}.out", .{std.fs.path.stem(entry.basename)}) catch {
|
|
||||||
@panic("a demo file name is too long");
|
|
||||||
},
|
|
||||||
|
|
||||||
.root_source_file = step.owner.path(source_path),
|
|
||||||
.target = self.target,
|
|
||||||
.optimize = self.optimize,
|
|
||||||
});
|
|
||||||
|
|
||||||
for (self.imports.items) |import| {
|
|
||||||
demo.root_module.addImport(import.name, import.module);
|
|
||||||
}
|
|
||||||
|
|
||||||
step.dependOn(&step.owner.addInstallArtifact(demo, .{
|
|
||||||
.dest_dir = .{
|
|
||||||
.override = .{.custom = "../demos"},
|
|
||||||
},
|
|
||||||
}).step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_tests(self: Project, step: *std.Build.Step) void {
|
|
||||||
for (self.imports.items) |import| {
|
|
||||||
step.dependOn(&step.owner.addRunArtifact(step.owner.addTest(.{
|
|
||||||
.root_source_file = import.module.root_source_file.?,
|
|
||||||
.target = self.target,
|
|
||||||
.optimize = self.optimize,
|
|
||||||
})).step);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_module(self: *Project, b: *std.Build, comptime name: []const u8, options: std.Build.Module.CreateOptions) !*std.Build.Module {
|
|
||||||
const cwd = std.fs.cwd();
|
|
||||||
var corrected_options = options;
|
|
||||||
|
|
||||||
if (corrected_options.root_source_file == null) {
|
|
||||||
corrected_options.root_source_file = b.path("src/" ++ name ++ "/" ++ name ++ ".zig");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (corrected_options.target == null) {
|
|
||||||
corrected_options.target = self.target;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (corrected_options.optimize == null) {
|
|
||||||
corrected_options.optimize = self.optimize;
|
|
||||||
}
|
|
||||||
|
|
||||||
const module = b.addModule(name, corrected_options);
|
|
||||||
|
|
||||||
try self.imports.append(.{
|
|
||||||
.name = name,
|
|
||||||
.module = module
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Probably want to make it search the same path as any explicit root_source_path override for shaders.
|
|
||||||
const shaders_path = "src/" ++ name ++ "/shaders/";
|
|
||||||
|
|
||||||
var shaders_dir = cwd.openDir(shaders_path, .{.iterate = true}) catch |open_error| {
|
|
||||||
return switch (open_error) {
|
|
||||||
error.FileNotFound, error.NotDir => module,
|
|
||||||
else => open_error,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
defer {
|
|
||||||
shaders_dir.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
var shaders_entries = try shaders_dir.walk(b.allocator);
|
|
||||||
|
|
||||||
defer {
|
|
||||||
shaders_entries.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (try shaders_entries.next()) |entry| {
|
|
||||||
if (entry.kind != .file) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
|
|
||||||
|
|
||||||
if (!is_shader_file) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var binary_buffer = [_:0]u8{0} ** 255;
|
|
||||||
const binary_name = try std.fmt.bufPrint(&binary_buffer, "{s}.spv", .{entry.path});
|
|
||||||
|
|
||||||
const glslang_validator_args = [_][]const u8{
|
|
||||||
"glslangValidator",
|
|
||||||
"-V",
|
|
||||||
b.pathJoin(&.{shaders_path, entry.path}),
|
|
||||||
"-o",
|
|
||||||
b.pathJoin(&.{shaders_path, binary_name}),
|
|
||||||
};
|
|
||||||
|
|
||||||
shaders_dir.access(binary_name, .{.mode = .read_only}) catch {
|
|
||||||
const output = b.run(&glslang_validator_args);
|
|
||||||
|
|
||||||
std.log.info("{s}", .{output[0 .. output.len - 1]});
|
|
||||||
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ((try shaders_dir.statFile(entry.path)).mtime > (try shaders_dir.statFile(binary_name)).mtime) {
|
|
||||||
const output = b.run(&glslang_validator_args);
|
|
||||||
|
|
||||||
std.log.info("{s}", .{output[0 .. output.len - 1]});
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return module;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) void {
|
||||||
var project = Project{
|
const target = b.standardTargetOptions(.{});
|
||||||
.imports = ImportList.init(b.allocator),
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
.target = b.standardTargetOptions(.{}),
|
|
||||||
.optimize = b.standardOptimizeOption(.{}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const sokol_dependency = b.dependency("sokol", .{
|
const coral_module = b.addModule("coral", .{
|
||||||
.target = project.target,
|
.root_source_file = b.path("src/coral/coral.zig"),
|
||||||
.optimize = project.optimize,
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
const ona_module = try project.add_module(b, "ona", .{});
|
const ext_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/ext/ext.zig"),
|
||||||
const hid_module = try project.add_module(b, "hid", .{
|
.target = target,
|
||||||
.imports = &.{
|
.optimize = optimize,
|
||||||
.{
|
.link_libc = true,
|
||||||
.name = "ona",
|
|
||||||
.module = ona_module,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const gfx_module = try project.add_module(b, "gfx", .{
|
ext_module.linkSystemLibrary("SDL3", .{
|
||||||
.imports = &.{
|
|
||||||
.{
|
|
||||||
.name = "sokol",
|
|
||||||
.module = sokol_dependency.module("sokol"),
|
|
||||||
},
|
|
||||||
|
|
||||||
.{
|
|
||||||
.name = "ona",
|
|
||||||
.module = ona_module,
|
|
||||||
},
|
|
||||||
|
|
||||||
.{
|
|
||||||
.name = "hid",
|
|
||||||
.module = hid_module,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
gfx_module.addIncludePath(b.path("ext/"));
|
|
||||||
|
|
||||||
gfx_module.linkLibrary(spirv_cross: {
|
|
||||||
const dir = "ext/spirv-cross/";
|
|
||||||
|
|
||||||
const sources = [_][]const u8{
|
|
||||||
"spirv_cross.cpp",
|
|
||||||
"spirv_parser.cpp",
|
|
||||||
"spirv_cross_parsed_ir.cpp",
|
|
||||||
"spirv_cfg.cpp",
|
|
||||||
"spirv_glsl.cpp",
|
|
||||||
"spirv_msl.cpp",
|
|
||||||
"spirv_hlsl.cpp",
|
|
||||||
"spirv_reflect.cpp",
|
|
||||||
"spirv_cross_util.cpp",
|
|
||||||
"spirv_cross_c.cpp",
|
|
||||||
};
|
|
||||||
|
|
||||||
const lib = b.addStaticLibrary(.{
|
|
||||||
.name = "spirvcross",
|
|
||||||
.target = project.target,
|
|
||||||
.optimize = project.optimize,
|
|
||||||
});
|
|
||||||
|
|
||||||
switch (lib.rootModuleTarget().abi) {
|
|
||||||
.msvc => lib.linkLibC(),
|
|
||||||
else => lib.linkLibCpp(),
|
|
||||||
}
|
|
||||||
|
|
||||||
inline for (sources) |src| {
|
|
||||||
lib.addCSourceFile(.{
|
|
||||||
.file = b.path(dir ++ src),
|
|
||||||
|
|
||||||
.flags = &.{
|
|
||||||
"-fstrict-aliasing",
|
|
||||||
"-DSPIRV_CROSS_C_API_GLSL",
|
|
||||||
"-DSPIRV_CROSS_C_API_HLSL",
|
|
||||||
"-DSPIRV_CROSS_C_API_MSL",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break: spirv_cross lib;
|
|
||||||
});
|
|
||||||
|
|
||||||
gfx_module.linkSystemLibrary("SDL2", .{
|
|
||||||
.needed = true,
|
.needed = true,
|
||||||
.preferred_link_mode = .dynamic,
|
.preferred_link_mode = .dynamic,
|
||||||
});
|
});
|
||||||
|
|
||||||
gfx_module.link_libc = true;
|
const ona_module = b.addModule("ona", .{
|
||||||
|
.root_source_file = b.path("src/ona/ona.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.link_libc = true,
|
||||||
|
|
||||||
_ = try project.add_module(b, "gui", .{
|
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{
|
.{
|
||||||
.name = "ona",
|
.name = "ext",
|
||||||
.module = ona_module,
|
.module = ext_module,
|
||||||
},
|
},
|
||||||
|
|
||||||
.{
|
.{
|
||||||
.name = "gfx",
|
.name = "coral",
|
||||||
.module = gfx_module,
|
.module = coral_module,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
project.find_demos(b.step("demos", "Build demos"));
|
scan_demos(b, .{
|
||||||
project.find_tests(b.step("tests", "Build and run tests"));
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
.ona_module = ona_module,
|
||||||
|
});
|
||||||
|
|
||||||
|
const test_tests = b.addTest(.{
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path("src/tests.zig"),
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
|
||||||
|
.imports = &.{
|
||||||
|
.{
|
||||||
|
.name = "coral",
|
||||||
|
.module = coral_module,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const test_step = b.step("test", "Run unit tests");
|
||||||
|
|
||||||
|
test_step.dependOn(&b.addRunArtifact(test_tests).step);
|
||||||
|
test_step.dependOn(&b.addInstallArtifact(test_tests, .{}).step);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scan_demos(b: *std.Build, common: CommonArgs) void {
|
||||||
|
const build_demos_step = b.step("demos", "Build demos");
|
||||||
|
|
||||||
|
b.default_step.dependOn(build_demos_step);
|
||||||
|
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
|
||||||
|
var demos_dir = cwd.openDir("src/demos/", .{ .iterate = true }) catch {
|
||||||
|
return build_demos_step.dependOn(&b.addFail("failed to open demo files directory").step);
|
||||||
|
};
|
||||||
|
|
||||||
|
defer {
|
||||||
|
demos_dir.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
var demos_iterator = demos_dir.iterate();
|
||||||
|
|
||||||
|
while (demos_iterator.next() catch {
|
||||||
|
return build_demos_step.dependOn(&b.addFail("failed to iterate over next entry in demos directory").step);
|
||||||
|
}) |entry| {
|
||||||
|
if (entry.kind != .file) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std.mem.eql(u8, std.fs.path.extension(entry.name), ".zig")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const demo_executable = b.addExecutable(.{
|
||||||
|
.name = std.fmt.allocPrint(b.allocator, "{s}_demo", .{std.fs.path.stem(entry.name)}) catch {
|
||||||
|
return build_demos_step.dependOn(&b.addFail("failed to allocate demo name buffer").step);
|
||||||
|
},
|
||||||
|
|
||||||
|
.root_module = b.createModule(.{
|
||||||
|
.root_source_file = b.path(b.pathJoin(&.{ "src/demos/", entry.name })),
|
||||||
|
.target = common.target,
|
||||||
|
.optimize = common.optimize,
|
||||||
|
|
||||||
|
.imports = &.{
|
||||||
|
.{
|
||||||
|
.name = "ona",
|
||||||
|
.module = common.ona_module,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
demo_executable.linkSystemLibrary2("SDL3", .{
|
||||||
|
.needed = true,
|
||||||
|
.preferred_link_mode = .dynamic,
|
||||||
|
});
|
||||||
|
|
||||||
|
const demo_installation = b.addInstallArtifact(demo_executable, .{});
|
||||||
|
|
||||||
|
build_demos_step.dependOn(&demo_installation.step);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,12 @@
|
|||||||
.{
|
.{
|
||||||
.name = "ona",
|
.name = .ona,
|
||||||
.version = "0.0.1",
|
.version = "0.0.0",
|
||||||
.minimum_zig_version = "0.13.0",
|
.fingerprint = 0x7d0142e88b22421d,
|
||||||
|
.minimum_zig_version = "0.14.0",
|
||||||
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"src",
|
|
||||||
"ext",
|
|
||||||
"build.zig",
|
"build.zig",
|
||||||
"build.zig.zon",
|
"build.zig.zon",
|
||||||
"LICENSE",
|
"src",
|
||||||
"README.md",
|
|
||||||
},
|
|
||||||
|
|
||||||
.dependencies = .{
|
|
||||||
.sokol = .{
|
|
||||||
.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
|
|
||||||
.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
|
|
||||||
},
|
|
||||||
|
|
||||||
.@"spirv-cross" = . {
|
|
||||||
.path = "./ext/spirv-cross",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
const gfx = @import("gfx");
|
|
||||||
|
|
||||||
const gui = @import("gui");
|
|
||||||
|
|
||||||
const hid = @import("hid");
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
pub const main = ona.App.game
|
|
||||||
.with_module(gfx)
|
|
||||||
.with_module(hid)
|
|
||||||
.with_module(gui)
|
|
||||||
.with_system(.load, ona.system_fn(load), .{ .label = "Hello Ona" }).build();
|
|
||||||
|
|
||||||
fn load(canvas: ona.Write(gui.Canvas)) !void {
|
|
||||||
const item = try canvas.state.append("", .{
|
|
||||||
.x = 0,
|
|
||||||
.y = 0,
|
|
||||||
.width = 256,
|
|
||||||
.height = 256,
|
|
||||||
});
|
|
||||||
|
|
||||||
try canvas.state.set_block(item, .{
|
|
||||||
.has_color = gfx.colors.purple,
|
|
||||||
});
|
|
||||||
|
|
||||||
try canvas.state.set_label(item, .{
|
|
||||||
.has_text = "Hello, world",
|
|
||||||
});
|
|
||||||
|
|
||||||
const item2 = try canvas.state.append("", .{
|
|
||||||
.x = 128,
|
|
||||||
.y = 128,
|
|
||||||
.width = 256,
|
|
||||||
.height = 256,
|
|
||||||
});
|
|
||||||
|
|
||||||
try canvas.state.set_block(item2, .{
|
|
||||||
.has_color = .{0, 1, 0, 1},
|
|
||||||
});
|
|
||||||
|
|
||||||
canvas.state.set_parent(item2, item);
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
|||||||
#version 430
|
|
||||||
|
|
||||||
// Adapted from: https://www.shadertoy.com/view/4sf3Dr
|
|
||||||
|
|
||||||
layout (binding = 0) uniform sampler2D sprite;
|
|
||||||
|
|
||||||
layout (location = 0) in vec4 color;
|
|
||||||
layout (location = 1) in vec2 uv;
|
|
||||||
|
|
||||||
layout (location = 0) out vec4 texel;
|
|
||||||
|
|
||||||
layout (binding = 0) uniform Effect {
|
|
||||||
float screen_width;
|
|
||||||
float screen_height;
|
|
||||||
float time;
|
|
||||||
};
|
|
||||||
|
|
||||||
vec3 scanline(vec2 coord, vec3 screen) {
|
|
||||||
screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02;
|
|
||||||
return screen;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2 crt(vec2 coord, float bend) {
|
|
||||||
// put in symmetrical coords
|
|
||||||
coord = (coord - 0.5) * 2.0;
|
|
||||||
|
|
||||||
coord *= 1.0;
|
|
||||||
|
|
||||||
// deform coords
|
|
||||||
coord.x *= 1.0 + pow((abs(coord.y) / bend), 2.0);
|
|
||||||
coord.y *= 1.0 + pow((abs(coord.x) / bend), 2.0);
|
|
||||||
|
|
||||||
// transform back to 0.0 - 1.0 space
|
|
||||||
coord = (coord / 2.0) + 0.5;
|
|
||||||
|
|
||||||
return coord;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 crtCoords = crt(uv, 4.8);
|
|
||||||
|
|
||||||
// Split the color channels
|
|
||||||
texel.rgb = texture(sprite, crtCoords).rgb;
|
|
||||||
texel.a = 1;
|
|
||||||
|
|
||||||
vec2 screenSpace = crtCoords * vec2(screen_width, screen_height);
|
|
||||||
texel.rgb = scanline(screenSpace, texel.rgb);
|
|
||||||
}
|
|
BIN
demos/crt.frag.spv
(Stored with Git LFS)
BIN
demos/crt.frag.spv
(Stored with Git LFS)
Binary file not shown.
@ -1,112 +0,0 @@
|
|||||||
const gfx = @import("gfx");
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const CRT = extern struct {
|
|
||||||
width: f32,
|
|
||||||
height: f32,
|
|
||||||
time: f32,
|
|
||||||
padding: [4]u8 = undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Effects = struct {
|
|
||||||
render_texture: gfx.Texture = .default,
|
|
||||||
image_textures: [2]gfx.Texture = [_]gfx.Texture{.default} ** 2,
|
|
||||||
last_time: f64 = 0,
|
|
||||||
image_index: usize = 0,
|
|
||||||
crt_effect: gfx.Effect = .default,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn load(display: ona.Write(gfx.Display), effects: ona.Write(Effects), assets: ona.Write(gfx.Assets)) !void {
|
|
||||||
display.state.width, display.state.height = .{1280, 720};
|
|
||||||
|
|
||||||
effects.state.render_texture = try assets.state.load_texture(.{
|
|
||||||
.format = .rgba8,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.render = .{
|
|
||||||
.width = display.state.width,
|
|
||||||
.height = display.state.height,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
effects.state.crt_effect = try assets.state.load_effect_file(ona.files.bundle, "./crt.frag.spv");
|
|
||||||
|
|
||||||
var descs = gfx.Descs.init(ona.heap.allocator);
|
|
||||||
|
|
||||||
defer {
|
|
||||||
descs.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
effects.state.image_textures = .{
|
|
||||||
try assets.state.load_texture(try descs.checker_texture(.{
|
|
||||||
.colors = .{gfx.colors.black, gfx.colors.purple},
|
|
||||||
.width = 8,
|
|
||||||
.height = 8,
|
|
||||||
})),
|
|
||||||
|
|
||||||
try assets.state.load_texture(try descs.checker_texture(.{
|
|
||||||
.colors = .{gfx.colors.black, gfx.colors.grey},
|
|
||||||
.width = 8,
|
|
||||||
.height = 8,
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const main = ona.App.game
|
|
||||||
.with_module(gfx)
|
|
||||||
.with_state(Effects{})
|
|
||||||
.with_system(.load, ona.system_fn(load), .{.label = "load effects"})
|
|
||||||
.with_system(.update, ona.system_fn(update), .{.label = "update effects"})
|
|
||||||
.with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build();
|
|
||||||
|
|
||||||
fn update(effects: ona.Write(Effects), loop: ona.Read(ona.Loop)) void {
|
|
||||||
const update_seconds = 5;
|
|
||||||
|
|
||||||
if ((loop.state.elapsed_time - effects.state.last_time) > update_seconds) {
|
|
||||||
effects.state.image_index = (effects.state.image_index + 1) % effects.state.image_textures.len;
|
|
||||||
effects.state.last_time = loop.state.elapsed_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(commands: gfx.Commands, effects: ona.Write(Effects), loop: ona.Read(ona.Loop), display: ona.Write(gfx.Display)) !void {
|
|
||||||
try commands.set_target(.{
|
|
||||||
.texture = effects.state.render_texture,
|
|
||||||
.clear_color = gfx.colors.black,
|
|
||||||
.clear_depth = 0,
|
|
||||||
.clear_stencil = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
const width: f32 = @floatFromInt(display.state.width);
|
|
||||||
const height: f32 = @floatFromInt(display.state.height);
|
|
||||||
|
|
||||||
try commands.draw_texture(.{
|
|
||||||
.texture = effects.state.image_textures[effects.state.image_index],
|
|
||||||
.size = .{width, height},
|
|
||||||
});
|
|
||||||
|
|
||||||
try commands.set_effect(.{
|
|
||||||
.effect = effects.state.crt_effect,
|
|
||||||
|
|
||||||
.properties = std.mem.asBytes(&CRT{
|
|
||||||
.width = width,
|
|
||||||
.height = height,
|
|
||||||
.time = @floatCast(loop.state.elapsed_time),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
try commands.set_target(.{
|
|
||||||
.clear_color = null,
|
|
||||||
.clear_depth = null,
|
|
||||||
.clear_stencil = null,
|
|
||||||
});
|
|
||||||
|
|
||||||
try commands.draw_texture(.{
|
|
||||||
.texture = effects.state.render_texture,
|
|
||||||
.size = .{width, height},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
|||||||
const gfx = @import("gfx");
|
|
||||||
|
|
||||||
const hid = @import("hid");
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Visuals = struct {
|
|
||||||
spawned: SpawnMap = SpawnMap.init(ona.heap.allocator),
|
|
||||||
random: std.Random.Xoroshiro128,
|
|
||||||
mouse_position: @Vector(2, f32) = @splat(0),
|
|
||||||
|
|
||||||
const SpawnMap = ona.SlotMap(struct {
|
|
||||||
color: gfx.Color,
|
|
||||||
position: gfx.Vector2,
|
|
||||||
rotation: f32,
|
|
||||||
lifetime_seconds: f32,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
fn cleanup(visuals: ona.Write(Visuals)) !void {
|
|
||||||
visuals.state.spawned.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const main = ona.App.game
|
|
||||||
.with_module(gfx)
|
|
||||||
.with_module(hid)
|
|
||||||
.with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)})
|
|
||||||
.with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"})
|
|
||||||
.with_system(.render, ona.system_fn(render), .{.label = "render visuals"})
|
|
||||||
.with_system(.exit, ona.system_fn(cleanup), .{.label = "clean up visuals"}).build();
|
|
||||||
|
|
||||||
fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: ona.Read(gfx.Display)) !void {
|
|
||||||
const float_speed = 6;
|
|
||||||
|
|
||||||
for (0 .. visuals.state.spawned.next) |i| {
|
|
||||||
const spawned = visuals.state.spawned.get(i) orelse {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
spawned.lifetime_seconds -= 1.0 / 60.0;
|
|
||||||
spawned.position -= .{0, float_speed};
|
|
||||||
|
|
||||||
if (spawned.lifetime_seconds <= 0) {
|
|
||||||
std.debug.assert(visuals.state.spawned.remove(i) != null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const random = visuals.state.random.random();
|
|
||||||
const width: f32 = @floatFromInt(display.state.width);
|
|
||||||
const height: f32 = @floatFromInt(display.state.height);
|
|
||||||
|
|
||||||
for (events.messages()) |event| {
|
|
||||||
switch (event) {
|
|
||||||
.key_down => {
|
|
||||||
_ = try visuals.state.spawned.insert(.{
|
|
||||||
.lifetime_seconds = 2.5 + (5 * random.float(f32)),
|
|
||||||
.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)},
|
|
||||||
.position = .{width * random.float(f32), height},
|
|
||||||
.rotation = std.math.pi * random.float(f32),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
.mouse_down => {
|
|
||||||
_ = try visuals.state.spawned.insert(.{
|
|
||||||
.lifetime_seconds = 2.5 + (5 * random.float(f32)),
|
|
||||||
.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)},
|
|
||||||
.position = visuals.state.mouse_position,
|
|
||||||
.rotation = std.math.pi * random.float(f32),
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
.mouse_motion => |motion| {
|
|
||||||
visuals.state.mouse_position = motion.absolute_position;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(visuals: ona.Write(Visuals), commands: gfx.Commands) !void {
|
|
||||||
for (visuals.state.spawned.values()) |visual| {
|
|
||||||
try commands.draw_texture(.{
|
|
||||||
.anchor = @splat(0.5),
|
|
||||||
.position = visual.position,
|
|
||||||
.tint = visual.color,
|
|
||||||
.size = @as(gfx.Vector2, @splat(64)),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
Subproject commit 6fd1f75636b1c424b809ad8a84804654cf5ae48b
|
|
32
readme.md
32
readme.md
@ -13,9 +13,9 @@
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
Ona is a straightforward game engine with the aim of staying reasonably lightweight through a modular architecture.
|
Ona is the Catalan word for "wave".
|
||||||
|
|
||||||
Ona is also the Catalan word for "wave".
|
Ona is also a straightforward game engine with the aim of staying reasonably lightweight through a modular architecture.
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
@ -36,32 +36,25 @@ Ona is also the Catalan word for "wave".
|
|||||||
Ona currently depends the following third-party tools to build it:
|
Ona currently depends the following third-party tools to build it:
|
||||||
|
|
||||||
* Platform support for SDL2 at version 2.0.20 installed via the standard shared binary format redistributable.
|
* Platform support for SDL2 at version 2.0.20 installed via the standard shared binary format redistributable.
|
||||||
* SPIR-V shader compilation system utilities available from the system path, namely `glslangValidator`.
|
|
||||||
* Zig compiler toolchain.
|
* Zig compiler toolchain.
|
||||||
|
|
||||||
As the project evolves, dependencies on libraries external to the project codebase will be minimized or removed outright to meet the goals of the project as closely as possible.
|
As the project evolves, dependencies on libraries external to the project codebase will be minimized or removed outright to meet the goals of the project as closely as possible.
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
After the repository has finished cloning, you will then want to also clone all Git submodule dependencies via the `git submodule update --init --recursive` command. If you are using a Git front-end, this may be a menu option or is handled *automagically*, it varies depending on the front-end.
|
Running the `zig build` command will build the everything. To learn how to consume the project as a library, see the [packaging](#packaging) section.
|
||||||
|
|
||||||
Once all third-party tools and dependencies are satisfied, navigate to the root project folder and run `zig build demos` to build the demo executables. The resulting binaries will be placed in `/demos` with a name respective to the source file it was built from.
|
For simple experimentation without creating a standalone Zig project, new demos may be freely created within the `src/demos` folder; The build script will automatically detect them and `zig build demos` step will automatically (re)build it along with everything else.
|
||||||
|
|
||||||
To experiment without creating your own Zig project, you can create a new uniquely named source file in this directory and the `zig build demos` step will (re)build it along with everything else.
|
The unit testing suite for the engine modules may be built and ran via `zig build tests`.
|
||||||
|
|
||||||
The unit testing suite for the engine modules (`src/ona`, `src/gfx`, etc.) may be built and ran via `zig build tests`.
|
|
||||||
|
|
||||||
Tests are ran by our continuous integration host so should these shouldn't fail locally without user intervention. If they do, feel free to report an issue via my email linked in my Sauce Control bio or through any other means of public communication I have.
|
|
||||||
|
|
||||||
### Packaging
|
### Packaging
|
||||||
|
|
||||||
**Note** that consuming Ona as a package requires it be downloaded manually, either through the releases page or by cloning, and referencing as a local dependency. This is due to the repository using Git submodules to reference third-party code.
|
|
||||||
|
|
||||||
To reduce the amount of setup work, a [project template is provided](https://sauce.pizzawednes.day/kayomn/ona-template) that will open a window and show the default texture on the middle of the screen. If you decide to use this project, the steps below can be skipped.
|
To reduce the amount of setup work, a [project template is provided](https://sauce.pizzawednes.day/kayomn/ona-template) that will open a window and show the default texture on the middle of the screen. If you decide to use this project, the steps below can be skipped.
|
||||||
|
|
||||||
2. Create a new Zig project if you have not already.
|
1. Create a new Zig project if you have not already.
|
||||||
3. Create a `build.zig.zon` accompanying your `build.zig` if you do not already have one.
|
2. Create a `build.zig.zon` accompanying your `build.zig` if you do not already have one.
|
||||||
4. Your `build.zig.zon` should, at minimum, contain the following dependency listed in the dependencies section:
|
3. Your `build.zig.zon` should, at minimum, contain the following dependency listed in the dependencies section:
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
.{
|
.{
|
||||||
@ -71,7 +64,7 @@ To reduce the amount of setup work, a [project template is provided](https://sau
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Add a package declaration for Ona to your `build.zig` and add the relevant modules to your application.
|
4. Add a package declaration for Ona to your `build.zig` and add the relevant modules to your application.
|
||||||
|
|
||||||
```zig
|
```zig
|
||||||
const ona_dependency = b.dependency("ona", .{
|
const ona_dependency = b.dependency("ona", .{
|
||||||
@ -87,10 +80,9 @@ const app = b.addExecutable(.{
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.root_module.addImport("ona", ona_dependency.module("ona"));
|
app.root_module.addImport("ona", ona_dependency.module("ona"));
|
||||||
app.root_module.addImport("gfx", ona_dependency.module("gfx"));
|
app.root_module.addImport("coral", ona_dependency.module("coral"));
|
||||||
app.root_module.addImport("hid", ona_dependency.module("hid"));
|
|
||||||
b.installArtifact(app);
|
b.installArtifact(app);
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Create a `main.zig` containing a valid Ona app declaration.
|
5. Create a `main.zig` containing a valid Ona app declaration.
|
||||||
7. Run `zig build` to build your new game application.
|
6. Run `zig build` to build your new game application.
|
||||||
|
68
src/coral/Box.zig
Normal file
68
src/coral/Box.zig
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
delete: *const fn (*anyopaque) void,
|
||||||
|
erased: *anyopaque,
|
||||||
|
|
||||||
|
fn Layout(comptime Type: type) type {
|
||||||
|
return struct {
|
||||||
|
allocator: std.mem.Allocator,
|
||||||
|
value: Type,
|
||||||
|
|
||||||
|
const TypeLayout = @This();
|
||||||
|
|
||||||
|
fn delete(erased: *anyopaque) void {
|
||||||
|
const layout: *TypeLayout = @ptrCast(@alignCast(erased));
|
||||||
|
|
||||||
|
if (@hasDecl(Type, "deinit")) {
|
||||||
|
layout.value.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
layout.allocator.destroy(layout);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.delete(self.erased);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn has(self: Self, comptime Type: type) ?*Type {
|
||||||
|
const ValueLayout = Layout(Type);
|
||||||
|
|
||||||
|
if (self.delete == ValueLayout.delete) {
|
||||||
|
const layout: *ValueLayout = @ptrCast(@alignCast(self.erased));
|
||||||
|
|
||||||
|
return &layout.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(value: anytype) std.mem.Allocator.Error!Self {
|
||||||
|
return initWithAllocator(coral.heap.allocator, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initWithAllocator(allocator: std.mem.Allocator, value: anytype) std.mem.Allocator.Error!Self {
|
||||||
|
const ValueLayout = Layout(@TypeOf(value));
|
||||||
|
const value_layout = try allocator.create(ValueLayout);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
allocator.destroy(value_layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
value_layout.* = .{
|
||||||
|
.allocator = allocator,
|
||||||
|
.value = value,
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.erased = @ptrCast(value_layout),
|
||||||
|
.delete = ValueLayout.delete,
|
||||||
|
};
|
||||||
|
}
|
13
src/coral/TypeId.zig
Normal file
13
src/coral/TypeId.zig
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
name: []const u8,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn of(comptime T: type) *const Self {
|
||||||
|
const instance = struct {
|
||||||
|
const id = Self{
|
||||||
|
.name = @typeName(T),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return &instance.id;
|
||||||
|
}
|
277
src/coral/asio.zig
Normal file
277
src/coral/asio.zig
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn CallTask(comptime function: anytype) type {
|
||||||
|
const Function = @TypeOf(function);
|
||||||
|
|
||||||
|
const function_fn = switch (@typeInfo(Function)) {
|
||||||
|
.@"fn" => |@"fn"| @"fn",
|
||||||
|
else => @compileError("expeceted param `exec` to be an fn type"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const Args = std.meta.ArgsTuple(Function);
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
args: Args,
|
||||||
|
@"return": Future(function_fn.return_type.?),
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(args: Args) Self {
|
||||||
|
return .{
|
||||||
|
.args = args,
|
||||||
|
.@"return" = .empty,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(self: *Self) void {
|
||||||
|
std.debug.assert(self.@"return".resolve(@call(.auto, function, self.args)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn BlockingQueue(comptime max: usize, comptime T: type) type {
|
||||||
|
const circular_max = max + 1;
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
buffer: [circular_max]T,
|
||||||
|
head: usize,
|
||||||
|
tail: usize,
|
||||||
|
access: std.Thread.Mutex,
|
||||||
|
not_empty: std.Thread.Condition,
|
||||||
|
not_full: std.Thread.Condition,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn dequeue(self: *Self) T {
|
||||||
|
self.access.lock();
|
||||||
|
|
||||||
|
defer {
|
||||||
|
self.access.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (self.head == self.tail) {
|
||||||
|
self.not_empty.wait(&self.access);
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = self.buffer[self.head];
|
||||||
|
const wrapped_head = (self.head + 1) % circular_max;
|
||||||
|
|
||||||
|
self.head = wrapped_head;
|
||||||
|
|
||||||
|
self.not_full.signal();
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const empty = Self{
|
||||||
|
.buffer = undefined,
|
||||||
|
.head = 0,
|
||||||
|
.tail = 0,
|
||||||
|
.access = .{},
|
||||||
|
.not_empty = .{},
|
||||||
|
.not_full = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn enqueue(self: *Self, item: T) void {
|
||||||
|
self.access.lock();
|
||||||
|
|
||||||
|
defer {
|
||||||
|
self.access.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapped_tail = (self.tail + 1) % circular_max;
|
||||||
|
|
||||||
|
while (wrapped_tail == self.head) {
|
||||||
|
self.not_full.wait(&self.access);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.buffer[self.tail] = item;
|
||||||
|
self.tail = wrapped_tail;
|
||||||
|
|
||||||
|
self.not_empty.signal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Event = Future(void);
|
||||||
|
|
||||||
|
pub fn Future(comptime Payload: type) type {
|
||||||
|
return struct {
|
||||||
|
payload: ?Payload,
|
||||||
|
resolved: std.Thread.ResetEvent,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const empty = Self{
|
||||||
|
.payload = null,
|
||||||
|
.resolved = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn get(self: *Self) *Payload {
|
||||||
|
// TODO: Make async.
|
||||||
|
self.resolved.wait();
|
||||||
|
|
||||||
|
return &self.payload.?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resolve(self: *Self, payload: Payload) bool {
|
||||||
|
if (self.resolved.isSet()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.payload = payload;
|
||||||
|
|
||||||
|
self.resolved.set();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const TaskQueue = struct {
|
||||||
|
threads: []std.Thread,
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
tasks: *Buffer,
|
||||||
|
|
||||||
|
const Buffer = struct {
|
||||||
|
waiting: std.Thread.WaitGroup,
|
||||||
|
pending: BlockingQueue(512, Runnable),
|
||||||
|
|
||||||
|
fn consume(self: *Buffer) void {
|
||||||
|
while (true) {
|
||||||
|
const runner = self.pending.dequeue();
|
||||||
|
|
||||||
|
if (runner.is_fn(poison_pill)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
runner.call(.{});
|
||||||
|
self.waiting.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poison(self: *Buffer, threads: []const std.Thread) void {
|
||||||
|
var threads_remaining = threads.len;
|
||||||
|
|
||||||
|
while (threads_remaining != 0) : (threads_remaining -= 1) {
|
||||||
|
self.pending.enqueue(.initFn(poison_pill));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (threads) |thread| {
|
||||||
|
thread.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poison_pill() void {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Runnable = coral.Callable(void, &.{});
|
||||||
|
|
||||||
|
pub fn create(self: *TaskQueue, task: anytype) std.mem.Allocator.Error!*const @TypeOf(task) {
|
||||||
|
const Task = @TypeOf(task);
|
||||||
|
|
||||||
|
switch (@hasDecl(Task, "run")) {
|
||||||
|
true => switch (@TypeOf(Task.run)) {
|
||||||
|
fn (*Task) void, fn (*const Task) void => {},
|
||||||
|
else => @compileError("param `task` must contain a `run` declaration"),
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("`run` declaration on param `task` must be an fn type"),
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (builtin.single_threaded) {
|
||||||
|
true => {
|
||||||
|
Runnable.initRef(&task, task.run).call(.{});
|
||||||
|
|
||||||
|
return &task;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
const allocated_task = try self.arena.allocator().create(Task);
|
||||||
|
|
||||||
|
allocated_task.* = task;
|
||||||
|
|
||||||
|
self.tasks.waiting.start();
|
||||||
|
self.tasks.pending.enqueue(.initRef(allocated_task, Task.run));
|
||||||
|
|
||||||
|
return allocated_task;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute(self: *TaskQueue, task: anytype) std.mem.Allocator.Error!void {
|
||||||
|
_ = try self.create(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *TaskQueue) void {
|
||||||
|
self.tasks.waiting.wait();
|
||||||
|
self.tasks.poison(self.threads);
|
||||||
|
self.arena.deinit();
|
||||||
|
coral.heap.allocator.destroy(self.tasks);
|
||||||
|
coral.heap.allocator.free(self.threads);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self: *TaskQueue) void {
|
||||||
|
self.tasks.waiting.wait();
|
||||||
|
self.tasks.waiting.reset();
|
||||||
|
|
||||||
|
_ = self.arena.reset(.retain_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(thread_count: usize) (std.mem.Allocator.Error || std.Thread.SpawnError)!TaskQueue {
|
||||||
|
const tasks = try coral.heap.allocator.create(Buffer);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
coral.heap.allocator.destroy(tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.* = .{
|
||||||
|
.pending = .empty,
|
||||||
|
.waiting = .{},
|
||||||
|
};
|
||||||
|
|
||||||
|
const threads = try coral.heap.allocator.alloc(std.Thread, if (builtin.single_threaded) 0 else thread_count);
|
||||||
|
var threads_spawned: usize = 0;
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
tasks.poison(threads[0..threads_spawned]);
|
||||||
|
coral.heap.allocator.free(threads);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (threads) |*thread| {
|
||||||
|
thread.* = try .spawn(.{}, Buffer.consume, .{tasks});
|
||||||
|
threads_spawned += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.assert(threads_spawned == thread_count);
|
||||||
|
|
||||||
|
const name = comptime try coral.ShortString.init("ona worker");
|
||||||
|
|
||||||
|
for (threads) |thread| {
|
||||||
|
thread.setName(name.slice()) catch |set_name_error| {
|
||||||
|
switch (set_name_error) {
|
||||||
|
error.Unsupported, error.NameTooLong => break,
|
||||||
|
else => continue,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.arena = .init(coral.heap.allocator),
|
||||||
|
.tasks = tasks,
|
||||||
|
.threads = threads,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn wait_all(comptime Payload: type, futures: []const *Future(Payload)) void {
|
||||||
|
for (futures) |future| {
|
||||||
|
_ = future.get();
|
||||||
|
}
|
||||||
|
}
|
314
src/coral/bytes.zig
Normal file
314
src/coral/bytes.zig
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
|
const formatting = @import("./bytes/formatting.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const PrintError = error{BufferOverflow};
|
||||||
|
|
||||||
|
pub const ReadOnlySpan = struct {
|
||||||
|
bytes: []const u8,
|
||||||
|
read_cursor: usize = 0,
|
||||||
|
|
||||||
|
pub fn read(self: *ReadOnlySpan, buffer: []u8) usize {
|
||||||
|
_ = self;
|
||||||
|
_ = buffer;
|
||||||
|
|
||||||
|
// TODO: Implement.
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ReadWriteError = error{IncompleteWrite};
|
||||||
|
|
||||||
|
pub const Readable = coral.Callable(usize, &.{[]u8});
|
||||||
|
|
||||||
|
pub const ReadWriteSpan = struct {
|
||||||
|
bytes: []u8,
|
||||||
|
write_cursor: usize = 0,
|
||||||
|
read_cursor: usize = 0,
|
||||||
|
|
||||||
|
pub fn read(self: *ReadWriteSpan, buffer: []u8) usize {
|
||||||
|
_ = self;
|
||||||
|
_ = buffer;
|
||||||
|
|
||||||
|
// TODO: Implement.
|
||||||
|
unreachable;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(self: *ReadWriteSpan, buffer: []const u8) usize {
|
||||||
|
const written = @min(buffer.len, self.bytes.len - self.write_cursor);
|
||||||
|
|
||||||
|
@memcpy(self.bytes[self.write_cursor .. self.write_cursor + written], buffer[0..written]);
|
||||||
|
|
||||||
|
self.write_cursor += written;
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writer(self: *ReadWriteSpan) Writable {
|
||||||
|
return .initRef(self, write);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn Span(comptime Ptr: type) type {
|
||||||
|
return switch (@typeInfo(Ptr)) {
|
||||||
|
.pointer => |pointer| if (pointer.is_const) ReadOnlySpan else ReadWriteSpan,
|
||||||
|
else => @compileError("param `Ptr` must be a non-const pointer type, not " ++ @typeName(Ptr)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Writable = coral.Callable(usize, &.{[]const u8});
|
||||||
|
|
||||||
|
pub const null_writer = Writable.initFn(writeNull);
|
||||||
|
|
||||||
|
pub fn allocFormatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![:0]u8 {
|
||||||
|
const formatted_len = countFormatted(format, args);
|
||||||
|
const buffer = try allocator.allocSentinel(u8, formatted_len, 0);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
allocator.free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This is messy.
|
||||||
|
return @constCast(printFormatted(buffer, format, args) catch unreachable);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn countFormatted(comptime format: []const u8, args: anytype) usize {
|
||||||
|
const Counter = struct {
|
||||||
|
written: usize,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
fn write(self: *Self, input: []const u8) usize {
|
||||||
|
self.written += input.len;
|
||||||
|
|
||||||
|
return input.len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var count = Counter{ .written = 0 };
|
||||||
|
|
||||||
|
writeFormatted(.initRef(&count, Counter.write), format, args) catch unreachable;
|
||||||
|
|
||||||
|
return count.written;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn printFormatted(buffer: [:0]u8, comptime format: []const u8, args: anytype) PrintError![:0]const u8 {
|
||||||
|
const len = countFormatted(format, args);
|
||||||
|
|
||||||
|
if (len > buffer.len) {
|
||||||
|
return error.BufferOverflow;
|
||||||
|
}
|
||||||
|
|
||||||
|
var buffer_span = span(buffer);
|
||||||
|
|
||||||
|
writeFormatted(buffer_span.writer(), format, args) catch unreachable;
|
||||||
|
|
||||||
|
if (buffer.len < len) {
|
||||||
|
buffer[len] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return buffer[0..len :0];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn span(ptr: anytype) Span(@TypeOf(ptr)) {
|
||||||
|
return .{
|
||||||
|
.bytes = switch (@typeInfo(@TypeOf(ptr)).pointer.size) {
|
||||||
|
.slice => std.mem.sliceAsBytes(ptr),
|
||||||
|
.one => std.mem.asBytes(ptr),
|
||||||
|
else => @compileError("Parameter `memory` must be a slice or single-element pointer"),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stackWriter(stack: *coral.Stack(u8)) Writable {
|
||||||
|
const writing = struct {
|
||||||
|
fn write(self: *coral.Stack(u8), buffer: []const u8) usize {
|
||||||
|
self.grow(buffer.len) catch {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
std.debug.assert(self.pushAll(buffer));
|
||||||
|
|
||||||
|
return buffer.len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return .initRef(stack, writing.write);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn streamAll(input: Readable, output: Writable) ReadWriteError!usize {
|
||||||
|
var buffer: [512]u8 = undefined;
|
||||||
|
var copied: usize = 0;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const read = input.call(.{&buffer});
|
||||||
|
|
||||||
|
if (read == 0) {
|
||||||
|
return copied;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.call(.{buffer[0..read]}) != read) {
|
||||||
|
return error.IncompleteWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
copied += read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn streamN(input: Readable, output: Writable, limit: usize) ReadWriteError!usize {
|
||||||
|
var buffer: [512]u8 = undefined;
|
||||||
|
var remaining = limit;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const read = input.call(.{buffer[0..@min(remaining, buffer.len)]});
|
||||||
|
|
||||||
|
if (read == 0) {
|
||||||
|
return limit - remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (output.yield(.{buffer[0..read]}) != read) {
|
||||||
|
return error.IncompleteWrite;
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining -= read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeAll(output: Writable, data: []const u8) ReadWriteError!void {
|
||||||
|
if (output.call(.{data}) != data.len) {
|
||||||
|
return error.IncompleteWrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeN(output: Writable, data: []const u8, count: usize) ReadWriteError!void {
|
||||||
|
var remaining = count;
|
||||||
|
|
||||||
|
while (remaining != 0) : (remaining -= 1) {
|
||||||
|
try writeAll(output, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeFormatted(output: Writable, comptime format: []const u8, args: anytype) ReadWriteError!void {
|
||||||
|
comptime {
|
||||||
|
if (!std.unicode.utf8ValidateSlice(format)) {
|
||||||
|
@compileError("`format` must be a valid UTF8 sequence");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
comptime var tokens = formatting.TokenStream.init(format);
|
||||||
|
|
||||||
|
inline while (comptime tokens.next()) |token| {
|
||||||
|
switch (token) {
|
||||||
|
.invalid => |invalid| {
|
||||||
|
@compileError(std.fmt.comptimePrint("unexpected `{s}` in format sequence `{s}`", .{ invalid, format }));
|
||||||
|
},
|
||||||
|
|
||||||
|
.literal => |literal| {
|
||||||
|
try coral.bytes.writeAll(output, literal);
|
||||||
|
},
|
||||||
|
|
||||||
|
.escaped => |escaped| {
|
||||||
|
try coral.bytes.writeAll(output, std.mem.asBytes(&escaped));
|
||||||
|
},
|
||||||
|
|
||||||
|
.placeholder => |placeholder| {
|
||||||
|
const Args = @TypeOf(args);
|
||||||
|
|
||||||
|
if (!@hasField(Args, placeholder)) {
|
||||||
|
@compileError(std.fmt.comptimePrint("format string `{s}` uses field `{s}` not present in {s}", .{
|
||||||
|
format,
|
||||||
|
placeholder,
|
||||||
|
@typeName(Args),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const field = @field(args, placeholder);
|
||||||
|
const Field = @TypeOf(field);
|
||||||
|
|
||||||
|
switch (@typeInfo(Field)) {
|
||||||
|
.pointer => |pointer| {
|
||||||
|
const error_message = std.fmt.comptimePrint("{s} is not a string-like type", .{@typeName(Field)});
|
||||||
|
|
||||||
|
switch (pointer.size) {
|
||||||
|
.one => switch (@typeInfo(pointer.child)) {
|
||||||
|
.array => |array| switch (array.child == u8) {
|
||||||
|
true => try coral.bytes.writeAll(output, field.*[0..]),
|
||||||
|
false => @compileError(error_message),
|
||||||
|
},
|
||||||
|
|
||||||
|
.@"struct", .@"union", .@"enum" => {
|
||||||
|
try field.writeFormat(output);
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError(error_message),
|
||||||
|
},
|
||||||
|
|
||||||
|
.slice => switch (pointer.child == u8) {
|
||||||
|
true => try coral.bytes.writeAll(output, field),
|
||||||
|
false => @compileError(error_message),
|
||||||
|
},
|
||||||
|
|
||||||
|
.many => switch ((pointer.sentinel() != null) and (pointer.child == u8)) {
|
||||||
|
true => try coral.bytes.writeAll(output, std.mem.span(field)),
|
||||||
|
false => @compileError(error_message),
|
||||||
|
},
|
||||||
|
|
||||||
|
else => false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.@"struct", .@"union", .@"enum" => {
|
||||||
|
try field.writeFormat(output);
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
@compileError("Unsupported placeholder type");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeLittle(output: Writable, value: anytype) ReadWriteError!void {
|
||||||
|
switch (builtin.cpu.arch.endian()) {
|
||||||
|
.little => {
|
||||||
|
try writeAll(output, std.mem.asBytes(&value));
|
||||||
|
},
|
||||||
|
|
||||||
|
.big => {
|
||||||
|
const Value = @TypeOf(value);
|
||||||
|
|
||||||
|
switch (@typeInfo(Value)) {
|
||||||
|
.@"struct", .array => {
|
||||||
|
var copy = value;
|
||||||
|
|
||||||
|
std.mem.byteSwapAllFields(Value, ©);
|
||||||
|
|
||||||
|
try writeAll(output, std.mem.asBytes(&value));
|
||||||
|
},
|
||||||
|
|
||||||
|
.int, .float, .bool => {
|
||||||
|
try writeAll(@byteSwap(value));
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
@compileError(std.fmt.comptimePrint("{s} is not byte-swappable", .{@typeName(Value)}));
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn writeNull(buffer: []const u8) usize {
|
||||||
|
return buffer.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeSentineled(output: Writable, data: []const u8, sentinel: u8) void {
|
||||||
|
try writeAll(output, data);
|
||||||
|
try writeAll((&sentinel)[0..1]);
|
||||||
|
}
|
110
src/coral/bytes/formatting.zig
Normal file
110
src/coral/bytes/formatting.zig
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Token = union(enum) {
|
||||||
|
invalid: []const u8,
|
||||||
|
literal: []const u8,
|
||||||
|
escaped: u8,
|
||||||
|
placeholder: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TokenStream = struct {
|
||||||
|
format: []const u8,
|
||||||
|
|
||||||
|
pub fn chop(self: *TokenStream, len: usize) []const u8 {
|
||||||
|
const token = self.format[0..len];
|
||||||
|
|
||||||
|
self.skip(len);
|
||||||
|
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(format: []const u8) TokenStream {
|
||||||
|
return .{
|
||||||
|
.format = format,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *TokenStream) ?Token {
|
||||||
|
if (self.format.len != 0) {
|
||||||
|
switch (self.format[0]) {
|
||||||
|
'}' => {
|
||||||
|
if (self.peek(1) != '}') {
|
||||||
|
return .{ .invalid = self.chop(1) };
|
||||||
|
}
|
||||||
|
|
||||||
|
self.skip(1);
|
||||||
|
|
||||||
|
return .{ .escaped = self.chop(1)[0] };
|
||||||
|
},
|
||||||
|
|
||||||
|
'{' => {
|
||||||
|
self.skip(1);
|
||||||
|
|
||||||
|
if (self.format.len != 0) {
|
||||||
|
if (self.format[0] == '{') {
|
||||||
|
return .{ .escaped = self.chop(1)[0] };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(u8, self.format, '}')) |end_index| {
|
||||||
|
defer {
|
||||||
|
self.skip(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .placeholder = self.chop(end_index) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .invalid = self.chop(self.format.len) };
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
if (std.mem.indexOfScalar(u8, self.format, '{')) |placeholder_index| {
|
||||||
|
return .{ .literal = self.chop(placeholder_index) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const end_index = std.mem.indexOfScalar(u8, self.format, '}') orelse self.format.len;
|
||||||
|
|
||||||
|
return .{ .literal = self.chop(end_index) };
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek(self: TokenStream, ahead_index: usize) ?u8 {
|
||||||
|
if (self.format.len < (ahead_index + 1)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.format[ahead_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skip(self: *TokenStream, len: usize) void {
|
||||||
|
self.format = self.format[len..];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "complex sequence" {
|
||||||
|
var stream = TokenStream.init("layout (binding = {binding}) uniform {identifier} {{ }}\n");
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("layout (binding = ", stream.next().?.literal);
|
||||||
|
try std.testing.expectEqualStrings("binding", stream.next().?.placeholder);
|
||||||
|
try std.testing.expectEqualStrings(") uniform ", stream.next().?.literal);
|
||||||
|
try std.testing.expectEqualStrings("identifier", stream.next().?.placeholder);
|
||||||
|
try std.testing.expectEqualStrings(" ", stream.next().?.literal);
|
||||||
|
try std.testing.expectEqual('{', stream.next().?.escaped);
|
||||||
|
try std.testing.expectEqualStrings(" ", stream.next().?.literal);
|
||||||
|
try std.testing.expectEqual('}', stream.next().?.escaped);
|
||||||
|
try std.testing.expectEqualStrings("\n", stream.next().?.literal);
|
||||||
|
try std.testing.expectEqual(null, stream.next());
|
||||||
|
}
|
||||||
|
|
||||||
|
test "invalid placeholder symbol usage" {
|
||||||
|
var stream = TokenStream.init("} {identifier};\n");
|
||||||
|
|
||||||
|
try std.testing.expectEqualStrings("}", stream.next().?.invalid);
|
||||||
|
try std.testing.expectEqualStrings(" ", stream.next().?.literal);
|
||||||
|
try std.testing.expectEqualStrings("identifier", stream.next().?.placeholder);
|
||||||
|
try std.testing.expectEqualStrings(";\n", stream.next().?.literal);
|
||||||
|
}
|
353
src/coral/coral.zig
Normal file
353
src/coral/coral.zig
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
pub const Box = @import("./Box.zig");
|
||||||
|
|
||||||
|
pub const TypeId = @import("./TypeId.zig");
|
||||||
|
|
||||||
|
pub const asio = @import("./asio.zig");
|
||||||
|
|
||||||
|
pub const bytes = @import("./bytes.zig");
|
||||||
|
|
||||||
|
pub const hashes = @import("./hashes.zig");
|
||||||
|
|
||||||
|
pub const heap = @import("./heap.zig");
|
||||||
|
|
||||||
|
pub const map = @import("./map.zig");
|
||||||
|
|
||||||
|
pub const scalars = @import("./scalars.zig");
|
||||||
|
|
||||||
|
pub const shaders = @import("./shaders.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const utf8 = @import("./utf8.zig");
|
||||||
|
|
||||||
|
pub fn Callable(comptime Output: type, comptime input_types: []const type) type {
|
||||||
|
const InputTuple = std.meta.Tuple(input_types);
|
||||||
|
const fn_context: *allowzero anyopaque = @ptrFromInt(0);
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
context: *allowzero anyopaque,
|
||||||
|
call_with_context: *const fn (*anyopaque, InputTuple) Output,
|
||||||
|
|
||||||
|
fn FnCall(comptime invoke: anytype) type {
|
||||||
|
return struct {
|
||||||
|
fn call(context: *anyopaque, inputs: InputTuple) Output {
|
||||||
|
std.debug.assert(context == fn_context);
|
||||||
|
|
||||||
|
return @call(.auto, invoke, inputs);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn RefCall(comptime ContextPtr: type, comptime invoke: anytype) type {
|
||||||
|
const Invoke = @TypeOf(invoke);
|
||||||
|
const is_zero_aligned = @typeInfo(ContextPtr).pointer.alignment == 0;
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
fn invoke_concrete(context: *anyopaque, inputs: InputTuple) Output {
|
||||||
|
std.debug.assert(context != fn_context);
|
||||||
|
|
||||||
|
if (is_zero_aligned) {
|
||||||
|
return @call(.auto, invoke, .{@as(ContextPtr, @ptrCast(context))} ++ inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
var args: std.meta.ArgsTuple(Invoke) = undefined;
|
||||||
|
|
||||||
|
args[0] = @as(ContextPtr, @ptrCast(@alignCast(context)));
|
||||||
|
|
||||||
|
inline for (1..args.len, &inputs) |i, input| {
|
||||||
|
args[i] = input;
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (@typeInfo(Output)) {
|
||||||
|
.error_union => try @call(.auto, invoke, args),
|
||||||
|
else => @call(.auto, invoke, args),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn call(self: Self, inputs: InputTuple) Output {
|
||||||
|
return self.call_with_context(self.context, inputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initFn(comptime invoke: anytype) Self {
|
||||||
|
return .{
|
||||||
|
.context = fn_context,
|
||||||
|
.call_with_context = FnCall(invoke).call,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initRef(context_ptr: anytype, comptime invoke: anytype) Self {
|
||||||
|
return .{
|
||||||
|
.context = @ptrCast(context_ptr),
|
||||||
|
.call_with_context = RefCall(@TypeOf(context_ptr), invoke).invoke_concrete,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_fn(self: Self, invoke: anytype) bool {
|
||||||
|
const Invoke = @TypeOf(invoke);
|
||||||
|
|
||||||
|
return switch (@typeInfo(Invoke)) {
|
||||||
|
.pointer => self.is_fn(invoke.*),
|
||||||
|
.@"fn" => (self.context == fn_context) and (self.call_with_context == FnCall(invoke).call),
|
||||||
|
else => @compileError(std.fmt.comptimePrint("parameter `fn` must be a function type or pointer to one, not {s}", .{@typeName(Invoke)})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const ShortString = extern struct {
|
||||||
|
buffer: [max]u8,
|
||||||
|
remaining: u8,
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
StringTooLong,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn append(self: *ShortString, c: u8) Error!void {
|
||||||
|
const post_remaining = scalars.sub(self.remaining, 1) orelse {
|
||||||
|
return error.StringTooLong;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.buffer[self.len()] = c;
|
||||||
|
self.remaining = post_remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign(self: *ShortString, data: []const u8) Error!void {
|
||||||
|
self.clear();
|
||||||
|
|
||||||
|
try self.join(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(self: *ShortString) void {
|
||||||
|
self.* = empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(data: []const u8) Error!ShortString {
|
||||||
|
var string = empty;
|
||||||
|
|
||||||
|
try string.join(data);
|
||||||
|
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isEmpty(self: ShortString) bool {
|
||||||
|
return self.remaining == max;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn join(self: *ShortString, data: []const u8) Error!void {
|
||||||
|
const remaining = scalars.sub(self.remaining, data.len) orelse {
|
||||||
|
return error.StringTooLong;
|
||||||
|
};
|
||||||
|
|
||||||
|
@memcpy(self.buffer[self.len()..(max - remaining)], data);
|
||||||
|
|
||||||
|
self.remaining = @intCast(remaining);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const empty = ShortString{
|
||||||
|
.buffer = [_]u8{0} ** max,
|
||||||
|
.remaining = max,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn len(self: ShortString) usize {
|
||||||
|
return max - self.remaining;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const max = 255;
|
||||||
|
|
||||||
|
pub fn ptr(self: *const ShortString) [*:0]const u8 {
|
||||||
|
return @ptrCast(&self.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn slice(self: *const ShortString) [:0]const u8 {
|
||||||
|
return @ptrCast(self.ptr()[0..self.len()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeFormat(self: ShortString, output: bytes.Writable) bytes.ReadWriteError!void {
|
||||||
|
return bytes.writeAll(output, self.slice());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writer(self: *ShortString) bytes.Writable {
|
||||||
|
const writing = struct {
|
||||||
|
fn write(string: *ShortString, buffer: []const u8) usize {
|
||||||
|
string.append(buffer) catch {
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
return buffer.len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return .init(ShortString, self, writing.write);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn Stack(comptime Value: type) type {
|
||||||
|
return struct {
|
||||||
|
values: []Value,
|
||||||
|
cap: usize,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn clear(self: *Self) void {
|
||||||
|
self.values = self.values[0..0];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
if (self.cap == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
heap.allocator.free(self.values.ptr[0..self.cap]);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const empty = Self{
|
||||||
|
.values = &.{},
|
||||||
|
.cap = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
|
||||||
|
const grown_capacity = self.values.len + additional;
|
||||||
|
|
||||||
|
if (grown_capacity <= self.cap) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = try heap.allocator.alloc(Value, grown_capacity);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
heap.allocator.deallocate(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.cap != 0) {
|
||||||
|
@memcpy(buffer[0..self.values.len], self.values);
|
||||||
|
heap.allocator.free(self.values.ptr[0..self.cap]);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.values = buffer[0..self.values.len];
|
||||||
|
self.cap = grown_capacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isEmpty(self: Self) bool {
|
||||||
|
return self.values.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isFull(self: Self) bool {
|
||||||
|
return self.values.len == self.cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: Self) ?*Value {
|
||||||
|
if (self.values.len == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return &self.values[self.values.len - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop(self: *Self) ?Value {
|
||||||
|
if (self.values.len == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tail_index = self.values.len - 1;
|
||||||
|
|
||||||
|
defer self.values = self.values[0..tail_index];
|
||||||
|
|
||||||
|
return self.values[tail_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(self: *Self, value: Value) bool {
|
||||||
|
if (self.isFull()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset_index = self.values.len;
|
||||||
|
|
||||||
|
self.values = self.values.ptr[0 .. self.values.len + 1];
|
||||||
|
self.values[offset_index] = value;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pushAll(self: *Self, values: []const Value) bool {
|
||||||
|
const new_length = self.values.len + values.len;
|
||||||
|
|
||||||
|
if (new_length > self.cap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset_index = self.values.len;
|
||||||
|
|
||||||
|
self.values = self.values.ptr[0..new_length];
|
||||||
|
|
||||||
|
for (0..values.len) |index| {
|
||||||
|
self.values[offset_index + index] = values[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pushGrow(self: *Self, value: Value) std.mem.Allocator.Error!void {
|
||||||
|
try self.grow(@max(1, self.values.len));
|
||||||
|
|
||||||
|
std.debug.assert(self.push(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pushMany(self: *Self, n: usize, value: Value) bool {
|
||||||
|
const new_length = self.values.len + n;
|
||||||
|
|
||||||
|
if (new_length > self.cap) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset_index = self.values.len;
|
||||||
|
|
||||||
|
self.values = self.values.ptr[0..new_length];
|
||||||
|
|
||||||
|
for (0..n) |index| {
|
||||||
|
self.values[offset_index + index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void {
|
||||||
|
if (self.cap == size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = try heap.allocator.alloc(Value, size);
|
||||||
|
|
||||||
|
for (0..@min(values.len, self.values.len)) |i| {
|
||||||
|
values[i] = self.values[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (values.len > self.values.len) {
|
||||||
|
for (self.values.len..values.len) |i| {
|
||||||
|
values[i] = default_value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.values = values[0..values.len];
|
||||||
|
self.cap = values.len;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn require(function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) switch (@typeInfo(@typeInfo(@TypeOf(function)).@"fn".return_type.?)) {
|
||||||
|
.error_union => |error_union| error_union.payload,
|
||||||
|
else => @compileError("fn parameter `function` must return an error union"),
|
||||||
|
} {
|
||||||
|
return @call(.auto, function, args) catch |call_error| {
|
||||||
|
if (@errorReturnTrace()) |trace| {
|
||||||
|
std.debug.dumpStackTrace(trace.*);
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.panic("requirement failed: {s}", .{@errorName(call_error)});
|
||||||
|
};
|
||||||
|
}
|
27
src/coral/hashes.zig
Normal file
27
src/coral/hashes.zig
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn djb2(comptime Int: type, bytes: []const u8) Int {
|
||||||
|
var hash = @as(Int, @intCast(5381));
|
||||||
|
|
||||||
|
for (bytes) |byte| {
|
||||||
|
hash = ((hash << 5) +% hash) +% byte;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn jenkins(comptime Int: type, bytes: []const u8) Int {
|
||||||
|
var hash = @as(Int, 0);
|
||||||
|
|
||||||
|
for (bytes) |byte| {
|
||||||
|
hash +%= byte;
|
||||||
|
hash +%= (hash << 10);
|
||||||
|
hash ^= (hash >> 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
hash +%= (hash << 3);
|
||||||
|
hash ^= (hash >> 11);
|
||||||
|
hash +%= (hash << 15);
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
75
src/coral/heap.zig
Normal file
75
src/coral/heap.zig
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn RefCounting(comptime Payload: type, comptime finalize: fn (*Payload) void) type {
|
||||||
|
const AtomicCount = std.atomic.Value(usize);
|
||||||
|
|
||||||
|
return opaque {
|
||||||
|
const Layout = struct {
|
||||||
|
ref_count: AtomicCount,
|
||||||
|
payload: Payload,
|
||||||
|
|
||||||
|
fn get(self: *Self) *Layout {
|
||||||
|
return @ptrCast(@alignCast(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_const(self: *const Self) *const Layout {
|
||||||
|
return @ptrCast(@alignCast(self));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn acquire(self: *Self) *const Payload {
|
||||||
|
const layout = Layout.get(self);
|
||||||
|
const ref_count = layout.ref_count.fetchAdd(1, .monotonic);
|
||||||
|
|
||||||
|
std.debug.assert(ref_count != 0);
|
||||||
|
|
||||||
|
return &layout.payload;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(payload: Payload) std.mem.Allocator.Error!*Self {
|
||||||
|
const allocation = try allocator.create(Layout);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
allocator.destroy(allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
allocation.* = .{
|
||||||
|
.ref_count = AtomicCount.init(1),
|
||||||
|
.payload = payload,
|
||||||
|
};
|
||||||
|
|
||||||
|
return @ptrCast(allocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release(self: *Self) void {
|
||||||
|
const layout = Layout.get(self);
|
||||||
|
const ref_count = layout.ref_count.fetchSub(1, .monotonic);
|
||||||
|
|
||||||
|
std.debug.assert(ref_count != 0);
|
||||||
|
|
||||||
|
if (ref_count == 1) {
|
||||||
|
finalize(&layout.payload);
|
||||||
|
allocator.destroy(layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn weak(self: *const Self) *const Payload {
|
||||||
|
return &Layout.get_const(self).payload;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const allocator = switch (builtin.mode) {
|
||||||
|
.ReleaseFast => std.heap.smp_allocator,
|
||||||
|
else => gpa.allocator(),
|
||||||
|
};
|
||||||
|
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = true }){};
|
||||||
|
|
||||||
|
pub fn traceLeaks() void {
|
||||||
|
_ = gpa.detectLeaks();
|
||||||
|
}
|
323
src/coral/map.zig
Normal file
323
src/coral/map.zig
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(Key)) type {
|
||||||
|
const load_max = 0.75;
|
||||||
|
const max_int = std.math.maxInt(usize);
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
entry_map: []?Entry,
|
||||||
|
len: usize,
|
||||||
|
|
||||||
|
pub const Entry = struct {
|
||||||
|
key: Key,
|
||||||
|
value: Value,
|
||||||
|
|
||||||
|
fn writeInto(self: Entry, table: *Self) ?*Entry {
|
||||||
|
const hash_max = @min(max_int, table.entry_map.len);
|
||||||
|
var hashed_key = traits.hash(self.key) % hash_max;
|
||||||
|
var iterations = @as(usize, 0);
|
||||||
|
|
||||||
|
while (true) : (iterations += 1) {
|
||||||
|
std.debug.assert(iterations < table.entry_map.len);
|
||||||
|
|
||||||
|
const table_entry = &(table.entry_map[hashed_key] orelse {
|
||||||
|
table.entry_map[hashed_key] = .{
|
||||||
|
.key = self.key,
|
||||||
|
.value = self.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
table.len += 1;
|
||||||
|
|
||||||
|
return &table.entry_map[hashed_key].?;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (traits.are_equal(table_entry.key, self.key)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed_key = (hashed_key +% 1) % hash_max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Entries = struct {
|
||||||
|
table: *const Self,
|
||||||
|
iterations: usize,
|
||||||
|
|
||||||
|
pub fn next(self: *Entries) ?*Entry {
|
||||||
|
while (self.iterations < self.table.entry_map.len) {
|
||||||
|
defer self.iterations += 1;
|
||||||
|
|
||||||
|
if (self.table.entry_map[self.iterations]) |*entry| {
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const empty = Self{
|
||||||
|
.entry_map = &.{},
|
||||||
|
.len = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn entries(self: *const Self) Entries {
|
||||||
|
return .{
|
||||||
|
.table = self,
|
||||||
|
.iterations = 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isEmpty(self: Self) bool {
|
||||||
|
return self.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self: *Self, key: Key) ?Entry {
|
||||||
|
const hash_max = @min(max_int, self.entry_map.len);
|
||||||
|
var hashed_key = key.hash() % hash_max;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const entry = &(self.entry_map[hashed_key] orelse continue);
|
||||||
|
|
||||||
|
if (self.keys_equal(entry.key, key)) {
|
||||||
|
const original_entry = entry.*;
|
||||||
|
|
||||||
|
self.entry_map[hashed_key] = null;
|
||||||
|
|
||||||
|
return original_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed_key = (hashed_key +% 1) % hash_max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!?Entry {
|
||||||
|
try self.rehash(load_max);
|
||||||
|
|
||||||
|
std.debug.assert(self.entry_map.len > self.len);
|
||||||
|
|
||||||
|
{
|
||||||
|
const hash_max = @min(max_int, self.entry_map.len);
|
||||||
|
var hashed_key = traits.hash(key) % hash_max;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
const entry = &(self.entry_map[hashed_key] orelse {
|
||||||
|
self.entry_map[hashed_key] = .{
|
||||||
|
.key = key,
|
||||||
|
.value = value,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.len += 1;
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (traits.are_equal(key, entry.key)) {
|
||||||
|
const original_entry = entry.*;
|
||||||
|
|
||||||
|
entry.* = .{
|
||||||
|
.key = key,
|
||||||
|
.value = value,
|
||||||
|
};
|
||||||
|
|
||||||
|
return original_entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed_key = (hashed_key +% 1) % hash_max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(self: *Self) void {
|
||||||
|
for (self.entry_map) |*entry| {
|
||||||
|
entry.* = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
if (self.entry_map.len == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
coral.heap.allocator.free(self.entry_map);
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: Self, key: Key) ?*Value {
|
||||||
|
if (self.len == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hash_max = @min(max_int, self.entry_map.len);
|
||||||
|
var hashed_key = traits.hash(key) % hash_max;
|
||||||
|
var iterations = @as(usize, 0);
|
||||||
|
|
||||||
|
while (iterations < self.len) : (iterations += 1) {
|
||||||
|
const entry = &(self.entry_map[hashed_key] orelse return null);
|
||||||
|
|
||||||
|
if (traits.are_equal(entry.key, key)) {
|
||||||
|
return &entry.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
hashed_key = (hashed_key +% 1) % hash_max;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn emplace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!switch (Value) {
|
||||||
|
void => bool,
|
||||||
|
else => ?*Value,
|
||||||
|
} {
|
||||||
|
try self.rehash(load_max);
|
||||||
|
|
||||||
|
std.debug.assert(self.entry_map.len > self.len);
|
||||||
|
|
||||||
|
const entry = Entry{
|
||||||
|
.key = key,
|
||||||
|
.value = value,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (entry.writeInto(self)) |written_entry| {
|
||||||
|
return switch (Value) {
|
||||||
|
void => true,
|
||||||
|
else => &written_entry.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (Value) {
|
||||||
|
void => false,
|
||||||
|
else => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadFactor(self: Self) f32 {
|
||||||
|
return if (self.entry_map.len == 0) 1 else @as(f32, @floatFromInt(self.len)) / @as(f32, @floatFromInt(self.entry_map.len));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rehash(self: *Self, max_load: f32) std.mem.Allocator.Error!void {
|
||||||
|
if (self.loadFactor() <= max_load) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var table = empty;
|
||||||
|
|
||||||
|
errdefer table.deinit();
|
||||||
|
|
||||||
|
table.entry_map = allocate: {
|
||||||
|
const min_len = @max(1, self.len);
|
||||||
|
const table_size = min_len * 2;
|
||||||
|
const zeroed_entry_map = try coral.heap.allocator.alloc(?Entry, table_size);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
coral.heap.allocator.free(zeroed_entry_map);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (zeroed_entry_map) |*entry| {
|
||||||
|
entry.* = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
break :allocate zeroed_entry_map;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (self.entry_map) |maybe_entry| {
|
||||||
|
if (maybe_entry) |entry| {
|
||||||
|
std.debug.assert(entry.writeInto(&table) != null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.deinit();
|
||||||
|
|
||||||
|
self.* = table;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Traits(comptime Key: type) type {
|
||||||
|
return struct {
|
||||||
|
are_equal: fn (Key, Key) bool,
|
||||||
|
hash: fn (Key) usize,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enumTraits(comptime Enum: type) Traits(Enum) {
|
||||||
|
const enums = struct {
|
||||||
|
fn are_equal(a: Enum, b: Enum) bool {
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash(value: Enum) usize {
|
||||||
|
return @intFromEnum(value) % std.math.maxInt(usize);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.are_equal = enums.are_equal,
|
||||||
|
.hash = enums.hash,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ptrTraits(comptime Ptr: type) Traits(Ptr) {
|
||||||
|
const pointers = struct {
|
||||||
|
fn are_equal(a: Ptr, b: Ptr) bool {
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash(value: Ptr) usize {
|
||||||
|
return @intFromPtr(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return switch (@typeInfo(Ptr)) {
|
||||||
|
.pointer => .{
|
||||||
|
.are_equal = pointers.are_equal,
|
||||||
|
.hash = pointers.hash,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError(std.fmt.comptimePrint("parameter `Ptr` must be a pointer type, not {s}", .{
|
||||||
|
@typeName(Ptr),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const string_traits = init: {
|
||||||
|
const strings = struct {
|
||||||
|
fn are_equal(a: []const u8, b: []const u8) bool {
|
||||||
|
return std.mem.eql(u8, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash(value: []const u8) usize {
|
||||||
|
return coral.hashes.djb2(usize, value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
break :init Traits([]const u8){
|
||||||
|
.are_equal = strings.are_equal,
|
||||||
|
.hash = strings.hash,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const usize_traits = init: {
|
||||||
|
const usizes = struct {
|
||||||
|
fn are_equal(a: usize, b: usize) bool {
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hash(value: usize) usize {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
break :init Traits(usize){
|
||||||
|
.are_equal = usizes.are_equal,
|
||||||
|
.hash = usizes.hash,
|
||||||
|
};
|
||||||
|
};
|
26
src/coral/scalars.zig
Normal file
26
src/coral/scalars.zig
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn add(a: anytype, b: anytype) ?@TypeOf(a + b) {
|
||||||
|
const result, const overflow = @addWithOverflow(a, b);
|
||||||
|
|
||||||
|
return if (overflow == 0) result else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lerpAngle(origin_angle: anytype, target_angle: anytype, weight: anytype) @TypeOf(origin_angle, target_angle, weight) {
|
||||||
|
const angle_difference = @mod(target_angle - origin_angle, std.math.tau);
|
||||||
|
const distance = @mod(2.0 * angle_difference, std.math.tau) - angle_difference;
|
||||||
|
|
||||||
|
return origin_angle + distance * weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mul(a: anytype, b: anytype) ?@TypeOf(a * b) {
|
||||||
|
const result, const overflow = @mulWithOverflow(a, b);
|
||||||
|
|
||||||
|
return if (overflow == 0) result else null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sub(a: anytype, b: anytype) ?@TypeOf(a - b) {
|
||||||
|
const result, const overflow = @subWithOverflow(a, b);
|
||||||
|
|
||||||
|
return if (overflow == 0) result else null;
|
||||||
|
}
|
342
src/coral/shaders.zig
Normal file
342
src/coral/shaders.zig
Normal file
@ -0,0 +1,342 @@
|
|||||||
|
pub const Field = @import("./shaders/Field.zig");
|
||||||
|
|
||||||
|
pub const Root = @import("./shaders/Root.zig");
|
||||||
|
|
||||||
|
pub const Scope = @import("./shaders/Scope.zig");
|
||||||
|
|
||||||
|
pub const Type = @import("./shaders/Type.zig");
|
||||||
|
|
||||||
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
|
const glsl = @import("./shaders/glsl.zig");
|
||||||
|
|
||||||
|
const spirv = @import("./shaders/spirv.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const tokens = @import("./shaders/tokens.zig");
|
||||||
|
|
||||||
|
pub const Argument = struct {
|
||||||
|
expression: *const Expression,
|
||||||
|
has_next: ?*const Argument = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Block = struct {
|
||||||
|
scope: *const Scope,
|
||||||
|
depth: usize,
|
||||||
|
has_statement: ?*const Statement = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DefinitionError = std.mem.Allocator.Error || error{
|
||||||
|
TooManySymbols,
|
||||||
|
DuplicateIdentifier,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Expression = union(enum) {
|
||||||
|
float: Float,
|
||||||
|
int: Int,
|
||||||
|
group_expression: *const Expression,
|
||||||
|
add: BinaryOperation,
|
||||||
|
subtract: BinaryOperation,
|
||||||
|
multiply: BinaryOperation,
|
||||||
|
divide: BinaryOperation,
|
||||||
|
equal: BinaryOperation,
|
||||||
|
greater_than: BinaryOperation,
|
||||||
|
greater_equal: BinaryOperation,
|
||||||
|
lesser_than: BinaryOperation,
|
||||||
|
lesser_equal: BinaryOperation,
|
||||||
|
negate_expression: *const Expression,
|
||||||
|
get_local: *const Local,
|
||||||
|
get_parameter: *const Parameter,
|
||||||
|
get_uniform: GetUniform,
|
||||||
|
get_object: GetObject,
|
||||||
|
mutate_local: LocalMutation,
|
||||||
|
mutate_output: OutputMutation,
|
||||||
|
get_texture: *const Texture,
|
||||||
|
get_output: *const Output,
|
||||||
|
get_input: *const Input,
|
||||||
|
invoke: Invocation,
|
||||||
|
convert: Conversion,
|
||||||
|
pow: Intrinsic,
|
||||||
|
abs: Intrinsic,
|
||||||
|
sin: Intrinsic,
|
||||||
|
sample: Builtin,
|
||||||
|
|
||||||
|
pub const BinaryOperation = struct {
|
||||||
|
rhs_expression: *const Expression,
|
||||||
|
lhs_expression: *const Expression,
|
||||||
|
|
||||||
|
pub fn inferType(self: BinaryOperation) TypeError!*const Type {
|
||||||
|
const lhs_type = try self.lhs_expression.inferType();
|
||||||
|
const rhs_type = try self.rhs_expression.inferType();
|
||||||
|
|
||||||
|
if (lhs_type != rhs_type) {
|
||||||
|
return error.IncompatibleTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lhs_type;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Builtin = struct {
|
||||||
|
has_argument: ?*const Argument,
|
||||||
|
signatures: []const Signature,
|
||||||
|
|
||||||
|
pub const Signature = struct {
|
||||||
|
parameter_types: []const *const Type,
|
||||||
|
return_type: *const Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn inferType(self: Builtin) TypeError!*const Type {
|
||||||
|
signature_matching: for (self.signatures) |signature| {
|
||||||
|
var parameter_index: usize = 0;
|
||||||
|
var has_argument = self.has_argument;
|
||||||
|
|
||||||
|
while (has_argument) |argument| : ({
|
||||||
|
has_argument = argument.has_next;
|
||||||
|
parameter_index += 1;
|
||||||
|
}) {
|
||||||
|
if (signature.parameter_types.len == parameter_index) {
|
||||||
|
continue :signature_matching;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (signature.parameter_types[parameter_index] != try argument.expression.inferType()) {
|
||||||
|
continue :signature_matching;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return signature.return_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.IncompatibleArguments;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Conversion = struct {
|
||||||
|
target_type: *const Type,
|
||||||
|
first_argument: *const Argument,
|
||||||
|
parameter_types: []const *const Type,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Float = struct {
|
||||||
|
whole: [:0]const u8,
|
||||||
|
decimal: [:0]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Intrinsic = struct {
|
||||||
|
allowed_parameter_types: []const *const Type,
|
||||||
|
expected_parameter_count: usize,
|
||||||
|
first_argument: *const Argument,
|
||||||
|
|
||||||
|
pub fn inferType(self: Intrinsic) TypeError!*const Type {
|
||||||
|
const return_type = try self.first_argument.expression.inferType();
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(*const Type, self.allowed_parameter_types, return_type) == null) {
|
||||||
|
return error.IncompatibleTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
var has_next_argument = self.first_argument.has_next;
|
||||||
|
var arguments_remaining = self.expected_parameter_count;
|
||||||
|
|
||||||
|
while (has_next_argument) |next_argument| : ({
|
||||||
|
has_next_argument = next_argument.has_next;
|
||||||
|
arguments_remaining += 1;
|
||||||
|
}) {
|
||||||
|
if (arguments_remaining == 0) {
|
||||||
|
return error.IncompatibleArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try next_argument.expression.inferType() != return_type) {
|
||||||
|
return error.IncompatibleTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (arguments_remaining != 0) {
|
||||||
|
return error.IncompatibleArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return return_type;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GetObject = struct {
|
||||||
|
field: *const Field,
|
||||||
|
object_expression: *const Expression,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GetUniform = struct {
|
||||||
|
field: *const Field,
|
||||||
|
uniform: *const Uniform,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Invocation = struct {
|
||||||
|
function: *const Function,
|
||||||
|
has_argument: ?*const Argument = null,
|
||||||
|
|
||||||
|
pub fn inferType(self: Invocation) TypeError!*const Type {
|
||||||
|
var has_parameter = self.function.has_parameter;
|
||||||
|
var has_argument = self.has_argument;
|
||||||
|
|
||||||
|
while (has_parameter) |parameter| {
|
||||||
|
const argument = has_argument orelse {
|
||||||
|
return error.IncompatibleArguments;
|
||||||
|
};
|
||||||
|
|
||||||
|
defer {
|
||||||
|
has_parameter = parameter.has_next;
|
||||||
|
has_argument = argument.has_next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parameter.type != try argument.expression.inferType()) {
|
||||||
|
return error.IncompatibleTypes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_argument != null) {
|
||||||
|
return error.IncompatibleArguments;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.function.has_return_type orelse error.IncompatibleTypes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Int = struct {
|
||||||
|
literal: [:0]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const OutputMutation = struct {
|
||||||
|
output: *const Output,
|
||||||
|
expression: *const Expression,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LocalMutation = struct {
|
||||||
|
local: *const Local,
|
||||||
|
expression: *const Expression,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn inferType(self: Expression) TypeError!*const Type {
|
||||||
|
return switch (self) {
|
||||||
|
.float => .float,
|
||||||
|
.int => .int,
|
||||||
|
.negate_expression, .group_expression => |expression| expression.inferType(),
|
||||||
|
.abs, .pow, .sin => |generic| generic.inferType(),
|
||||||
|
.convert => |convert| convert.target_type,
|
||||||
|
.invoke => |invoke| invoke.inferType(),
|
||||||
|
.mutate_local => |mutate_local| mutate_local.local.type,
|
||||||
|
.get_local => |local| local.type,
|
||||||
|
.get_object => |get_object| get_object.field.type,
|
||||||
|
.get_parameter => |get_parameter| get_parameter.type,
|
||||||
|
.sample => |builtin| builtin.inferType(),
|
||||||
|
.get_uniform => |uniform| uniform.field.type,
|
||||||
|
.get_input => |input| input.type,
|
||||||
|
.get_output => |output| output.type,
|
||||||
|
.mutate_output => |mutate_output| mutate_output.output.type,
|
||||||
|
|
||||||
|
.get_texture => |texture| switch (texture.layout) {
|
||||||
|
.dimensions_2 => .texture2,
|
||||||
|
},
|
||||||
|
|
||||||
|
.add,
|
||||||
|
.subtract,
|
||||||
|
.multiply,
|
||||||
|
.divide,
|
||||||
|
.equal,
|
||||||
|
.greater_than,
|
||||||
|
.greater_equal,
|
||||||
|
.lesser_than,
|
||||||
|
.lesser_equal,
|
||||||
|
=> |binary_op| binary_op.inferType(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Function = struct {
|
||||||
|
identifier: [:0]const u8,
|
||||||
|
has_return_type: ?*const Type = null,
|
||||||
|
has_parameter: ?*const Parameter = null,
|
||||||
|
block: *const Block,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const GenerationError = coral.bytes.ReadWriteError || error{
|
||||||
|
UnsupportedFeature,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Input = struct {
|
||||||
|
identifier: [:0]const u8,
|
||||||
|
type: *const Type,
|
||||||
|
location: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Output = struct {
|
||||||
|
identifier: [:0]const u8,
|
||||||
|
type: *const Type,
|
||||||
|
location: u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Parameter = struct {
|
||||||
|
identifier: [:0]const u8,
|
||||||
|
type: *const Type,
|
||||||
|
has_next: ?*const Parameter = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ParsingError = std.mem.Allocator.Error || tokens.ExpectationError || DefinitionError || TypeError || error{
|
||||||
|
ImmutableStorage,
|
||||||
|
UndefinedIdentifier,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Texture = struct {
|
||||||
|
identifier: [:0]const u8,
|
||||||
|
binding: u8,
|
||||||
|
layout: Layout,
|
||||||
|
|
||||||
|
pub const Layout = enum {
|
||||||
|
dimensions_2,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Statement = union(enum) {
|
||||||
|
declare_local: LocalDeclaration,
|
||||||
|
mutate_local: LocalMutation,
|
||||||
|
mutate_output: OutputMutation,
|
||||||
|
return_expression: ?*const Expression,
|
||||||
|
|
||||||
|
pub const LocalDeclaration = struct {
|
||||||
|
local: *const Local,
|
||||||
|
has_next: ?*const Statement = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const LocalMutation = struct {
|
||||||
|
local: *const Local,
|
||||||
|
expression: *const Expression,
|
||||||
|
has_next: ?*const Statement = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const OutputMutation = struct {
|
||||||
|
output: *const Output,
|
||||||
|
expression: *const Expression,
|
||||||
|
has_next: ?*const Statement = null,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Local = struct {
|
||||||
|
identifier: [:0]const u8,
|
||||||
|
expression: *const Expression,
|
||||||
|
type: *const Type,
|
||||||
|
is_constant: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TypeError = error{
|
||||||
|
IncompatibleTypes,
|
||||||
|
IncompatibleArguments,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Uniform = struct {
|
||||||
|
identifier: [:0]const u8,
|
||||||
|
semantic: [:0]const u8,
|
||||||
|
binding: u8,
|
||||||
|
has_field: ?*const Field = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const generateGlsl = glsl.generate;
|
||||||
|
|
||||||
|
pub const generateSpirv = spirv.generate;
|
1782
src/coral/shaders/Field.zig
Normal file
1782
src/coral/shaders/Field.zig
Normal file
File diff suppressed because it is too large
Load Diff
198
src/coral/shaders/Root.zig
Normal file
198
src/coral/shaders/Root.zig
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
const coral = @import("../coral.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const tokens = @import("./tokens.zig");
|
||||||
|
|
||||||
|
scope: *coral.shaders.Scope,
|
||||||
|
inputs: std.BoundedArray(*const coral.shaders.Input, 15) = .{},
|
||||||
|
outputs: std.BoundedArray(*const coral.shaders.Output, 15) = .{},
|
||||||
|
uniforms: std.BoundedArray(*const coral.shaders.Uniform, 15) = .{},
|
||||||
|
textures: std.BoundedArray(*const coral.shaders.Texture, 15) = .{},
|
||||||
|
functions: std.BoundedArray(*const coral.shaders.Function, 255) = .{},
|
||||||
|
|
||||||
|
pub const ParseResult = union(enum) {
|
||||||
|
ok,
|
||||||
|
failure: [:0]const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn clear(self: *Self) void {
|
||||||
|
self.scope.clear();
|
||||||
|
self.inputs.clear();
|
||||||
|
self.outputs.clear();
|
||||||
|
self.uniforms.clear();
|
||||||
|
self.textures.clear();
|
||||||
|
self.functions.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn defineFunction(self: *Self, arena: *std.heap.ArenaAllocator, function: coral.shaders.Function) coral.shaders.DefinitionError!void {
|
||||||
|
const defined = try self.scope.define(arena, function);
|
||||||
|
|
||||||
|
self.functions.append(defined) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn defineInput(self: *Self, arena: *std.heap.ArenaAllocator, input: coral.shaders.Input) coral.shaders.DefinitionError!void {
|
||||||
|
const defined = try self.scope.define(arena, input);
|
||||||
|
|
||||||
|
self.inputs.append(defined) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn defineOutput(self: *Self, arena: *std.heap.ArenaAllocator, output: coral.shaders.Output) coral.shaders.DefinitionError!void {
|
||||||
|
const defined = try self.scope.define(arena, output);
|
||||||
|
|
||||||
|
self.outputs.append(defined) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn defineTexture(self: *Self, arena: *std.heap.ArenaAllocator, input: coral.shaders.Texture) coral.shaders.DefinitionError!void {
|
||||||
|
const defined = try self.scope.define(arena, input);
|
||||||
|
|
||||||
|
self.textures.append(defined) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn defineUniform(self: *Self, arena: *std.heap.ArenaAllocator, input: coral.shaders.Uniform) coral.shaders.DefinitionError!void {
|
||||||
|
const defined = try self.scope.define(arena, input);
|
||||||
|
|
||||||
|
self.uniforms.append(defined) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!Self {
|
||||||
|
const scope = try arena.allocator().create(coral.shaders.Scope);
|
||||||
|
|
||||||
|
scope.* = .{};
|
||||||
|
|
||||||
|
return .{ .scope = scope };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!void {
|
||||||
|
errdefer {
|
||||||
|
self.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (source.skip(.newline) != .end) {
|
||||||
|
const symbol = try self.scope.parse(arena, source);
|
||||||
|
|
||||||
|
if (symbol.has(coral.shaders.Input)) |input| {
|
||||||
|
self.inputs.append(input) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol.has(coral.shaders.Output)) |output| {
|
||||||
|
self.outputs.append(output) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol.has(coral.shaders.Uniform)) |uniform| {
|
||||||
|
self.uniforms.append(uniform) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol.has(coral.shaders.Texture)) |texture| {
|
||||||
|
self.textures.append(texture) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (symbol.has(coral.shaders.Function)) |function| {
|
||||||
|
self.functions.append(function) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.Overflow => error.TooManySymbols,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parseText(self: *Self, arena: *std.heap.ArenaAllocator, source_text: []const u8) std.mem.Allocator.Error!ParseResult {
|
||||||
|
errdefer {
|
||||||
|
self.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
var source = tokens.Stream.init(source_text);
|
||||||
|
|
||||||
|
self.parse(arena, &source) catch |parse_error| {
|
||||||
|
const arena_allocator = arena.allocator();
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.failure = try switch (parse_error) {
|
||||||
|
error.OutOfMemory => error.OutOfMemory,
|
||||||
|
|
||||||
|
error.ImmutableStorage => coral.bytes.allocFormatted(arena_allocator, "{location}: attempt to modify an immutable value", .{
|
||||||
|
.location = source.location,
|
||||||
|
}),
|
||||||
|
|
||||||
|
error.TooManySymbols => coral.bytes.allocFormatted(arena_allocator, "{location}: number of definitions in the current scope exceeded", .{
|
||||||
|
.location = source.location,
|
||||||
|
}),
|
||||||
|
|
||||||
|
error.IncompatibleArguments => coral.bytes.allocFormatted(arena_allocator, "{location}: incompatible set of arguments", .{
|
||||||
|
.location = source.location,
|
||||||
|
}),
|
||||||
|
|
||||||
|
error.IncompatibleTypes => coral.bytes.allocFormatted(arena_allocator, "{location}: incompatible types", .{
|
||||||
|
.location = source.location,
|
||||||
|
}),
|
||||||
|
|
||||||
|
error.DuplicateIdentifier => coral.bytes.allocFormatted(arena_allocator, "{location}: {kind} {token} already defined", .{
|
||||||
|
.location = source.location,
|
||||||
|
.token = source.current,
|
||||||
|
.kind = std.meta.activeTag(source.current),
|
||||||
|
}),
|
||||||
|
|
||||||
|
error.UnexpectedToken => coral.bytes.allocFormatted(arena_allocator, "{location}: unexpected {token}", .{
|
||||||
|
.location = source.location,
|
||||||
|
.token = source.current,
|
||||||
|
}),
|
||||||
|
|
||||||
|
error.UndefinedIdentifier => coral.bytes.allocFormatted(arena_allocator, "{location}: undefined identifier {token}", .{
|
||||||
|
.location = source.location,
|
||||||
|
.token = source.current,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return .ok;
|
||||||
|
}
|
698
src/coral/shaders/Scope.zig
Normal file
698
src/coral/shaders/Scope.zig
Normal file
@ -0,0 +1,698 @@
|
|||||||
|
const coral = @import("../coral.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const tokens = @import("./tokens.zig");
|
||||||
|
|
||||||
|
identifiers: [max][:0]const u8 = undefined,
|
||||||
|
symbols: [max]coral.Box = undefined,
|
||||||
|
len: usize = 0,
|
||||||
|
has_enclosing: ?*const Self = null,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn clear(self: *Self) void {
|
||||||
|
self.len = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(arena: *std.heap.ArenaAllocator, node: anytype) std.mem.Allocator.Error!*@TypeOf(node) {
|
||||||
|
const allocation = try arena.allocator().create(@TypeOf(node));
|
||||||
|
|
||||||
|
allocation.* = node;
|
||||||
|
|
||||||
|
return allocation;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn createScope(self: *const Self, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!*Self {
|
||||||
|
return create(arena, Self{
|
||||||
|
.has_enclosing = self,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn define(self: *Self, arena: *std.heap.ArenaAllocator, symbol: anytype) coral.shaders.DefinitionError!*@TypeOf(symbol) {
|
||||||
|
const Symbol = @TypeOf(symbol);
|
||||||
|
const identifier_field = "identifier";
|
||||||
|
const Identifier = [:0]const u8;
|
||||||
|
|
||||||
|
const identifier = switch (@hasField(Symbol, identifier_field)) {
|
||||||
|
true => switch (@TypeOf(symbol.identifier)) {
|
||||||
|
Identifier => symbol.identifier,
|
||||||
|
|
||||||
|
else => @compileError(std.fmt.comptimePrint("field {s}.{s} must be of type `{s}`", .{
|
||||||
|
@typeName(Symbol),
|
||||||
|
identifier_field,
|
||||||
|
@typeName(Identifier),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
|
||||||
|
false => @compileError(std.fmt.comptimePrint("Type `{s}` must contain a field identifierd `{s}`", .{
|
||||||
|
@typeName(Symbol),
|
||||||
|
identifier_field,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (self.exists(identifier)) {
|
||||||
|
return error.DuplicateIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.len >= max) {
|
||||||
|
return error.TooManySymbols;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stored_symbol = &self.symbols[self.len];
|
||||||
|
|
||||||
|
self.identifiers[self.len] = identifier;
|
||||||
|
stored_symbol.* = try .initWithAllocator(arena.allocator(), symbol);
|
||||||
|
self.len += 1;
|
||||||
|
|
||||||
|
return stored_symbol.has(Symbol).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn exists(self: Self, identifier: []const u8) bool {
|
||||||
|
for (self.identifiers) |existing_identifier| {
|
||||||
|
if (std.mem.eql(u8, existing_identifier, identifier)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.has_enclosing) |enclosing| {
|
||||||
|
return enclosing.exists(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hasGlobal(self: *const Self, comptime Symbol: type, identifier: []const u8) ?*const Symbol {
|
||||||
|
if (self.hasLocal(Symbol, identifier)) |local| {
|
||||||
|
return local;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.has_enclosing) |enclosing| {
|
||||||
|
return enclosing.hasGlobal(Symbol, identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hasLocal(self: *const Self, comptime Symbol: type, identifier: []const u8) ?*const Symbol {
|
||||||
|
for (0..self.len) |i| {
|
||||||
|
if (std.mem.eql(u8, self.identifiers[i], identifier)) {
|
||||||
|
if (self.symbols[i].has(Symbol)) |symbol| {
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const max = 256;
|
||||||
|
|
||||||
|
pub fn parse(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!coral.Box {
|
||||||
|
const index = self.len;
|
||||||
|
|
||||||
|
switch (try source.current.expectAny(&.{.keyword_function})) {
|
||||||
|
.keyword_function => {
|
||||||
|
const function = try self.define(arena, coral.shaders.Function{
|
||||||
|
.identifier = try arena.allocator().dupeZ(u8, try source.skip(.newline).expectIdentifier()),
|
||||||
|
|
||||||
|
.block = &.{
|
||||||
|
.scope = &.{},
|
||||||
|
.depth = 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
const inner_scope = try self.createScope(arena);
|
||||||
|
|
||||||
|
function.has_parameter = switch (try source.skip(.newline).expectAny(&.{ .symbol_paren_right, .identifier })) {
|
||||||
|
.identifier => try inner_scope.parseParameter(arena, source),
|
||||||
|
.symbol_paren_right => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
try source.skip(.newline).expect(.symbol_arrow);
|
||||||
|
|
||||||
|
var peeking = source.*;
|
||||||
|
|
||||||
|
if (peeking.skip(.newline) != .symbol_brace_left) {
|
||||||
|
function.has_return_type = try self.parseType(source);
|
||||||
|
}
|
||||||
|
|
||||||
|
function.block = try inner_scope.parseBlock(arena, source);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
std.debug.assert((index + 1) == self.len);
|
||||||
|
|
||||||
|
return self.symbols[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseArgument(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Argument {
|
||||||
|
const expression = try self.parseExpression(arena, source);
|
||||||
|
|
||||||
|
return switch (try source.current.expectAny(&.{ .symbol_paren_right, .symbol_comma })) {
|
||||||
|
.symbol_paren_right => try create(arena, coral.shaders.Argument{
|
||||||
|
.expression = expression,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.symbol_comma => {
|
||||||
|
var peeking = source.*;
|
||||||
|
|
||||||
|
if (peeking.skip(.newline) == .symbol_paren_right) {
|
||||||
|
source.* = peeking;
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Argument{ .expression = expression });
|
||||||
|
}
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Argument{
|
||||||
|
.expression = expression,
|
||||||
|
.has_next = try self.parseArgument(arena, source),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseBlock(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Block {
|
||||||
|
try source.skip(.newline).expect(.symbol_brace_left);
|
||||||
|
|
||||||
|
return create(arena, coral.shaders.Block{
|
||||||
|
.scope = self,
|
||||||
|
.depth = source.depth,
|
||||||
|
|
||||||
|
.has_statement = switch (source.skip(.newline)) {
|
||||||
|
.symbol_brace_right => null,
|
||||||
|
else => try self.parseBlockStatement(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseBlockStatement(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Statement {
|
||||||
|
switch (try source.current.expectAny(&.{ .keyword_let, .keyword_var, .keyword_return, .identifier })) {
|
||||||
|
.identifier => {
|
||||||
|
const identifier = try source.current.expectIdentifier();
|
||||||
|
|
||||||
|
if (self.hasLocal(coral.shaders.Local, identifier)) |local| {
|
||||||
|
if (local.is_constant) {
|
||||||
|
return error.ImmutableStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
try source.skip(.newline).expect(.symbol_equals);
|
||||||
|
|
||||||
|
const expression = try self.parseExpression(arena, source);
|
||||||
|
|
||||||
|
if (try expression.inferType() != local.type) {
|
||||||
|
return error.IncompatibleTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Statement{
|
||||||
|
.mutate_local = .{
|
||||||
|
.local = local,
|
||||||
|
.expression = expression,
|
||||||
|
|
||||||
|
.has_next = switch (source.current) {
|
||||||
|
.symbol_brace_right => null,
|
||||||
|
else => try self.parseBlockStatement(arena, source),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasGlobal(coral.shaders.Output, identifier)) |output| {
|
||||||
|
try source.skip(.newline).expect(.symbol_equals);
|
||||||
|
|
||||||
|
const expression = try self.parseExpression(arena, source);
|
||||||
|
|
||||||
|
if (try expression.inferType() != output.type) {
|
||||||
|
return error.IncompatibleTypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Statement{
|
||||||
|
.mutate_output = .{
|
||||||
|
.output = output,
|
||||||
|
.expression = expression,
|
||||||
|
|
||||||
|
.has_next = switch (source.current) {
|
||||||
|
.symbol_brace_right => null,
|
||||||
|
else => try self.parseBlockStatement(arena, source),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.ImmutableStorage;
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_return => {
|
||||||
|
return try create(arena, coral.shaders.Statement{
|
||||||
|
.return_expression = try self.parseExpression(arena, source),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_var, .keyword_let => {
|
||||||
|
const local = try self.define(arena, coral.shaders.Local{
|
||||||
|
.is_constant = source.current != .keyword_var,
|
||||||
|
.identifier = try arena.allocator().dupeZ(u8, try source.next().expectIdentifier()),
|
||||||
|
.expression = &.{ .int = .{ .literal = "0" } },
|
||||||
|
.type = .int,
|
||||||
|
});
|
||||||
|
|
||||||
|
try source.skip(.newline).expect(.symbol_equals);
|
||||||
|
|
||||||
|
local.expression = try self.parseExpression(arena, source);
|
||||||
|
local.type = try local.expression.inferType();
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Statement{
|
||||||
|
.declare_local = .{
|
||||||
|
.local = local,
|
||||||
|
|
||||||
|
.has_next = try switch (source.current) {
|
||||||
|
.symbol_brace_right => null,
|
||||||
|
else => self.parseBlockStatement(arena, source),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
|
||||||
|
const expression = try self.parseAdditiveExpression(arena, source);
|
||||||
|
|
||||||
|
if (source.current == .symbol_equals) {
|
||||||
|
return switch (expression.*) {
|
||||||
|
.get_local => |local| switch (local.is_constant) {
|
||||||
|
false => try create(arena, coral.shaders.Expression{
|
||||||
|
.mutate_local = .{
|
||||||
|
.local = local,
|
||||||
|
.expression = try self.parseExpression(arena, source),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
true => error.ImmutableStorage,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => error.UnexpectedToken,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseAdditiveExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
|
||||||
|
const expression = try self.parseEqualityExpression(arena, source);
|
||||||
|
|
||||||
|
if (source.current == .symbol_plus) {
|
||||||
|
return create(arena, coral.shaders.Expression{
|
||||||
|
.add = .{
|
||||||
|
.rhs_expression = try self.parseEqualityExpression(arena, source),
|
||||||
|
.lhs_expression = expression,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.current == .symbol_minus) {
|
||||||
|
return create(arena, coral.shaders.Expression{
|
||||||
|
.subtract = .{
|
||||||
|
.rhs_expression = try self.parseEqualityExpression(arena, source),
|
||||||
|
.lhs_expression = expression,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseComparativeExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
|
||||||
|
const expression = try self.parseTermExpression(arena, source);
|
||||||
|
|
||||||
|
if (source.current == .symbol_greater_than) {
|
||||||
|
return create(arena, coral.shaders.Expression{
|
||||||
|
.greater_than = .{
|
||||||
|
.rhs_expression = try self.parseTermExpression(arena, source),
|
||||||
|
.lhs_expression = expression,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.current == .symbol_greater_equals) {
|
||||||
|
return create(arena, coral.shaders.Expression{
|
||||||
|
.greater_equal = .{
|
||||||
|
.rhs_expression = try self.parseTermExpression(arena, source),
|
||||||
|
.lhs_expression = expression,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.current == .symbol_lesser_than) {
|
||||||
|
return create(arena, coral.shaders.Expression{
|
||||||
|
.divide = .{
|
||||||
|
.rhs_expression = try self.parseTermExpression(arena, source),
|
||||||
|
.lhs_expression = expression,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.current == .symbol_lesser_equals) {
|
||||||
|
return create(arena, coral.shaders.Expression{
|
||||||
|
.divide = .{
|
||||||
|
.rhs_expression = try self.parseTermExpression(arena, source),
|
||||||
|
.lhs_expression = expression,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseEqualityExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
|
||||||
|
const expression = try self.parseComparativeExpression(arena, source);
|
||||||
|
|
||||||
|
if (source.current == .symbol_double_equals) {
|
||||||
|
return create(arena, coral.shaders.Expression{
|
||||||
|
.equal = .{
|
||||||
|
.rhs_expression = try self.parseComparativeExpression(arena, source),
|
||||||
|
.lhs_expression = expression,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseFactorExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
|
||||||
|
var expression = try self.parseOperandExpression(arena, source);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
switch (source.skip(.newline)) {
|
||||||
|
.symbol_period => {
|
||||||
|
const object_type = try expression.inferType();
|
||||||
|
|
||||||
|
expression = try create(arena, coral.shaders.Expression{
|
||||||
|
.get_object = .{
|
||||||
|
.object_expression = expression,
|
||||||
|
|
||||||
|
.field = object_type.hasField(try source.skip(.newline).expectIdentifier()) orelse {
|
||||||
|
return error.UndefinedIdentifier;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
return expression;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseOperandExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
|
||||||
|
switch (source.skip(.newline)) {
|
||||||
|
.symbol_minus => {
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.negate_expression = try self.parseExpression(arena, source),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_paren_left => {
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.group_expression = try self.parseExpression(arena, source),
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.scalar => {
|
||||||
|
const arena_allocator = arena.allocator();
|
||||||
|
const scalar = try source.current.expectScalar();
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(u8, scalar, '.')) |index| {
|
||||||
|
return create(arena, coral.shaders.Expression{
|
||||||
|
.float = .{
|
||||||
|
.whole = try arena_allocator.dupeZ(u8, scalar[0..index]),
|
||||||
|
.decimal = try arena_allocator.dupeZ(u8, scalar[index + 1 ..]),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return create(arena, coral.shaders.Expression{
|
||||||
|
.int = .{
|
||||||
|
.literal = try arena_allocator.dupeZ(u8, scalar),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.identifier => |identifier| {
|
||||||
|
if (self.hasGlobal(coral.shaders.Function, identifier)) |function| {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
var peeking = source.*;
|
||||||
|
|
||||||
|
switch (peeking.skip(.newline)) {
|
||||||
|
.symbol_paren_left => {
|
||||||
|
source.* = peeking;
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.invoke = .{
|
||||||
|
.function = function,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.invoke = .{
|
||||||
|
.function = function,
|
||||||
|
.has_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasGlobal(coral.shaders.Uniform, identifier)) |uniform| {
|
||||||
|
try source.skip(.newline).expect(.symbol_period);
|
||||||
|
|
||||||
|
const field_name = try source.skip(.newline).expectIdentifier();
|
||||||
|
|
||||||
|
const first_field = uniform.has_field orelse {
|
||||||
|
return error.UndefinedIdentifier;
|
||||||
|
};
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.get_uniform = .{
|
||||||
|
.uniform = uniform,
|
||||||
|
|
||||||
|
.field = first_field.has(field_name) orelse {
|
||||||
|
return error.UndefinedIdentifier;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasGlobal(coral.shaders.Texture, identifier)) |texture| {
|
||||||
|
return try create(arena, coral.shaders.Expression{ .get_texture = texture });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasGlobal(coral.shaders.Output, identifier)) |output| {
|
||||||
|
return try create(arena, coral.shaders.Expression{ .get_output = output });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasGlobal(coral.shaders.Input, identifier)) |input| {
|
||||||
|
return try create(arena, coral.shaders.Expression{ .get_input = input });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasLocal(coral.shaders.Local, identifier)) |local| {
|
||||||
|
return try create(arena, coral.shaders.Expression{ .get_local = local });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.hasLocal(coral.shaders.Parameter, identifier)) |parameter| {
|
||||||
|
return try create(arena, coral.shaders.Expression{ .get_parameter = parameter });
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.UndefinedIdentifier;
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_pow => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.pow = .{
|
||||||
|
.allowed_parameter_types = &.{ .float, .float2, .float3, .float4 },
|
||||||
|
.expected_parameter_count = 2,
|
||||||
|
.first_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_abs => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.abs = .{
|
||||||
|
.allowed_parameter_types = &.{ .float, .float2, .float3, .float4 },
|
||||||
|
.expected_parameter_count = 1,
|
||||||
|
.first_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_sin => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.sin = .{
|
||||||
|
.allowed_parameter_types = &.{ .float, .float2, .float3, .float4 },
|
||||||
|
.expected_parameter_count = 1,
|
||||||
|
.first_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_int => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.convert = .{
|
||||||
|
.target_type = .int,
|
||||||
|
.parameter_types = &.{.float},
|
||||||
|
.first_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_float => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.convert = .{
|
||||||
|
.target_type = .float,
|
||||||
|
.parameter_types = &.{.int},
|
||||||
|
.first_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_float2 => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.convert = .{
|
||||||
|
.target_type = .float2,
|
||||||
|
.parameter_types = &.{ .float, .float },
|
||||||
|
.first_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_float3 => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.convert = .{
|
||||||
|
.target_type = .float3,
|
||||||
|
.parameter_types = &.{ .float, .float, .float },
|
||||||
|
.first_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_float4 => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.convert = .{
|
||||||
|
.target_type = .float4,
|
||||||
|
.parameter_types = &.{ .float, .float, .float, .float },
|
||||||
|
.first_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_float4x4 => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.convert = .{
|
||||||
|
.target_type = .float4x4,
|
||||||
|
.parameter_types = &.{ .float4, .float4, .float4, .float4 },
|
||||||
|
.first_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
.keyword_sample => {
|
||||||
|
try source.skip(.newline).expect(.symbol_paren_left);
|
||||||
|
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.sample = .{
|
||||||
|
.signatures = &.{
|
||||||
|
.{
|
||||||
|
.parameter_types = &.{ .texture2, .float2 },
|
||||||
|
.return_type = .float4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
.has_argument = try self.parseArgument(arena, source),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
return error.UnexpectedToken;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseTermExpression(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Expression {
|
||||||
|
const expression = try self.parseFactorExpression(arena, source);
|
||||||
|
|
||||||
|
if (source.current == .symbol_asterisk) {
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.multiply = .{
|
||||||
|
.rhs_expression = try self.parseFactorExpression(arena, source),
|
||||||
|
.lhs_expression = expression,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source.current == .symbol_forward_slash) {
|
||||||
|
return try create(arena, coral.shaders.Expression{
|
||||||
|
.divide = .{
|
||||||
|
.rhs_expression = try self.parseFactorExpression(arena, source),
|
||||||
|
.lhs_expression = expression,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseParameter(self: *Self, arena: *std.heap.ArenaAllocator, source: *tokens.Stream) coral.shaders.ParsingError!?*const coral.shaders.Parameter {
|
||||||
|
const parameter = try self.define(arena, coral.shaders.Parameter{
|
||||||
|
.identifier = try arena.allocator().dupeZ(u8, try source.current.expectIdentifier()),
|
||||||
|
.type = .int,
|
||||||
|
});
|
||||||
|
|
||||||
|
parameter.type = try self.parseType(source);
|
||||||
|
|
||||||
|
if (try source.skip(.newline).expectAny(&.{ .symbol_paren_right, .symbol_comma }) == .symbol_comma) {
|
||||||
|
parameter.has_next = switch (try source.skip(.newline).expectAny(&.{ .symbol_paren_right, .identifier })) {
|
||||||
|
.symbol_paren_right => null,
|
||||||
|
.identifier => try self.parseParameter(arena, source),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parseType(self: *const Self, source: *tokens.Stream) coral.shaders.ParsingError!*const coral.shaders.Type {
|
||||||
|
return switch (source.skip(.newline)) {
|
||||||
|
.keyword_float => .float,
|
||||||
|
.keyword_float4x4 => .float4x4,
|
||||||
|
.keyword_float2 => .float2,
|
||||||
|
.keyword_float3 => .float3,
|
||||||
|
.keyword_float4 => .float4,
|
||||||
|
.keyword_int => .int,
|
||||||
|
.identifier => |identifier| self.hasGlobal(coral.shaders.Type, identifier) orelse error.UndefinedIdentifier,
|
||||||
|
else => error.UnexpectedToken,
|
||||||
|
};
|
||||||
|
}
|
924
src/coral/shaders/Type.zig
Normal file
924
src/coral/shaders/Type.zig
Normal file
@ -0,0 +1,924 @@
|
|||||||
|
const coral = @import("../coral.zig");
|
||||||
|
|
||||||
|
identifier: [:0]const u8,
|
||||||
|
layout: Layout,
|
||||||
|
|
||||||
|
pub const Layout = union(enum) {
|
||||||
|
handle,
|
||||||
|
scalar,
|
||||||
|
vector: VectorWidth,
|
||||||
|
matrix: VectorWidth,
|
||||||
|
record_fields: ?*const coral.shaders.Field,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const VectorWidth = enum(u2) {
|
||||||
|
@"1",
|
||||||
|
@"2",
|
||||||
|
@"3",
|
||||||
|
@"4",
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const float = &Self{
|
||||||
|
.identifier = "float",
|
||||||
|
.layout = .scalar,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const float2 = &Self{
|
||||||
|
.identifier = "float2",
|
||||||
|
.layout = .{ .vector = .@"2" },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const float3 = &Self{
|
||||||
|
.identifier = "float3",
|
||||||
|
.layout = .{ .vector = .@"3" },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const float4 = &Self{
|
||||||
|
.identifier = "float4",
|
||||||
|
.layout = .{ .vector = .@"4" },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const float4x4 = &Self{
|
||||||
|
.identifier = "float4x4",
|
||||||
|
.layout = .{ .matrix = .@"4" },
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn hasField(self: Self, field_identifier: []const u8) ?*const coral.shaders.Field {
|
||||||
|
return switch (self.layout) {
|
||||||
|
.scalar, .matrix, .handle => null,
|
||||||
|
.record_fields => |has_field| if (has_field) |field| field.has(field_identifier) else null,
|
||||||
|
|
||||||
|
.vector => |vector_width| switch (vector_width) {
|
||||||
|
.@"1" => switch (field_identifier.len) {
|
||||||
|
1 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => .vector_x,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
.@"2" => switch (field_identifier.len) {
|
||||||
|
1 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => .vector_x,
|
||||||
|
'y', 'g' => .vector_y,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
2 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => .vector_xx,
|
||||||
|
'y', 'g' => .vector_xy,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => .vector_yx,
|
||||||
|
'y', 'g' => .vector_yy,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
.@"3" => switch (field_identifier.len) {
|
||||||
|
1 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => .vector_x,
|
||||||
|
'y', 'g' => .vector_y,
|
||||||
|
'z', 'b' => .vector_z,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
2 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => .vector_xx,
|
||||||
|
'y', 'g' => .vector_xy,
|
||||||
|
'z', 'b' => .vector_xz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => .vector_yx,
|
||||||
|
'y', 'g' => .vector_yy,
|
||||||
|
'z', 'b' => .vector_yz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
3 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_xxx,
|
||||||
|
'y', 'g' => .vector_xxy,
|
||||||
|
'z', 'b' => .vector_xxz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_xyx,
|
||||||
|
'y', 'g' => .vector_xyy,
|
||||||
|
'z', 'b' => .vector_xyz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_xzx,
|
||||||
|
'y', 'g' => .vector_xzy,
|
||||||
|
'z', 'b' => .vector_xzz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_yxx,
|
||||||
|
'y', 'g' => .vector_yxy,
|
||||||
|
'z', 'b' => .vector_yxz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_yyx,
|
||||||
|
'y', 'g' => .vector_yyy,
|
||||||
|
'z', 'b' => .vector_yyz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_yzx,
|
||||||
|
'y', 'g' => .vector_yzy,
|
||||||
|
'z', 'b' => .vector_yzz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_zxx,
|
||||||
|
'y', 'g' => .vector_zxy,
|
||||||
|
'z', 'b' => .vector_zxz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_zyx,
|
||||||
|
'y', 'g' => .vector_zyy,
|
||||||
|
'z', 'b' => .vector_zyz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_zzx,
|
||||||
|
'y', 'g' => .vector_zzy,
|
||||||
|
'z', 'b' => .vector_zzz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
.@"4" => switch (field_identifier.len) {
|
||||||
|
1 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => .vector_x,
|
||||||
|
'y', 'g' => .vector_y,
|
||||||
|
'z', 'b' => .vector_z,
|
||||||
|
'w', 'a' => .vector_w,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
2 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => .vector_xx,
|
||||||
|
'y', 'g' => .vector_xy,
|
||||||
|
'z', 'b' => .vector_xz,
|
||||||
|
'w', 'a' => .vector_xw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => .vector_yx,
|
||||||
|
'y', 'g' => .vector_yy,
|
||||||
|
'z', 'b' => .vector_yz,
|
||||||
|
'w', 'a' => .vector_yz,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
3 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_xxx,
|
||||||
|
'y', 'g' => .vector_xxy,
|
||||||
|
'z', 'b' => .vector_xxz,
|
||||||
|
'w', 'a' => .vector_xxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_xyx,
|
||||||
|
'y', 'g' => .vector_xyy,
|
||||||
|
'z', 'b' => .vector_xyz,
|
||||||
|
'w', 'a' => .vector_xyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_xzx,
|
||||||
|
'y', 'g' => .vector_xzy,
|
||||||
|
'z', 'b' => .vector_xzz,
|
||||||
|
'w', 'a' => .vector_xzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_yxx,
|
||||||
|
'y', 'g' => .vector_yxy,
|
||||||
|
'z', 'b' => .vector_yxz,
|
||||||
|
'w', 'a' => .vector_yxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_yyx,
|
||||||
|
'y', 'g' => .vector_yyy,
|
||||||
|
'z', 'b' => .vector_yyz,
|
||||||
|
'w', 'a' => .vector_yyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_yzx,
|
||||||
|
'y', 'g' => .vector_yzy,
|
||||||
|
'z', 'b' => .vector_yzz,
|
||||||
|
'w', 'a' => .vector_yzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_zxx,
|
||||||
|
'y', 'g' => .vector_zxy,
|
||||||
|
'z', 'b' => .vector_zxz,
|
||||||
|
'w', 'a' => .vector_zxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_zyx,
|
||||||
|
'y', 'g' => .vector_zyy,
|
||||||
|
'z', 'b' => .vector_zyz,
|
||||||
|
'w', 'a' => .vector_zyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => .vector_zzx,
|
||||||
|
'y', 'g' => .vector_zzy,
|
||||||
|
'z', 'b' => .vector_zzz,
|
||||||
|
'w', 'a' => .vector_zzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
4 => switch (field_identifier[0]) {
|
||||||
|
'x', 'r' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xxxx,
|
||||||
|
'y', 'g' => .vector_xxxy,
|
||||||
|
'z', 'b' => .vector_xxxz,
|
||||||
|
'w', 'a' => .vector_xxxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xxyx,
|
||||||
|
'y', 'g' => .vector_xxyy,
|
||||||
|
'z', 'b' => .vector_xxyz,
|
||||||
|
'w', 'a' => .vector_xxyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xxzx,
|
||||||
|
'y', 'g' => .vector_xxzy,
|
||||||
|
'z', 'b' => .vector_xxzz,
|
||||||
|
'w', 'a' => .vector_xxzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xxwx,
|
||||||
|
'y', 'g' => .vector_xxwy,
|
||||||
|
'z', 'b' => .vector_xxwz,
|
||||||
|
'w', 'a' => .vector_xxww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xyxx,
|
||||||
|
'y', 'g' => .vector_xyxy,
|
||||||
|
'z', 'b' => .vector_xyxz,
|
||||||
|
'w', 'a' => .vector_xyxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xyyx,
|
||||||
|
'y', 'g' => .vector_xyyy,
|
||||||
|
'z', 'b' => .vector_xyyz,
|
||||||
|
'w', 'a' => .vector_xyyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xyzx,
|
||||||
|
'y', 'g' => .vector_xyzy,
|
||||||
|
'z', 'b' => .vector_xyzz,
|
||||||
|
'w', 'a' => .vector_xyzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xywx,
|
||||||
|
'y', 'g' => .vector_xywy,
|
||||||
|
'z', 'b' => .vector_xywz,
|
||||||
|
'w', 'a' => .vector_xyww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xzxx,
|
||||||
|
'y', 'g' => .vector_xzxy,
|
||||||
|
'z', 'b' => .vector_xzxz,
|
||||||
|
'w', 'a' => .vector_xzxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xzyx,
|
||||||
|
'y', 'g' => .vector_xzyy,
|
||||||
|
'z', 'b' => .vector_xzyz,
|
||||||
|
'w', 'a' => .vector_xzyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xzzx,
|
||||||
|
'y', 'g' => .vector_xzzy,
|
||||||
|
'z', 'b' => .vector_xzzz,
|
||||||
|
'w', 'a' => .vector_xzzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xzwx,
|
||||||
|
'y', 'g' => .vector_xzwy,
|
||||||
|
'z', 'b' => .vector_xzwz,
|
||||||
|
'w', 'a' => .vector_xzww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xwxx,
|
||||||
|
'y', 'g' => .vector_xwxy,
|
||||||
|
'z', 'b' => .vector_xwxz,
|
||||||
|
'w', 'a' => .vector_xwxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xwyx,
|
||||||
|
'y', 'g' => .vector_xwyy,
|
||||||
|
'z', 'b' => .vector_xwyz,
|
||||||
|
'w', 'a' => .vector_xwyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xwzx,
|
||||||
|
'y', 'g' => .vector_xwzy,
|
||||||
|
'z', 'b' => .vector_xwzz,
|
||||||
|
'w', 'a' => .vector_xwzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_xwwx,
|
||||||
|
'y', 'g' => .vector_xwwy,
|
||||||
|
'z', 'b' => .vector_xwwz,
|
||||||
|
'w', 'a' => .vector_xwww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yxxx,
|
||||||
|
'y', 'g' => .vector_yxxy,
|
||||||
|
'z', 'b' => .vector_yxxz,
|
||||||
|
'w', 'a' => .vector_yxxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yxyx,
|
||||||
|
'y', 'g' => .vector_yxyy,
|
||||||
|
'z', 'b' => .vector_yxyz,
|
||||||
|
'w', 'a' => .vector_yxyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yxzx,
|
||||||
|
'y', 'g' => .vector_yxzy,
|
||||||
|
'z', 'b' => .vector_yxzz,
|
||||||
|
'w', 'a' => .vector_yxzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yxwx,
|
||||||
|
'y', 'g' => .vector_yxwy,
|
||||||
|
'z', 'b' => .vector_yxwz,
|
||||||
|
'w', 'a' => .vector_yxww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yyxx,
|
||||||
|
'y', 'g' => .vector_yyxy,
|
||||||
|
'z', 'b' => .vector_yyxz,
|
||||||
|
'w', 'a' => .vector_yyxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yyyx,
|
||||||
|
'y', 'g' => .vector_yyyy,
|
||||||
|
'z', 'b' => .vector_yyyz,
|
||||||
|
'w', 'a' => .vector_yyyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yyzx,
|
||||||
|
'y', 'g' => .vector_yyzy,
|
||||||
|
'z', 'b' => .vector_yyzz,
|
||||||
|
'w', 'a' => .vector_yyzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yywx,
|
||||||
|
'y', 'g' => .vector_yywy,
|
||||||
|
'z', 'b' => .vector_yywz,
|
||||||
|
'w', 'a' => .vector_yyww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yzxx,
|
||||||
|
'y', 'g' => .vector_yzxy,
|
||||||
|
'z', 'b' => .vector_yzxz,
|
||||||
|
'w', 'a' => .vector_yzxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yzyx,
|
||||||
|
'y', 'g' => .vector_yzyy,
|
||||||
|
'z', 'b' => .vector_yzyz,
|
||||||
|
'w', 'a' => .vector_yzyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yzzx,
|
||||||
|
'y', 'g' => .vector_yzzy,
|
||||||
|
'z', 'b' => .vector_yzzz,
|
||||||
|
'w', 'a' => .vector_yzzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_yzwx,
|
||||||
|
'y', 'g' => .vector_yzwy,
|
||||||
|
'z', 'b' => .vector_yzwz,
|
||||||
|
'w', 'a' => .vector_yzww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_ywxx,
|
||||||
|
'y', 'g' => .vector_ywxy,
|
||||||
|
'z', 'b' => .vector_ywxz,
|
||||||
|
'w', 'a' => .vector_ywxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_ywyx,
|
||||||
|
'y', 'g' => .vector_ywyy,
|
||||||
|
'z', 'b' => .vector_ywyz,
|
||||||
|
'w', 'a' => .vector_ywyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_ywzx,
|
||||||
|
'y', 'g' => .vector_ywzy,
|
||||||
|
'z', 'b' => .vector_ywzz,
|
||||||
|
'w', 'a' => .vector_ywzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_ywwx,
|
||||||
|
'y', 'g' => .vector_ywwy,
|
||||||
|
'z', 'b' => .vector_ywwz,
|
||||||
|
'w', 'a' => .vector_ywww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zxxx,
|
||||||
|
'y', 'g' => .vector_zxxy,
|
||||||
|
'z', 'b' => .vector_zxxz,
|
||||||
|
'w', 'a' => .vector_zxxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zxyx,
|
||||||
|
'y', 'g' => .vector_zxyy,
|
||||||
|
'z', 'b' => .vector_zxyz,
|
||||||
|
'w', 'a' => .vector_zxyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zxzx,
|
||||||
|
'y', 'g' => .vector_zxzy,
|
||||||
|
'z', 'b' => .vector_zxzz,
|
||||||
|
'w', 'a' => .vector_zxzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zxwx,
|
||||||
|
'y', 'g' => .vector_zxwy,
|
||||||
|
'z', 'b' => .vector_zxwz,
|
||||||
|
'w', 'a' => .vector_zxww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zyxx,
|
||||||
|
'y', 'g' => .vector_zyxy,
|
||||||
|
'z', 'b' => .vector_zyxz,
|
||||||
|
'w', 'a' => .vector_zyxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zyyx,
|
||||||
|
'y', 'g' => .vector_zyyy,
|
||||||
|
'z', 'b' => .vector_zyyz,
|
||||||
|
'w', 'a' => .vector_zyyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zyzx,
|
||||||
|
'y', 'g' => .vector_zyzy,
|
||||||
|
'z', 'b' => .vector_zyzz,
|
||||||
|
'w', 'a' => .vector_zyzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zywx,
|
||||||
|
'y', 'g' => .vector_zywy,
|
||||||
|
'z', 'b' => .vector_zywz,
|
||||||
|
'w', 'a' => .vector_zyww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zzxx,
|
||||||
|
'y', 'g' => .vector_zzxy,
|
||||||
|
'z', 'b' => .vector_zzxz,
|
||||||
|
'w', 'a' => .vector_zzxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zzyx,
|
||||||
|
'y', 'g' => .vector_zzyy,
|
||||||
|
'z', 'b' => .vector_zzyz,
|
||||||
|
'w', 'a' => .vector_zzyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zzzx,
|
||||||
|
'y', 'g' => .vector_zzzy,
|
||||||
|
'z', 'b' => .vector_zzzz,
|
||||||
|
'w', 'a' => .vector_zzzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zzwx,
|
||||||
|
'y', 'g' => .vector_zzwy,
|
||||||
|
'z', 'b' => .vector_zzwz,
|
||||||
|
'w', 'a' => .vector_zzww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zwxx,
|
||||||
|
'y', 'g' => .vector_zwxy,
|
||||||
|
'z', 'b' => .vector_zwxz,
|
||||||
|
'w', 'a' => .vector_zwxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zwyx,
|
||||||
|
'y', 'g' => .vector_zwyy,
|
||||||
|
'z', 'b' => .vector_zwyz,
|
||||||
|
'w', 'a' => .vector_zwyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zwzx,
|
||||||
|
'y', 'g' => .vector_zwzy,
|
||||||
|
'z', 'b' => .vector_zwzz,
|
||||||
|
'w', 'a' => .vector_zwzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_zwwx,
|
||||||
|
'y', 'g' => .vector_zwwy,
|
||||||
|
'z', 'b' => .vector_zwwz,
|
||||||
|
'w', 'a' => .vector_zwww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[1]) {
|
||||||
|
'x', 'r' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wxxx,
|
||||||
|
'y', 'g' => .vector_wxxy,
|
||||||
|
'z', 'b' => .vector_wxxz,
|
||||||
|
'w', 'a' => .vector_wxxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wxyx,
|
||||||
|
'y', 'g' => .vector_wxyy,
|
||||||
|
'z', 'b' => .vector_wxyz,
|
||||||
|
'w', 'a' => .vector_wxyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wxzx,
|
||||||
|
'y', 'g' => .vector_wxzy,
|
||||||
|
'z', 'b' => .vector_wxzz,
|
||||||
|
'w', 'a' => .vector_wxzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wxwx,
|
||||||
|
'y', 'g' => .vector_wxwy,
|
||||||
|
'z', 'b' => .vector_wxwz,
|
||||||
|
'w', 'a' => .vector_wxww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wyxx,
|
||||||
|
'y', 'g' => .vector_wyxy,
|
||||||
|
'z', 'b' => .vector_wyxz,
|
||||||
|
'w', 'a' => .vector_wyxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wyyx,
|
||||||
|
'y', 'g' => .vector_wyyy,
|
||||||
|
'z', 'b' => .vector_wyyz,
|
||||||
|
'w', 'a' => .vector_wyyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wyzx,
|
||||||
|
'y', 'g' => .vector_wyzy,
|
||||||
|
'z', 'b' => .vector_wyzz,
|
||||||
|
'w', 'a' => .vector_wyzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wywx,
|
||||||
|
'y', 'g' => .vector_wywy,
|
||||||
|
'z', 'b' => .vector_wywz,
|
||||||
|
'w', 'a' => .vector_wyww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wzxx,
|
||||||
|
'y', 'g' => .vector_wzxy,
|
||||||
|
'z', 'b' => .vector_wzxz,
|
||||||
|
'w', 'a' => .vector_wzxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wzyx,
|
||||||
|
'y', 'g' => .vector_wzyy,
|
||||||
|
'z', 'b' => .vector_wzyz,
|
||||||
|
'w', 'a' => .vector_wzyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wzzx,
|
||||||
|
'y', 'g' => .vector_wzzy,
|
||||||
|
'z', 'b' => .vector_wzzz,
|
||||||
|
'w', 'a' => .vector_wzzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wzwx,
|
||||||
|
'y', 'g' => .vector_wzwy,
|
||||||
|
'z', 'b' => .vector_wzwz,
|
||||||
|
'w', 'a' => .vector_wzww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[2]) {
|
||||||
|
'x', 'r' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wwxx,
|
||||||
|
'y', 'g' => .vector_wwxy,
|
||||||
|
'z', 'b' => .vector_wwxz,
|
||||||
|
'w', 'a' => .vector_wwxw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'y', 'g' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wwyx,
|
||||||
|
'y', 'g' => .vector_wwyy,
|
||||||
|
'z', 'b' => .vector_wwyz,
|
||||||
|
'w', 'a' => .vector_wwyw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'z', 'b' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wwzx,
|
||||||
|
'y', 'g' => .vector_wwzy,
|
||||||
|
'z', 'b' => .vector_wwzz,
|
||||||
|
'w', 'a' => .vector_wwzw,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
'w', 'a' => switch (field_identifier[3]) {
|
||||||
|
'x', 'r' => .vector_wwwx,
|
||||||
|
'y', 'g' => .vector_wwwy,
|
||||||
|
'z', 'b' => .vector_wwwz,
|
||||||
|
'w', 'a' => .vector_wwww,
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const int = &Self{
|
||||||
|
.identifier = "int",
|
||||||
|
.layout = .scalar,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const texture2 = &Self{
|
||||||
|
.identifier = "texture2",
|
||||||
|
.layout = .handle,
|
||||||
|
};
|
339
src/coral/shaders/glsl.zig
Normal file
339
src/coral/shaders/glsl.zig
Normal file
@ -0,0 +1,339 @@
|
|||||||
|
const coral = @import("../coral.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Version = enum {
|
||||||
|
core_430,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generate(source: coral.bytes.Writable, root: coral.shaders.Root, version: Version) coral.shaders.GenerationError!void {
|
||||||
|
try coral.bytes.writeFormatted(source, "#version {version_name}\n", .{
|
||||||
|
.version_name = switch (version) {
|
||||||
|
.core_430 => "430 core",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
for (root.uniforms.slice()) |uniform| {
|
||||||
|
try coral.bytes.writeFormatted(source, "\nlayout (binding = {binding}) uniform {semantic} {{\n", .{
|
||||||
|
.binding = coral.utf8.cDec(uniform.binding),
|
||||||
|
.semantic = uniform.semantic,
|
||||||
|
});
|
||||||
|
|
||||||
|
var has_field = uniform.has_field;
|
||||||
|
|
||||||
|
while (has_field) |field| : (has_field = field.has_next) {
|
||||||
|
try coral.bytes.writeAll(source, "\t");
|
||||||
|
try generateType(source, field.type);
|
||||||
|
try coral.bytes.writeFormatted(source, " {identifier};\n", .{ .identifier = field.identifier });
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeFormatted(source, "}} {identifier};\n", .{
|
||||||
|
.identifier = uniform.identifier,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (root.textures.slice()) |texture| {
|
||||||
|
try coral.bytes.writeFormatted(source, "\nlayout (binding = {binding}) uniform {layout} {identifier};\n", .{
|
||||||
|
.binding = coral.utf8.cDec(texture.binding),
|
||||||
|
.identifier = texture.identifier,
|
||||||
|
|
||||||
|
.layout = switch (texture.layout) {
|
||||||
|
.dimensions_2 => "sampler2D",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (root.inputs.slice()) |input| {
|
||||||
|
try coral.bytes.writeFormatted(source, "\nlayout (location = {location}) in ", .{
|
||||||
|
.location = coral.utf8.cDec(input.location),
|
||||||
|
});
|
||||||
|
|
||||||
|
try generateType(source, input.type);
|
||||||
|
try coral.bytes.writeFormatted(source, " {identifier};\n", .{ .identifier = input.identifier });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (root.outputs.slice()) |output| {
|
||||||
|
try coral.bytes.writeFormatted(source, "\nlayout (location = {location}) out ", .{
|
||||||
|
.location = coral.utf8.cDec(output.location),
|
||||||
|
});
|
||||||
|
|
||||||
|
try generateType(source, output.type);
|
||||||
|
try coral.bytes.writeFormatted(source, " {identifier};\n", .{ .identifier = output.identifier });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (root.functions.slice()) |function| {
|
||||||
|
if (function.has_return_type) |return_type| {
|
||||||
|
try coral.bytes.writeAll(source, "\n");
|
||||||
|
try generateType(source, return_type);
|
||||||
|
} else {
|
||||||
|
try coral.bytes.writeAll(source, "\nvoid");
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeFormatted(source, " {identifier}(", .{ .identifier = function.identifier });
|
||||||
|
|
||||||
|
var has_parameter = function.has_parameter;
|
||||||
|
|
||||||
|
if (has_parameter) |first_parameter| {
|
||||||
|
try generateType(source, first_parameter.type);
|
||||||
|
try coral.bytes.writeFormatted(source, " {identifier}", .{ .identifier = first_parameter.identifier });
|
||||||
|
|
||||||
|
has_parameter = first_parameter.has_next;
|
||||||
|
|
||||||
|
while (has_parameter) |parameter| : (has_parameter = parameter.has_next) {
|
||||||
|
try coral.bytes.writeAll(source, ", ");
|
||||||
|
try generateType(source, parameter.type);
|
||||||
|
try coral.bytes.writeFormatted(source, " {identifier}", .{ .identifier = parameter.identifier });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeAll(source, ") ");
|
||||||
|
try generateBlock(source, function.block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateArguments(source: coral.bytes.Writable, first_argument: *const coral.shaders.Argument) coral.bytes.ReadWriteError!void {
|
||||||
|
try generateExpression(source, first_argument.expression);
|
||||||
|
|
||||||
|
var has_next_argument = first_argument.has_next;
|
||||||
|
|
||||||
|
while (has_next_argument) |next_argument| : (has_next_argument = next_argument.has_next) {
|
||||||
|
try coral.bytes.writeAll(source, ", ");
|
||||||
|
try generateExpression(source, next_argument.expression);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateBlock(source: coral.bytes.Writable, block: *const coral.shaders.Block) coral.bytes.ReadWriteError!void {
|
||||||
|
try coral.bytes.writeAll(source, "{\n");
|
||||||
|
|
||||||
|
var has_next_statement = block.has_statement;
|
||||||
|
|
||||||
|
while (has_next_statement) |statement| {
|
||||||
|
try coral.bytes.writeN(source, "\t", block.depth);
|
||||||
|
|
||||||
|
switch (statement.*) {
|
||||||
|
.return_expression => |return_expression| {
|
||||||
|
try coral.bytes.writeAll(source, "return ");
|
||||||
|
|
||||||
|
if (return_expression) |expression| {
|
||||||
|
try generateExpression(source, expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeAll(source, ";\n");
|
||||||
|
|
||||||
|
has_next_statement = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
.declare_local => |declare_local| {
|
||||||
|
try generateType(source, declare_local.local.type);
|
||||||
|
try coral.bytes.writeFormatted(source, " {identifier} = ", .{ .identifier = declare_local.local.identifier });
|
||||||
|
try generateExpression(source, declare_local.local.expression);
|
||||||
|
try coral.bytes.writeAll(source, ";\n");
|
||||||
|
|
||||||
|
has_next_statement = declare_local.has_next;
|
||||||
|
},
|
||||||
|
|
||||||
|
.mutate_local => |mutate_local| {
|
||||||
|
try coral.bytes.writeFormatted(source, "{identifier} = ", .{ .identifier = mutate_local.local.identifier });
|
||||||
|
try generateExpression(source, mutate_local.expression);
|
||||||
|
try coral.bytes.writeAll(source, ";\n");
|
||||||
|
|
||||||
|
has_next_statement = mutate_local.has_next;
|
||||||
|
},
|
||||||
|
|
||||||
|
.mutate_output => |mutate_output| {
|
||||||
|
try coral.bytes.writeFormatted(source, "{identifier} = ", .{ .identifier = mutate_output.output.identifier });
|
||||||
|
try generateExpression(source, mutate_output.expression);
|
||||||
|
try coral.bytes.writeAll(source, ";\n");
|
||||||
|
|
||||||
|
has_next_statement = mutate_output.has_next;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeAll(source, "}\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateExpression(source: coral.bytes.Writable, expression: *const coral.shaders.Expression) coral.bytes.ReadWriteError!void {
|
||||||
|
switch (expression.*) {
|
||||||
|
.float => |float| {
|
||||||
|
try coral.bytes.writeFormatted(source, "{whole}.{decimal}", float);
|
||||||
|
},
|
||||||
|
|
||||||
|
.int => |int| {
|
||||||
|
try coral.bytes.writeAll(source, int.literal);
|
||||||
|
},
|
||||||
|
|
||||||
|
.group_expression => |group_expression| {
|
||||||
|
try coral.bytes.writeAll(source, "(");
|
||||||
|
try generateExpression(source, group_expression);
|
||||||
|
try coral.bytes.writeAll(source, ")");
|
||||||
|
},
|
||||||
|
|
||||||
|
.add => |add| {
|
||||||
|
try generateExpression(source, add.lhs_expression);
|
||||||
|
try coral.bytes.writeAll(source, " + ");
|
||||||
|
try generateExpression(source, add.rhs_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.subtract => |subtract| {
|
||||||
|
try generateExpression(source, subtract.lhs_expression);
|
||||||
|
try coral.bytes.writeAll(source, " - ");
|
||||||
|
try generateExpression(source, subtract.rhs_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.multiply => |multiply| {
|
||||||
|
try generateExpression(source, multiply.lhs_expression);
|
||||||
|
try coral.bytes.writeAll(source, " * ");
|
||||||
|
try generateExpression(source, multiply.rhs_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.divide => |divide| {
|
||||||
|
try generateExpression(source, divide.lhs_expression);
|
||||||
|
try coral.bytes.writeAll(source, " / ");
|
||||||
|
try generateExpression(source, divide.rhs_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.equal => |equal| {
|
||||||
|
try generateExpression(source, equal.lhs_expression);
|
||||||
|
try coral.bytes.writeAll(source, " == ");
|
||||||
|
try generateExpression(source, equal.rhs_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.greater_than => |greater_than| {
|
||||||
|
try generateExpression(source, greater_than.lhs_expression);
|
||||||
|
try coral.bytes.writeAll(source, " > ");
|
||||||
|
try generateExpression(source, greater_than.rhs_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.greater_equal => |greater_equal| {
|
||||||
|
try generateExpression(source, greater_equal.lhs_expression);
|
||||||
|
try coral.bytes.writeAll(source, " >= ");
|
||||||
|
try generateExpression(source, greater_equal.rhs_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.lesser_than => |lesser_than| {
|
||||||
|
try generateExpression(source, lesser_than.lhs_expression);
|
||||||
|
try coral.bytes.writeAll(source, " < ");
|
||||||
|
try generateExpression(source, lesser_than.rhs_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.lesser_equal => |lesser_equal| {
|
||||||
|
try generateExpression(source, lesser_equal.lhs_expression);
|
||||||
|
try coral.bytes.writeAll(source, " <= ");
|
||||||
|
try generateExpression(source, lesser_equal.rhs_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.negate_expression => |negate_expression| {
|
||||||
|
try coral.bytes.writeAll(source, "-");
|
||||||
|
try generateExpression(source, negate_expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.get_local => |local| {
|
||||||
|
try coral.bytes.writeAll(source, local.identifier);
|
||||||
|
},
|
||||||
|
|
||||||
|
.mutate_local => |mutate_local| {
|
||||||
|
try coral.bytes.writeAll(source, mutate_local.local.identifier);
|
||||||
|
try coral.bytes.writeAll(source, " = ");
|
||||||
|
try generateExpression(source, mutate_local.expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.get_object => |get_object| {
|
||||||
|
try generateExpression(source, get_object.object_expression);
|
||||||
|
try coral.bytes.writeAll(source, ".");
|
||||||
|
try coral.bytes.writeAll(source, get_object.field.identifier);
|
||||||
|
},
|
||||||
|
|
||||||
|
.get_uniform => |get_uniform| {
|
||||||
|
try coral.bytes.writeFormatted(source, "{identifier}.", .{ .identifier = get_uniform.uniform.identifier });
|
||||||
|
try coral.bytes.writeAll(source, get_uniform.field.identifier);
|
||||||
|
},
|
||||||
|
|
||||||
|
.get_texture => |texture| {
|
||||||
|
try coral.bytes.writeAll(source, texture.identifier);
|
||||||
|
},
|
||||||
|
|
||||||
|
.get_parameter => |parameter| {
|
||||||
|
try coral.bytes.writeAll(source, parameter.identifier);
|
||||||
|
},
|
||||||
|
|
||||||
|
.get_output => |output| {
|
||||||
|
try coral.bytes.writeAll(source, output.identifier);
|
||||||
|
},
|
||||||
|
|
||||||
|
.mutate_output => |mutate_output| {
|
||||||
|
try coral.bytes.writeAll(source, mutate_output.output.identifier);
|
||||||
|
try coral.bytes.writeAll(source, " = ");
|
||||||
|
try generateExpression(source, mutate_output.expression);
|
||||||
|
},
|
||||||
|
|
||||||
|
.get_input => |input| {
|
||||||
|
try coral.bytes.writeAll(source, input.identifier);
|
||||||
|
},
|
||||||
|
|
||||||
|
.invoke => |invoke| {
|
||||||
|
try coral.bytes.writeFormatted(source, "{name}(", .{ .name = invoke.function.identifier });
|
||||||
|
|
||||||
|
if (invoke.has_argument) |first_argument| {
|
||||||
|
try generateArguments(source, first_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeAll(source, ")");
|
||||||
|
},
|
||||||
|
|
||||||
|
.convert => |convert| {
|
||||||
|
try generateType(source, convert.target_type);
|
||||||
|
try coral.bytes.writeAll(source, "(");
|
||||||
|
try generateArguments(source, convert.first_argument);
|
||||||
|
try coral.bytes.writeAll(source, ")");
|
||||||
|
},
|
||||||
|
|
||||||
|
.pow => |pow| {
|
||||||
|
try coral.bytes.writeAll(source, "pow(");
|
||||||
|
try generateArguments(source, pow.first_argument);
|
||||||
|
try coral.bytes.writeAll(source, ")");
|
||||||
|
},
|
||||||
|
|
||||||
|
.abs => |abs| {
|
||||||
|
try coral.bytes.writeAll(source, "abs(");
|
||||||
|
try generateArguments(source, abs.first_argument);
|
||||||
|
try coral.bytes.writeAll(source, ")");
|
||||||
|
},
|
||||||
|
|
||||||
|
.sin => |sin| {
|
||||||
|
try coral.bytes.writeAll(source, "sin(");
|
||||||
|
try generateArguments(source, sin.first_argument);
|
||||||
|
try coral.bytes.writeAll(source, ")");
|
||||||
|
},
|
||||||
|
|
||||||
|
.sample => |sample| {
|
||||||
|
try coral.bytes.writeAll(source, "texture(");
|
||||||
|
|
||||||
|
if (sample.has_argument) |first_argument| {
|
||||||
|
try generateArguments(source, first_argument);
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeAll(source, ")");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn generateType(source: coral.bytes.Writable, @"type": *const coral.shaders.Type) coral.bytes.ReadWriteError!void {
|
||||||
|
if (@"type" == coral.shaders.Type.float2) {
|
||||||
|
return try coral.bytes.writeAll(source, "vec2");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@"type" == coral.shaders.Type.float3) {
|
||||||
|
return try coral.bytes.writeAll(source, "vec3");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@"type" == coral.shaders.Type.float4) {
|
||||||
|
return try coral.bytes.writeAll(source, "vec4");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@"type" == coral.shaders.Type.float4x4) {
|
||||||
|
return try coral.bytes.writeAll(source, "mat4");
|
||||||
|
}
|
||||||
|
|
||||||
|
try coral.bytes.writeAll(source, @"type".identifier);
|
||||||
|
}
|
97
src/coral/shaders/spirv.zig
Normal file
97
src/coral/shaders/spirv.zig
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
const coral = @import("../coral.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const AddressingModel = enum(u32) {
|
||||||
|
logical = 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Capability = enum(u32) {
|
||||||
|
matrix = 0,
|
||||||
|
shader = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Document = struct {
|
||||||
|
ids_assigned: u32 = 0,
|
||||||
|
ops: coral.Stack(u32) = .empty,
|
||||||
|
|
||||||
|
fn nextId(self: *Document) u32 {
|
||||||
|
self.ids_assigned += 1;
|
||||||
|
|
||||||
|
return self.ids_assigned;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn op(self: *Document, code: u32) std.mem.Allocator.Error!void {
|
||||||
|
try self.ops.pushGrow(@byteSwap(code));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opCapability(self: *Document, capability: Capability) std.mem.Allocator.Error!void {
|
||||||
|
try self.op(17);
|
||||||
|
try self.op(@intFromEnum(capability));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opMemoryModel(self: *Document, addressing: AddressingModel, memory: MemoryModel) std.mem.Allocator.Error!void {
|
||||||
|
try self.op(14);
|
||||||
|
try self.op(@intFromEnum(addressing));
|
||||||
|
try self.op(@intFromEnum(memory));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn opExtInstImport(self: *Document, extension: []const u8) std.mem.Allocator.Error!u32 {
|
||||||
|
const id = self.nextId();
|
||||||
|
|
||||||
|
try self.op(11);
|
||||||
|
try self.op(id);
|
||||||
|
try self.string(extension);
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn string(self: *Document, data: []const u8) std.mem.Allocator.Error!void {
|
||||||
|
const data_len_with_sentinel = data.len + 1;
|
||||||
|
const word_count = (data_len_with_sentinel + 4) / 4;
|
||||||
|
|
||||||
|
try self.ops.grow(word_count);
|
||||||
|
|
||||||
|
std.debug.assert(self.ops.pushMany(word_count, 0));
|
||||||
|
|
||||||
|
const buffer = std.mem.sliceAsBytes(self.ops.values[(self.ops.values.len - word_count)..]);
|
||||||
|
|
||||||
|
@memcpy(buffer[0..data.len], data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeTo(self: Document, spirv: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
const Header = extern struct {
|
||||||
|
magic_number: u32 = 0x07230203, // SPIR-V magic number in little-endian
|
||||||
|
version: u32, // SPIR-V version (e.g., 0x00010000 for 1.0)
|
||||||
|
generator_magic: u32, // Tool or generator identifier
|
||||||
|
id_upper_bound: u32, // Upper bound for IDs
|
||||||
|
reserved: u32 = 0, // Reserved, always 0
|
||||||
|
};
|
||||||
|
|
||||||
|
try coral.bytes.writeLittle(spirv, Header{
|
||||||
|
.magic_number = 0x07230203,
|
||||||
|
.version = 0x00010000,
|
||||||
|
.generator_magic = 0,
|
||||||
|
.id_upper_bound = self.ids_assigned,
|
||||||
|
});
|
||||||
|
|
||||||
|
return coral.bytes.writeAll(spirv, std.mem.sliceAsBytes(self.ops.values));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MemoryModel = enum(u32) {
|
||||||
|
glsl450 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn generate(spirv: coral.bytes.Writable, root: coral.shaders.Root) (coral.shaders.GenerationError || std.mem.Allocator.Error)!void {
|
||||||
|
// TODO: Finish and testing coverage.
|
||||||
|
var document = Document{};
|
||||||
|
|
||||||
|
try document.opCapability(.shader);
|
||||||
|
|
||||||
|
_ = root;
|
||||||
|
_ = try document.opExtInstImport("GLSL.std.450");
|
||||||
|
|
||||||
|
try document.opMemoryModel(.logical, .glsl450);
|
||||||
|
try document.writeTo(spirv);
|
||||||
|
}
|
494
src/coral/shaders/tokens.zig
Normal file
494
src/coral/shaders/tokens.zig
Normal file
@ -0,0 +1,494 @@
|
|||||||
|
const coral = @import("../coral.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const ExpectationError = error{
|
||||||
|
UnexpectedToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Kind = enum {
|
||||||
|
end,
|
||||||
|
unknown,
|
||||||
|
newline,
|
||||||
|
keyword_function,
|
||||||
|
keyword_return,
|
||||||
|
keyword_sample,
|
||||||
|
keyword_float,
|
||||||
|
keyword_float4x4,
|
||||||
|
keyword_float2,
|
||||||
|
keyword_float3,
|
||||||
|
keyword_float4,
|
||||||
|
keyword_int,
|
||||||
|
keyword_let,
|
||||||
|
keyword_var,
|
||||||
|
keyword_pow,
|
||||||
|
keyword_abs,
|
||||||
|
keyword_sin,
|
||||||
|
identifier,
|
||||||
|
scalar,
|
||||||
|
symbol_plus,
|
||||||
|
symbol_minus,
|
||||||
|
symbol_comma,
|
||||||
|
symbol_arrow,
|
||||||
|
symbol_equals,
|
||||||
|
symbol_period,
|
||||||
|
symbol_asterisk,
|
||||||
|
symbol_paren_left,
|
||||||
|
symbol_paren_right,
|
||||||
|
symbol_brace_left,
|
||||||
|
symbol_brace_right,
|
||||||
|
symbol_double_equals,
|
||||||
|
symbol_lesser_than,
|
||||||
|
symbol_lesser_equals,
|
||||||
|
symbol_greater_than,
|
||||||
|
symbol_greater_equals,
|
||||||
|
symbol_forward_slash,
|
||||||
|
|
||||||
|
pub fn writeFormat(self: Kind, writer: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
try coral.bytes.writeAll(writer, self.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(self: Kind) [:0]const u8 {
|
||||||
|
return switch (self) {
|
||||||
|
.end => "end",
|
||||||
|
.unknown => "unknown",
|
||||||
|
.newline => "newline",
|
||||||
|
.keyword_function => "`function` keyword",
|
||||||
|
.keyword_return => "`return` keyword",
|
||||||
|
.keyword_sample => "`sample` keyword",
|
||||||
|
.keyword_float => "`float` keyword",
|
||||||
|
.keyword_float4x4 => "`float4x4` keyword",
|
||||||
|
.keyword_float2 => "`float2` keyword",
|
||||||
|
.keyword_float3 => "`float3` keyword",
|
||||||
|
.keyword_float4 => "`float4` keyword",
|
||||||
|
.keyword_int => "`int` keyword",
|
||||||
|
.keyword_let => "`let` keyword",
|
||||||
|
.keyword_var => "`var` keyword",
|
||||||
|
.keyword_pow => "`pow` keyword",
|
||||||
|
.keyword_abs => "`abs` keyword",
|
||||||
|
.keyword_sin => "`sin` keyword",
|
||||||
|
.identifier => "identifier",
|
||||||
|
.scalar => "scalar literal",
|
||||||
|
.symbol_plus => "`+`",
|
||||||
|
.symbol_minus => "`-`",
|
||||||
|
.symbol_comma => "`,`",
|
||||||
|
.symbol_arrow => "`->`",
|
||||||
|
.symbol_equals => "`=`",
|
||||||
|
.symbol_period => "`.`",
|
||||||
|
.symbol_asterisk => "`*`",
|
||||||
|
.symbol_paren_left => "`(`",
|
||||||
|
.symbol_paren_right => "`)`",
|
||||||
|
.symbol_brace_left => "`{`",
|
||||||
|
.symbol_brace_right => "`}`",
|
||||||
|
.symbol_double_equals => "`==`",
|
||||||
|
.symbol_lesser_than => "`<`",
|
||||||
|
.symbol_lesser_equals => "`<=`",
|
||||||
|
.symbol_greater_than => "`>`",
|
||||||
|
.symbol_greater_equals => "`>=`",
|
||||||
|
.symbol_forward_slash => "`/`",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Location = struct {
|
||||||
|
line_number: u32 = 1,
|
||||||
|
column_number: u32 = 1,
|
||||||
|
|
||||||
|
pub fn writeFormat(self: Location, writer: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
try coral.bytes.writeFormatted(writer, "({line}, {column})", .{
|
||||||
|
.line = coral.utf8.cDec(self.line_number),
|
||||||
|
.column = coral.utf8.cDec(self.column_number),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Stream = struct {
|
||||||
|
source: []const u8,
|
||||||
|
current: Token,
|
||||||
|
location: Location,
|
||||||
|
depth: u32 = 0,
|
||||||
|
|
||||||
|
pub fn isFinished(self: Stream) bool {
|
||||||
|
return self.source.len == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(source: []const u8) Stream {
|
||||||
|
return .{
|
||||||
|
.source = source,
|
||||||
|
.current = .end,
|
||||||
|
.location = .{},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(self: *Stream) Token {
|
||||||
|
const initial_len = self.source.len;
|
||||||
|
|
||||||
|
self.current = consume(&self.source);
|
||||||
|
|
||||||
|
switch (self.current) {
|
||||||
|
.end => {
|
||||||
|
self.depth = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_brace_left => {
|
||||||
|
self.depth += 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
.symbol_brace_right => {
|
||||||
|
self.depth -= 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
.newline => {
|
||||||
|
self.location.line_number += 1;
|
||||||
|
self.location.column_number = 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
const consumed_len: u32 = @intCast(initial_len - self.source.len);
|
||||||
|
|
||||||
|
self.location.column_number += consumed_len;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.current;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn skip(self: *Stream, skip_kind: Kind) Token {
|
||||||
|
while (true) {
|
||||||
|
if (std.meta.activeTag(self.next()) != skip_kind) {
|
||||||
|
return self.current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Token = union(Kind) {
|
||||||
|
end,
|
||||||
|
unknown: []const u8,
|
||||||
|
newline,
|
||||||
|
keyword_function,
|
||||||
|
keyword_return,
|
||||||
|
keyword_sample,
|
||||||
|
keyword_float,
|
||||||
|
keyword_float4x4,
|
||||||
|
keyword_float2,
|
||||||
|
keyword_float3,
|
||||||
|
keyword_float4,
|
||||||
|
keyword_int,
|
||||||
|
keyword_let,
|
||||||
|
keyword_var,
|
||||||
|
keyword_pow,
|
||||||
|
keyword_abs,
|
||||||
|
keyword_sin,
|
||||||
|
identifier: []const u8,
|
||||||
|
scalar: []const u8,
|
||||||
|
symbol_plus,
|
||||||
|
symbol_minus,
|
||||||
|
symbol_comma,
|
||||||
|
symbol_arrow,
|
||||||
|
symbol_equals,
|
||||||
|
symbol_period,
|
||||||
|
symbol_asterisk,
|
||||||
|
symbol_paren_left,
|
||||||
|
symbol_paren_right,
|
||||||
|
symbol_brace_left,
|
||||||
|
symbol_brace_right,
|
||||||
|
symbol_double_equals,
|
||||||
|
symbol_lesser_than,
|
||||||
|
symbol_lesser_equals,
|
||||||
|
symbol_greater_than,
|
||||||
|
symbol_greater_equals,
|
||||||
|
symbol_forward_slash,
|
||||||
|
|
||||||
|
pub fn ExpectedAnyEnum(comptime expecteds: []const Kind) type {
|
||||||
|
comptime var fields: [expecteds.len]std.builtin.Type.EnumField = undefined;
|
||||||
|
|
||||||
|
inline for (0..expecteds.len) |i| {
|
||||||
|
fields[i] = .{
|
||||||
|
.name = @tagName(expecteds[i]),
|
||||||
|
.value = i,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return @Type(.{
|
||||||
|
.@"enum" = .{
|
||||||
|
.tag_type = @typeInfo(Kind).@"enum".tag_type,
|
||||||
|
.fields = &fields,
|
||||||
|
.decls = &.{},
|
||||||
|
.is_exhaustive = true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expect(self: Token, expected: Kind) ExpectationError!void {
|
||||||
|
if (std.meta.activeTag(self) != expected) {
|
||||||
|
return error.UnexpectedToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expectAny(self: Token, comptime expecteds: []const Kind) ExpectationError!ExpectedAnyEnum(expecteds) {
|
||||||
|
if (std.mem.indexOfScalar(Kind, expecteds, std.meta.activeTag(self))) |index| {
|
||||||
|
return @enumFromInt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return error.UnexpectedToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expectIdentifier(self: Token) ExpectationError![]const u8 {
|
||||||
|
try self.expect(.identifier);
|
||||||
|
|
||||||
|
return self.identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expectScalar(self: Token) ExpectationError![]const u8 {
|
||||||
|
try self.expect(.scalar);
|
||||||
|
|
||||||
|
return self.scalar;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeFormat(self: Token, writer: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
try switch (self) {
|
||||||
|
.unknown => |unknown| coral.bytes.writeFormatted(writer, "`{name}`", .{
|
||||||
|
.name = unknown,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.identifier => |identifier| coral.bytes.writeFormatted(writer, "`{name}`", .{
|
||||||
|
.name = identifier,
|
||||||
|
}),
|
||||||
|
|
||||||
|
.scalar => |literal| coral.bytes.writeFormatted(writer, "`{name}`", .{
|
||||||
|
.name = literal,
|
||||||
|
}),
|
||||||
|
|
||||||
|
else => std.meta.activeTag(self).writeFormat(writer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn consume(source: *[]const u8) Token {
|
||||||
|
var cursor = @as(usize, 0);
|
||||||
|
|
||||||
|
defer {
|
||||||
|
source.* = source.*[cursor..];
|
||||||
|
}
|
||||||
|
|
||||||
|
while (cursor < source.len) {
|
||||||
|
switch (source.*[cursor]) {
|
||||||
|
'#' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
while (cursor < source.len and source.*[cursor] != '\n') {
|
||||||
|
cursor += 1;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
' ', '\t' => {
|
||||||
|
cursor += 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
'\n' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .newline;
|
||||||
|
},
|
||||||
|
|
||||||
|
'0'...'9' => {
|
||||||
|
const begin = cursor;
|
||||||
|
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
while (cursor < source.len) {
|
||||||
|
switch (source.*[cursor]) {
|
||||||
|
'0'...'9' => {
|
||||||
|
cursor += 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
'.' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
while (cursor < source.len) {
|
||||||
|
switch (source.*[cursor]) {
|
||||||
|
'0'...'9' => {
|
||||||
|
cursor += 1;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .scalar = source.*[begin..cursor] };
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .scalar = source.*[begin..cursor] };
|
||||||
|
},
|
||||||
|
|
||||||
|
'A'...'Z', 'a'...'z', '_' => {
|
||||||
|
const begin = cursor;
|
||||||
|
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
while (cursor < source.len) {
|
||||||
|
switch (source.*[cursor]) {
|
||||||
|
'0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1,
|
||||||
|
else => break,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const identifier = source.*[begin..cursor];
|
||||||
|
|
||||||
|
std.debug.assert(identifier.len != 0);
|
||||||
|
|
||||||
|
const identifier_keyword_pairs = [_]struct { [:0]const u8, Kind }{
|
||||||
|
.{ "float", .keyword_float },
|
||||||
|
.{ "float2", .keyword_float2 },
|
||||||
|
.{ "float3", .keyword_float3 },
|
||||||
|
.{ "float4", .keyword_float4 },
|
||||||
|
.{ "float4x4", .keyword_float4x4 },
|
||||||
|
.{ "function", .keyword_function },
|
||||||
|
.{ "let", .keyword_let },
|
||||||
|
.{ "var", .keyword_var },
|
||||||
|
.{ "return", .keyword_return },
|
||||||
|
.{ "var", .keyword_var },
|
||||||
|
.{ "pow", .keyword_pow },
|
||||||
|
.{ "abs", .keyword_abs },
|
||||||
|
.{ "sin", .keyword_sin },
|
||||||
|
.{ "sample", .keyword_sample },
|
||||||
|
};
|
||||||
|
|
||||||
|
inline for (identifier_keyword_pairs) |pair| {
|
||||||
|
const keyword_identifier, const keyword = pair;
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, identifier, keyword_identifier)) {
|
||||||
|
return keyword;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .identifier = identifier };
|
||||||
|
},
|
||||||
|
|
||||||
|
'{' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_brace_left;
|
||||||
|
},
|
||||||
|
|
||||||
|
'}' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_brace_right;
|
||||||
|
},
|
||||||
|
|
||||||
|
',' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_comma;
|
||||||
|
},
|
||||||
|
|
||||||
|
')' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_paren_right;
|
||||||
|
},
|
||||||
|
|
||||||
|
'(' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_paren_left;
|
||||||
|
},
|
||||||
|
|
||||||
|
'/' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_forward_slash;
|
||||||
|
},
|
||||||
|
|
||||||
|
'*' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_asterisk;
|
||||||
|
},
|
||||||
|
|
||||||
|
'-' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
if (cursor < source.len and source.*[cursor] == '>') {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_arrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .symbol_minus;
|
||||||
|
},
|
||||||
|
|
||||||
|
'+' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_plus;
|
||||||
|
},
|
||||||
|
|
||||||
|
'=' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
if (cursor < source.len and source.*[cursor] == '=') {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_double_equals;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .symbol_equals;
|
||||||
|
},
|
||||||
|
|
||||||
|
'<' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
if (cursor < source.len and (source.*[cursor] == '=')) {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_lesser_equals;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .symbol_lesser_than;
|
||||||
|
},
|
||||||
|
|
||||||
|
'>' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
if (cursor < source.len and (source.*[cursor] == '=')) {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_greater_equals;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .symbol_greater_than;
|
||||||
|
},
|
||||||
|
|
||||||
|
'.' => {
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
return .symbol_period;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
const begin = cursor;
|
||||||
|
|
||||||
|
cursor += 1;
|
||||||
|
|
||||||
|
while (cursor < source.len and !std.ascii.isWhitespace(source.*[cursor])) {
|
||||||
|
cursor += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{ .unknown = source.*[begin..cursor] };
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return .end;
|
||||||
|
}
|
241
src/coral/utf8.zig
Normal file
241
src/coral/utf8.zig
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn Dec(comptime Value: type) type {
|
||||||
|
return struct {
|
||||||
|
value: Value,
|
||||||
|
format: *const DecFormat,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn length(self: Self) usize {
|
||||||
|
switch (@typeInfo(Value)) {
|
||||||
|
.ComptimeInt => {
|
||||||
|
return self.length(@as(std.math.IntFittingRange(self.value, self.value), self.value));
|
||||||
|
},
|
||||||
|
|
||||||
|
.Int => |int| {
|
||||||
|
if (self.value == 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const digit_count = switch (int.signedness) {
|
||||||
|
.unsigned => @as(usize, @intCast(std.math.log10((self.value)))),
|
||||||
|
.signed => @as(usize, @intCast(std.math.log10((@abs(self.value))))),
|
||||||
|
};
|
||||||
|
|
||||||
|
const has_prefix = switch (int.signedness) {
|
||||||
|
.unsigned => switch (self.positive_prefix) {
|
||||||
|
.none => false,
|
||||||
|
.plus, .space => true,
|
||||||
|
},
|
||||||
|
|
||||||
|
.signed => self.value < 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const delimiter_space = ((digit_count - 1) / 3) * self.delimiter.len;
|
||||||
|
|
||||||
|
return digit_count + @intFromBool(has_prefix) + delimiter_space;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("Cannot retrieve the decimal string length of " ++ @typeName(Value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn writeFormat(self: Self, writer: coral.bytes.Writable) coral.bytes.ReadWriteError!void {
|
||||||
|
if (self.value == 0) {
|
||||||
|
return coral.bytes.writeAll(writer, switch (self.format.positive_prefix) {
|
||||||
|
.none => "0",
|
||||||
|
.plus => "+0",
|
||||||
|
.space => " 0",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (@typeInfo(Value)) {
|
||||||
|
.int => |int| {
|
||||||
|
const radix = 10;
|
||||||
|
var buffer = [_]u8{0} ** (1 + @max(int.bits, 1));
|
||||||
|
var buffer_start = buffer.len - 1;
|
||||||
|
|
||||||
|
{
|
||||||
|
var decomposable_value = self.value;
|
||||||
|
|
||||||
|
while (decomposable_value != 0) : (buffer_start -= 1) {
|
||||||
|
buffer[buffer_start] = @intCast(@mod(decomposable_value, radix) + '0');
|
||||||
|
decomposable_value = @divTrunc(decomposable_value, radix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int.signedness == .unsigned and self.value < 0) {
|
||||||
|
buffer[buffer_start] = '-';
|
||||||
|
} else {
|
||||||
|
switch (self.format.positive_prefix) {
|
||||||
|
.none => buffer_start += 1,
|
||||||
|
.plus => buffer[buffer_start] = '+',
|
||||||
|
.space => buffer[buffer_start] = ' ',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return coral.bytes.writeAll(writer, buffer[buffer_start..]);
|
||||||
|
},
|
||||||
|
|
||||||
|
.float => |float| {
|
||||||
|
if (self.value < 0) {
|
||||||
|
try coral.bytes.writeAll(writer, "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
const Int = std.meta.Int(.unsigned, float.bits);
|
||||||
|
const integer = @as(Int, @intFromFloat(self.value));
|
||||||
|
|
||||||
|
try self.writeFormat(writer, integer);
|
||||||
|
try coral.bytes.writeAll(writer, ".");
|
||||||
|
try self.writeFormat(writer, @as(Int, @intFromFloat((self.value - @as(Value, @floatFromInt(integer))) * 100)));
|
||||||
|
},
|
||||||
|
|
||||||
|
.comptime_int => {
|
||||||
|
const repacked = Dec(std.math.IntFittingRange(self.value, self.value)){
|
||||||
|
.value = self.value,
|
||||||
|
.format = self.format,
|
||||||
|
};
|
||||||
|
|
||||||
|
try repacked.writeFormat(writer);
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be formatted to a decimal string"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DecFormat = struct {
|
||||||
|
delimiter: []const u8,
|
||||||
|
positive_prefix: enum { none, plus, space },
|
||||||
|
|
||||||
|
pub fn parse(self: DecFormat, utf8: []const u8, comptime Value: type) ?Value {
|
||||||
|
if (utf8.len == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (@typeInfo(Value)) {
|
||||||
|
.Int => |int| {
|
||||||
|
const has_sign = switch (utf8[0]) {
|
||||||
|
'-', '+', ' ' => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = @as(Value, 0);
|
||||||
|
|
||||||
|
for (@intFromBool(has_sign)..utf8.len) |index| {
|
||||||
|
const radix = 10;
|
||||||
|
const code = utf8[index];
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||||||
|
const offset_code = coral.scalars.sub(code, '0') orelse {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
result = coral.scalars.mul(result, radix) orelse {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
result = coral.scalars.add(result, offset_code) orelse {
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
else => {
|
||||||
|
if (self.delimiter.len == 0 or !std.mem.eql(u8, self.delimiter, utf8[index..])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (int.signedness) {
|
||||||
|
.signed => {
|
||||||
|
return result * @as(Value, if (has_sign and utf8[0] == '-') -1 else 1);
|
||||||
|
},
|
||||||
|
|
||||||
|
.unsigned => {
|
||||||
|
if (has_sign and utf8[0] == '-') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
.Float => {
|
||||||
|
const has_sign = switch (utf8[0]) {
|
||||||
|
'-', '+', ' ' => true,
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
// "-"
|
||||||
|
if (has_sign and utf8.len == 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sign_offset = @intFromBool(has_sign);
|
||||||
|
var has_decimal = utf8[sign_offset] == '.';
|
||||||
|
|
||||||
|
// "-."
|
||||||
|
if (has_decimal and (utf8.len == 2)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = @as(Value, 0);
|
||||||
|
var factor = @as(Value, if (has_sign and utf8[0] == '-') -1 else 1);
|
||||||
|
|
||||||
|
for (utf8[sign_offset + @intFromBool(has_decimal) .. utf8.len]) |code| {
|
||||||
|
switch (code) {
|
||||||
|
'.' => {
|
||||||
|
if (has_decimal) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
has_decimal = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||||||
|
if (has_decimal) {
|
||||||
|
factor /= 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = ((result * 10.0) + @as(Value, @floatFromInt(code - '0')));
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return null,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result * factor;
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be parsed from a decimal string"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "decimal parsing" {
|
||||||
|
const format = DecFormat{
|
||||||
|
.delimiter = "",
|
||||||
|
.positive_prefix = .none,
|
||||||
|
};
|
||||||
|
|
||||||
|
try std.testing.expectEqual(format.parse("69", i64), 69);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cDec(value: anytype) Dec(@TypeOf(value)) {
|
||||||
|
return .{
|
||||||
|
.value = value,
|
||||||
|
|
||||||
|
.format = &.{
|
||||||
|
.delimiter = "",
|
||||||
|
.positive_prefix = .none,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
20
src/demos/graphics.eff
Normal file
20
src/demos/graphics.eff
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
function crt(coords float2, bend float) -> float2 {
|
||||||
|
let symmetrical_coords = (coords - 0.5) * 2.0
|
||||||
|
|
||||||
|
let deformed_coords = symmetrical_coords * (symmetrical_coords + float2(
|
||||||
|
pow((abs(coords.y) / bend), 2.0),
|
||||||
|
pow((abs(coords.x) / bend), 2.0)
|
||||||
|
))
|
||||||
|
|
||||||
|
return (deformed_coords / 2.0) + 0.5
|
||||||
|
}
|
||||||
|
|
||||||
|
function frag(color float4, uv float2) -> float4 {
|
||||||
|
let crt_coords = crt(uv, 4.8)
|
||||||
|
let screenspace = crt_coords * float2(effect.screen_width, effect.screen_height)
|
||||||
|
let color = sample(albedo, crt_coords).rgb
|
||||||
|
let scanline = float4((color - sin((screenspace.y + (effect.time * 29.0))) * 0.02), 1.0)
|
||||||
|
|
||||||
|
return scanline
|
||||||
|
}
|
9
src/demos/graphics.zig
Normal file
9
src/demos/graphics.zig
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
const ona = @import("ona");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn main() void {
|
||||||
|
ona.realtime_app
|
||||||
|
.with(.initModule(ona.gfx))
|
||||||
|
.run();
|
||||||
|
}
|
3
src/ext/ext.zig
Normal file
3
src/ext/ext.zig
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub usingnamespace @cImport({
|
||||||
|
@cInclude("SDL3/SDL.h");
|
||||||
|
});
|
@ -1,484 +0,0 @@
|
|||||||
const gfx = @import("./gfx.zig");
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
|
||||||
|
|
||||||
const spirv = @import("./spirv.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
effects: EffectPool,
|
|
||||||
textures: TexturePool,
|
|
||||||
|
|
||||||
pub const Effect = struct {
|
|
||||||
shader: sokol.gfx.Shader,
|
|
||||||
pipeline: sokol.gfx.Pipeline,
|
|
||||||
properties: []u8,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Effect) void {
|
|
||||||
ona.heap.allocator.free(self.properties);
|
|
||||||
sokol.gfx.destroyPipeline(self.pipeline);
|
|
||||||
sokol.gfx.destroyShader(self.shader);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(desc: gfx.Effect.Desc) spirv.Error!Effect {
|
|
||||||
var spirv_arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
|
|
||||||
|
|
||||||
defer {
|
|
||||||
spirv_arena.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const spirv_program = try spirv.analyze(&spirv_arena, .{
|
|
||||||
.target = try switch (sokol.gfx.queryBackend()) {
|
|
||||||
.GLCORE => spirv.Target.glsl,
|
|
||||||
else => error.InvalidSPIRV,
|
|
||||||
},
|
|
||||||
|
|
||||||
.vertex_source = .{
|
|
||||||
.ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.vert.spv")),
|
|
||||||
},
|
|
||||||
|
|
||||||
.fragment_source = .{
|
|
||||||
.ops = desc.fragment_spirv_ops,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const shader = sokol.gfx.makeShader(shader_desc: {
|
|
||||||
const shader_desc = sokol.gfx.ShaderDesc{
|
|
||||||
.vs = stage_desc(spirv_program.vertex_stage),
|
|
||||||
.fs = stage_desc(spirv_program.fragment_stage),
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Vertex attributes, for some reason they aren't needed?
|
|
||||||
|
|
||||||
break: shader_desc shader_desc;
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: Review blending rules.
|
|
||||||
const pipeline = sokol.gfx.makePipeline(pipeline_desc: {
|
|
||||||
var pipeline_desc = sokol.gfx.PipelineDesc{
|
|
||||||
.label = "Effect pipeline",
|
|
||||||
.layout = vertex_layout_state,
|
|
||||||
.shader = shader,
|
|
||||||
.index_type = .UINT16,
|
|
||||||
.blend_color = .{.r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0},
|
|
||||||
};
|
|
||||||
|
|
||||||
pipeline_desc.colors[0] = .{
|
|
||||||
.write_mask = .RGBA,
|
|
||||||
|
|
||||||
.blend = .{
|
|
||||||
.enabled = true,
|
|
||||||
.src_factor_rgb = .SRC_ALPHA,
|
|
||||||
.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
break: pipeline_desc pipeline_desc;
|
|
||||||
});
|
|
||||||
|
|
||||||
const properties = try ona.heap.allocator.alloc(
|
|
||||||
u8,
|
|
||||||
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
ona.heap.allocator.free(properties);
|
|
||||||
sokol.gfx.destroyPipeline(pipeline);
|
|
||||||
sokol.gfx.destroyShader(shader);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.shader = shader,
|
|
||||||
.pipeline = pipeline,
|
|
||||||
.properties = properties,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stage_desc(spirv_stage: spirv.Stage) sokol.gfx.ShaderStageDesc {
|
|
||||||
var stage = sokol.gfx.ShaderStageDesc{
|
|
||||||
.entry = spirv_stage.entry_point,
|
|
||||||
.source = spirv_stage.source,
|
|
||||||
};
|
|
||||||
|
|
||||||
for (0 .. spirv.Stage.max_uniform_blocks) |slot| {
|
|
||||||
const uniform_block = &(spirv_stage.has_uniform_blocks[slot] orelse {
|
|
||||||
continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
const stage_uniform_block = &stage.uniform_blocks[slot];
|
|
||||||
|
|
||||||
stage_uniform_block.layout = switch (uniform_block.layout) {
|
|
||||||
.std140 => .STD140,
|
|
||||||
};
|
|
||||||
|
|
||||||
stage_uniform_block.size = uniform_block.size();
|
|
||||||
|
|
||||||
for (stage_uniform_block.uniforms[0 .. uniform_block.uniforms.len], uniform_block.uniforms) |*stage_uniform, uniform| {
|
|
||||||
stage_uniform.* = .{
|
|
||||||
.type = switch (uniform.type) {
|
|
||||||
.float => .FLOAT,
|
|
||||||
.float2 => .FLOAT2,
|
|
||||||
.float3 => .FLOAT3,
|
|
||||||
.float4 => .FLOAT4,
|
|
||||||
.integer => .INT,
|
|
||||||
.integer2 => .INT2,
|
|
||||||
.integer3 => .INT3,
|
|
||||||
.integer4 => .INT4,
|
|
||||||
.matrix4 => .MAT4,
|
|
||||||
},
|
|
||||||
|
|
||||||
.name = uniform.name,
|
|
||||||
.array_count = uniform.len,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. spirv.Stage.max_images) |slot| {
|
|
||||||
const image = &(spirv_stage.has_images[slot] orelse {
|
|
||||||
continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
stage.images[slot] = .{
|
|
||||||
.multisampled = image.is_multisampled,
|
|
||||||
.image_type = ._2D,
|
|
||||||
.sample_type = .FLOAT,
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. spirv.Stage.max_samplers) |slot| {
|
|
||||||
const sampler = &(spirv_stage.has_samplers[slot] orelse {
|
|
||||||
continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
stage.samplers[slot] = .{
|
|
||||||
.sampler_type = switch (sampler.*) {
|
|
||||||
.filtering => .FILTERING,
|
|
||||||
.non_filtering => .NONFILTERING,
|
|
||||||
.comparison => .COMPARISON,
|
|
||||||
},
|
|
||||||
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. spirv.Stage.max_image_sampler_pairs) |slot| {
|
|
||||||
const image_sampler_pair = &(spirv_stage.has_image_sampler_pairs[slot] orelse {
|
|
||||||
continue;
|
|
||||||
});
|
|
||||||
|
|
||||||
stage.image_sampler_pairs[slot] = .{
|
|
||||||
.glsl_name = image_sampler_pair.name,
|
|
||||||
.image_slot = @intCast(image_sampler_pair.image_slot),
|
|
||||||
.sampler_slot = @intCast(image_sampler_pair.sampler_slot),
|
|
||||||
.used = true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return stage;
|
|
||||||
}
|
|
||||||
|
|
||||||
const vertex_layout_state = sokol.gfx.VertexLayoutState{
|
|
||||||
.attrs = get: {
|
|
||||||
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
|
|
||||||
|
|
||||||
attrs[0] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[1] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[2] = .{
|
|
||||||
.format = .FLOAT,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[3] = .{
|
|
||||||
.format = .FLOAT,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[4] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[5] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[6] = .{
|
|
||||||
.format = .FLOAT2,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[7] = .{
|
|
||||||
.format = .FLOAT4,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
attrs[8] = .{
|
|
||||||
.format = .FLOAT4,
|
|
||||||
.buffer_index = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
break: get attrs;
|
|
||||||
},
|
|
||||||
|
|
||||||
.buffers = get: {
|
|
||||||
var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
|
|
||||||
|
|
||||||
vertex_buffer_layouts[1].step_func = .PER_INSTANCE;
|
|
||||||
|
|
||||||
break: get vertex_buffer_layouts;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const EffectPool = ona.Pool(Effect);
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub const Texture = struct {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
access: Access,
|
|
||||||
|
|
||||||
pub const Access = union (enum) {
|
|
||||||
render: RenderAccess,
|
|
||||||
static: StaticAccess,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RenderAccess = struct {
|
|
||||||
color_image: sokol.gfx.Image,
|
|
||||||
depth_image: sokol.gfx.Image,
|
|
||||||
attachments: sokol.gfx.Attachments,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const StaticAccess = struct {
|
|
||||||
image: sokol.gfx.Image,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn deinit(self: *Texture) void {
|
|
||||||
switch (self.access) {
|
|
||||||
.render => |render| {
|
|
||||||
sokol.gfx.destroyImage(render.color_image);
|
|
||||||
sokol.gfx.destroyImage(render.depth_image);
|
|
||||||
sokol.gfx.destroyAttachments(render.attachments);
|
|
||||||
},
|
|
||||||
|
|
||||||
.static => |static| {
|
|
||||||
sokol.gfx.destroyImage(static.image);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture {
|
|
||||||
const pixel_format = switch (desc.format) {
|
|
||||||
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
|
||||||
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (desc.access) {
|
|
||||||
.render => |render| {
|
|
||||||
const color_image = sokol.gfx.makeImage(.{
|
|
||||||
.pixel_format = pixel_format,
|
|
||||||
.width = render.width,
|
|
||||||
.height = render.height,
|
|
||||||
.render_target = true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const depth_image = sokol.gfx.makeImage(.{
|
|
||||||
.width = render.width,
|
|
||||||
.height = render.height,
|
|
||||||
.render_target = true,
|
|
||||||
.pixel_format = .DEPTH_STENCIL,
|
|
||||||
});
|
|
||||||
|
|
||||||
const attachments = sokol.gfx.makeAttachments(attachments_desc: {
|
|
||||||
var attachments_desc = sokol.gfx.AttachmentsDesc{
|
|
||||||
.depth_stencil = .{
|
|
||||||
.image = depth_image,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
attachments_desc.colors[0] = .{
|
|
||||||
.image = color_image,
|
|
||||||
};
|
|
||||||
|
|
||||||
break: attachments_desc attachments_desc;
|
|
||||||
});
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.width = render.width,
|
|
||||||
.height = render.height,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.render = .{
|
|
||||||
.attachments = attachments,
|
|
||||||
.color_image = color_image,
|
|
||||||
.depth_image = depth_image,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
.static => |static| {
|
|
||||||
const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse {
|
|
||||||
return error.OutOfMemory;
|
|
||||||
};
|
|
||||||
|
|
||||||
const image = sokol.gfx.makeImage(image_desc: {
|
|
||||||
var image_desc = sokol.gfx.ImageDesc{
|
|
||||||
.height = height,
|
|
||||||
.pixel_format = pixel_format,
|
|
||||||
.width = static.width,
|
|
||||||
};
|
|
||||||
|
|
||||||
image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data);
|
|
||||||
|
|
||||||
break: image_desc image_desc;
|
|
||||||
});
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
sokol.gfx.destroyImage(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.width = static.width,
|
|
||||||
.height = height,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.static = .{
|
|
||||||
.image = image,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const TexturePool = ona.Pool(Texture);
|
|
||||||
|
|
||||||
pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect {
|
|
||||||
var effect = try Effect.init(desc);
|
|
||||||
|
|
||||||
errdefer effect.deinit();
|
|
||||||
|
|
||||||
return @enumFromInt(try self.effects.insert(effect));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_texture(self: *Self, desc: gfx.Texture.Desc) !gfx.Texture {
|
|
||||||
var texture = try Texture.init(desc);
|
|
||||||
|
|
||||||
errdefer texture.deinit();
|
|
||||||
|
|
||||||
return @enumFromInt(try self.textures.insert(texture));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
var textures = self.textures.values();
|
|
||||||
|
|
||||||
while (textures.next()) |texture| {
|
|
||||||
texture.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.textures.deinit();
|
|
||||||
|
|
||||||
var effects = self.effects.values();
|
|
||||||
|
|
||||||
while (effects.next()) |effect| {
|
|
||||||
effect.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.effects.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
|
|
||||||
switch (handle) {
|
|
||||||
.default => {},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
var effect = self.effects.remove(@intFromEnum(handle)) orelse {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
effect.deinit();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
|
|
||||||
switch (handle) {
|
|
||||||
.default => {},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
var texture = self.textures.remove(@intFromEnum(handle)) orelse {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
texture.deinit();
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect {
|
|
||||||
return self.effects.get(@intFromEnum(handle));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_texture(self: *Self, handle: gfx.Texture) ?*Texture {
|
|
||||||
return self.textures.get(@intFromEnum(handle));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init() !Self {
|
|
||||||
var pools = Self{
|
|
||||||
.effects = EffectPool.init(ona.heap.allocator),
|
|
||||||
.textures = TexturePool.init(ona.heap.allocator),
|
|
||||||
};
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
pools.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
const assert = struct {
|
|
||||||
fn is_default_handle(actual: anytype) void {
|
|
||||||
std.debug.assert(actual == .default);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var descs = gfx.Descs.init(ona.heap.allocator);
|
|
||||||
|
|
||||||
defer {
|
|
||||||
descs.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.is_default_handle(try pools.create_effect(.{
|
|
||||||
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.frag.spv")),
|
|
||||||
}));
|
|
||||||
|
|
||||||
assert.is_default_handle(try pools.create_texture(try descs.solid_texture(.{
|
|
||||||
.width = 8,
|
|
||||||
.height = 8,
|
|
||||||
.color = gfx.colors.white,
|
|
||||||
})));
|
|
||||||
|
|
||||||
return pools;
|
|
||||||
}
|
|
688
src/gfx/gfx.zig
688
src/gfx/gfx.zig
@ -1,688 +0,0 @@
|
|||||||
const hid = @import("hid");
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
const ext = @cImport({
|
|
||||||
@cInclude("SDL2/SDL.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
const rendering = @import("./rendering.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Assets = struct {
|
|
||||||
window: *ext.SDL_Window,
|
|
||||||
frame_rendered: std.Thread.ResetEvent = .{},
|
|
||||||
pending_work: WorkQueue = .{},
|
|
||||||
has_worker_thread: ?std.Thread = null,
|
|
||||||
|
|
||||||
pub const LoadError = std.mem.Allocator.Error;
|
|
||||||
|
|
||||||
pub const LoadFileError = LoadError || ona.files.ReadAllError || error {
|
|
||||||
FormatUnsupported,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const TextureFormat = struct {
|
|
||||||
extension: []const u8,
|
|
||||||
load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const WorkQueue = ona.asyncio.BlockingQueue(1024, Work);
|
|
||||||
|
|
||||||
fn deinit(self: *Assets) void {
|
|
||||||
self.pending_work.enqueue(.shutdown);
|
|
||||||
|
|
||||||
if (self.has_worker_thread) |worker_thread| {
|
|
||||||
worker_thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(width: u16, height: u16) !Assets {
|
|
||||||
const window = create: {
|
|
||||||
const position = ext.SDL_WINDOWPOS_CENTERED;
|
|
||||||
const flags = ext.SDL_WINDOW_OPENGL;
|
|
||||||
|
|
||||||
break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
|
|
||||||
return error.Unsupported;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
ext.SDL_DestroyWindow(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.window = window,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_effect_file(self: *Assets, storage: ona.files.Storage, path: []const u8) LoadFileError!Effect {
|
|
||||||
if (!std.mem.endsWith(u8, path, ".spv")) {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fragment_file_stat = try storage.stat(path);
|
|
||||||
const fragment_spirv_ops = try ona.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
|
|
||||||
|
|
||||||
defer {
|
|
||||||
ona.heap.allocator.free(fragment_spirv_ops);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{});
|
|
||||||
|
|
||||||
std.debug.assert(bytes_read.len == fragment_file_stat.size);
|
|
||||||
|
|
||||||
var loaded = ona.asyncio.Future(std.mem.Allocator.Error!Effect){};
|
|
||||||
|
|
||||||
self.pending_work.enqueue(.{
|
|
||||||
.load_effect = .{
|
|
||||||
.desc = .{
|
|
||||||
.fragment_spirv_ops = fragment_spirv_ops,
|
|
||||||
},
|
|
||||||
|
|
||||||
.loaded = &loaded,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return loaded.get().*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_texture(self: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture {
|
|
||||||
var loaded = ona.asyncio.Future(std.mem.Allocator.Error!Texture){};
|
|
||||||
|
|
||||||
self.pending_work.enqueue(.{
|
|
||||||
.load_texture = .{
|
|
||||||
.desc = desc,
|
|
||||||
.loaded = &loaded,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return loaded.get().*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const thread_restriction = .main;
|
|
||||||
|
|
||||||
pub fn unload_effect(self: *Assets, handle: Effect) void {
|
|
||||||
self.pending_work.enqueue(.{.unload_effect = handle});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unload_texture(self: *Assets, handle: Texture) void {
|
|
||||||
self.pending_work.enqueue(.{.unload_texture = handle});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Color = @Vector(4, f32);
|
|
||||||
|
|
||||||
pub const Command = union (enum) {
|
|
||||||
draw_font: DrawFont,
|
|
||||||
draw_texture: DrawTexture,
|
|
||||||
set_effect: SetEffect,
|
|
||||||
set_scissor_rect: ?Rect,
|
|
||||||
set_target: SetTarget,
|
|
||||||
|
|
||||||
pub const DrawFont = struct {
|
|
||||||
position: Vector2 = @splat(0),
|
|
||||||
size: Vector2,
|
|
||||||
text: []const u8,
|
|
||||||
font: Font,
|
|
||||||
tint: Color,
|
|
||||||
depth: f32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DrawTexture = struct {
|
|
||||||
anchor: Vector2 = @splat(0),
|
|
||||||
size: ?Vector2 = null,
|
|
||||||
source: Rect = .{.left = 0, .top = 0, .right = 1, .bottom = 1},
|
|
||||||
position: Vector2 = @splat(0),
|
|
||||||
tint: Color = colors.white,
|
|
||||||
texture: Texture = .default,
|
|
||||||
rotation: f32 = 0,
|
|
||||||
depth: f32 = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SetEffect = struct {
|
|
||||||
effect: Effect,
|
|
||||||
properties: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SetTarget = struct {
|
|
||||||
texture: ?Texture = null,
|
|
||||||
clear_color: ?Color,
|
|
||||||
clear_depth: ?f32,
|
|
||||||
clear_stencil: ?u8,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Commands = struct {
|
|
||||||
pending: *List,
|
|
||||||
|
|
||||||
pub const List = struct {
|
|
||||||
arena: std.heap.ArenaAllocator,
|
|
||||||
stack: ona.stack.Sequential(Command),
|
|
||||||
|
|
||||||
fn clear(self: *List) void {
|
|
||||||
self.stack.clear();
|
|
||||||
|
|
||||||
if (!self.arena.reset(.retain_capacity)) {
|
|
||||||
std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: *List) void {
|
|
||||||
self.arena.deinit();
|
|
||||||
self.stack.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(allocator: std.mem.Allocator) List {
|
|
||||||
return .{
|
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
|
||||||
.stack = .{.allocator = allocator},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Param = struct {
|
|
||||||
swap_lists: [2]List,
|
|
||||||
swap_state: u1 = 0,
|
|
||||||
|
|
||||||
fn deinit(self: *Param) void {
|
|
||||||
for (&self.swap_lists) |*list| {
|
|
||||||
list.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pending_list(self: *Param) *List {
|
|
||||||
return &self.swap_lists[self.swap_state];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rotate(self: *Param) void {
|
|
||||||
const swapped_state = self.swap_state ^ 1;
|
|
||||||
|
|
||||||
self.swap_lists[swapped_state].clear();
|
|
||||||
|
|
||||||
self.swap_state = swapped_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submitted_commands(self: Param) []const Command {
|
|
||||||
return self.swap_lists[self.swap_state ^ 1].stack.values;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param {
|
|
||||||
return .{
|
|
||||||
.swap_lists = .{
|
|
||||||
List.init(ona.heap.allocator),
|
|
||||||
List.init(ona.heap.allocator),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(param: *Param) Self {
|
|
||||||
return .{
|
|
||||||
.pending = param.pending_list(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_font(self: Self, command: Command.DrawFont) std.mem.Allocator.Error!void {
|
|
||||||
try self.pending.stack.push_grow(.{
|
|
||||||
.draw_font = .{
|
|
||||||
.text = try self.pending.arena.allocator().dupe(u8, command.text),
|
|
||||||
.position = command.position,
|
|
||||||
.size = command.size,
|
|
||||||
.font = command.font,
|
|
||||||
.tint = command.tint,
|
|
||||||
.depth = command.depth,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_texture(self: Self, command: Command.DrawTexture) std.mem.Allocator.Error!void {
|
|
||||||
try self.pending.stack.push_grow(.{.draw_texture = command});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_effect(self: Self, command: Command.SetEffect) std.mem.Allocator.Error!void {
|
|
||||||
try self.pending.stack.push_grow(.{
|
|
||||||
.set_effect = .{
|
|
||||||
.properties = try self.pending.arena.allocator().dupe(u8, command.properties),
|
|
||||||
.effect = command.effect,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_scissor_rect(self: Self, has_rect: Rect) std.mem.Allocator.Error!void {
|
|
||||||
try self.pending.stack.push_grow(.{.set_scissor_rect = has_rect});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_target(self: Self, command: Command.SetTarget) std.mem.Allocator.Error!void {
|
|
||||||
try self.pending.stack.push_grow(.{.set_target = command});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unbind(param: *Param, _: ona.World.UnbindContext) void {
|
|
||||||
param.deinit();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Descs = struct {
|
|
||||||
arena: std.heap.ArenaAllocator,
|
|
||||||
|
|
||||||
pub const CheckerTextureDesc = struct {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
colors: [2]Color,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const BmpTextureDesc = struct {
|
|
||||||
storage: ona.files.Storage,
|
|
||||||
path: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SolidTextureDesc = struct {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
color: Color,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn bmp_texture(self: *Descs, desc: BmpTextureDesc) (error { OutOfMemory, FormatUnsupported })!Texture.Desc {
|
|
||||||
const header = try desc.storage.read_little(desc.path, 0, extern struct {
|
|
||||||
type: [2]u8 align (1),
|
|
||||||
file_size: u32 align (1),
|
|
||||||
reserved: [2]u16 align (1),
|
|
||||||
image_offset: u32 align (1),
|
|
||||||
header_size: u32 align (1),
|
|
||||||
pixel_width: i32 align (1),
|
|
||||||
pixel_height: i32 align (1),
|
|
||||||
color_planes: u16 align (1),
|
|
||||||
bits_per_pixel: u16 align (1),
|
|
||||||
compression_method: u32 align (1),
|
|
||||||
image_size: u32 align(1),
|
|
||||||
pixels_per_meter_x: i32 align (1),
|
|
||||||
pixels_per_meter_y: i32 align (1),
|
|
||||||
palette_colors_used: u32 align (1),
|
|
||||||
important_colors_used: u32 align (1),
|
|
||||||
}) orelse {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!std.mem.eql(u8, &header.type, "BM")) {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
};
|
|
||||||
|
|
||||||
const pixels = try self.arena.allocator().alloc(u8, header.image_size);
|
|
||||||
const bytes_per_pixel = header.bits_per_pixel / @bitSizeOf(u8);
|
|
||||||
const alignment = 4;
|
|
||||||
const byte_stride = pixel_width * bytes_per_pixel;
|
|
||||||
const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
|
|
||||||
const byte_padding = ona.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
|
|
||||||
var buffer_offset: usize = 0;
|
|
||||||
var file_offset = @as(usize, header.image_offset);
|
|
||||||
|
|
||||||
switch (header.bits_per_pixel) {
|
|
||||||
32 => {
|
|
||||||
while (buffer_offset < pixels.len) {
|
|
||||||
const line = pixels[buffer_offset .. buffer_offset + byte_stride];
|
|
||||||
|
|
||||||
if (try desc.storage.read(desc.path, line, file_offset) != byte_stride) {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. pixel_width) |i| {
|
|
||||||
const line_offset = i * 4;
|
|
||||||
const pixel = line[line_offset .. line_offset + 4];
|
|
||||||
|
|
||||||
std.mem.swap(u8, &pixel[0], &pixel[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
file_offset += line.len + byte_padding;
|
|
||||||
buffer_offset += padded_byte_stride;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return error.FormatUnsupported,
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.format = .rgba8,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.static = .{
|
|
||||||
.width = pixel_width,
|
|
||||||
.data = pixels,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn checker_texture(self: *Descs, desc: CheckerTextureDesc) std.mem.Allocator.Error!Texture.Desc {
|
|
||||||
const color_data = try self.arena.allocator().alloc(u32, desc.width * desc.height);
|
|
||||||
|
|
||||||
for (color_data, 0 .. color_data.len) |*color, i| {
|
|
||||||
const row = i / desc.width;
|
|
||||||
const col = i % desc.width;
|
|
||||||
|
|
||||||
color.* = colors.compress(desc.colors[(col + (row % 2)) % desc.colors.len]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.access = .{
|
|
||||||
.static = .{
|
|
||||||
.data = std.mem.sliceAsBytes(color_data),
|
|
||||||
.width = desc.width,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.format = .rgba8,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Descs) void {
|
|
||||||
self.arena.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) Descs {
|
|
||||||
return .{
|
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn solid_texture(self: *Descs, desc: SolidTextureDesc) std.mem.Allocator.Error!Texture.Desc {
|
|
||||||
const color_data = try self.arena.allocator().alloc(u32, desc.width * desc.height);
|
|
||||||
|
|
||||||
@memset(color_data, colors.compress(desc.color));
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.access = .{
|
|
||||||
.static = .{
|
|
||||||
.data = std.mem.sliceAsBytes(color_data),
|
|
||||||
.width = desc.width,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.format = .rgba8,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Display = struct {
|
|
||||||
width: u16 = 1280,
|
|
||||||
height: u16 = 720,
|
|
||||||
clear_color: Color = colors.black,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Effect = enum (u32) {
|
|
||||||
default,
|
|
||||||
_,
|
|
||||||
|
|
||||||
pub const Desc = struct {
|
|
||||||
fragment_spirv_ops: []const u32,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Font = enum (u32) {
|
|
||||||
default,
|
|
||||||
_,
|
|
||||||
|
|
||||||
pub const Desc = struct {
|
|
||||||
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Rect = extern struct {
|
|
||||||
left: f32,
|
|
||||||
top: f32,
|
|
||||||
right: f32,
|
|
||||||
bottom: f32,
|
|
||||||
|
|
||||||
pub fn area(self: Rect) f32 {
|
|
||||||
const width, const height = self.size();
|
|
||||||
|
|
||||||
return width * height;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn size(self: Rect) Vector2 {
|
|
||||||
return .{self.right - self.left, self.bottom - self.top};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const zero = Rect{
|
|
||||||
.left = 0,
|
|
||||||
.top = 0,
|
|
||||||
.right = 0,
|
|
||||||
.bottom = 0,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Texture = enum (u32) {
|
|
||||||
default,
|
|
||||||
_,
|
|
||||||
|
|
||||||
pub const Desc = struct {
|
|
||||||
format: Format,
|
|
||||||
access: Access,
|
|
||||||
|
|
||||||
pub const Access = union (enum) {
|
|
||||||
static: StaticAccess,
|
|
||||||
render: RenderAccess,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const StaticAccess = struct {
|
|
||||||
width: u16,
|
|
||||||
data: []const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RenderAccess = struct {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Format = enum {
|
|
||||||
rgba8,
|
|
||||||
bgra8,
|
|
||||||
|
|
||||||
pub fn byte_size(self: Format) usize {
|
|
||||||
return switch (self) {
|
|
||||||
.rgba8, .bgra8 => 4,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Vector2 = @Vector(2, f32);
|
|
||||||
|
|
||||||
const Work = union (enum) {
|
|
||||||
load_effect: LoadEffect,
|
|
||||||
load_texture: LoadTexture,
|
|
||||||
render_frame: RenderFrame,
|
|
||||||
shutdown,
|
|
||||||
unload_effect: UnloadEffect,
|
|
||||||
unload_texture: UnloadTexture,
|
|
||||||
|
|
||||||
const LoadEffect = struct {
|
|
||||||
desc: Effect.Desc,
|
|
||||||
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect),
|
|
||||||
};
|
|
||||||
|
|
||||||
const LoadTexture = struct {
|
|
||||||
desc: Texture.Desc,
|
|
||||||
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture),
|
|
||||||
};
|
|
||||||
|
|
||||||
const RenderFrame = struct {
|
|
||||||
clear_color: Color,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
finished: *std.Thread.ResetEvent,
|
|
||||||
has_commands: ?*ona.Params(Commands).Node,
|
|
||||||
};
|
|
||||||
|
|
||||||
const UnloadEffect = struct {
|
|
||||||
handle: Effect,
|
|
||||||
};
|
|
||||||
|
|
||||||
const UnloadTexture = struct {
|
|
||||||
handle: Texture,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const colors = struct {
|
|
||||||
pub const black = greyscale(0);
|
|
||||||
|
|
||||||
pub fn compress(color: Color) u32 {
|
|
||||||
const range: Color = @splat(255);
|
|
||||||
const r, const g, const b, const a = color * range;
|
|
||||||
|
|
||||||
return @bitCast([_]u8{@intFromFloat(r), @intFromFloat(g), @intFromFloat(b), @intFromFloat(a)});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const grey = greyscale(0.5);
|
|
||||||
|
|
||||||
pub fn greyscale(v: f32) Color {
|
|
||||||
return .{v, v, v, 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const purple = rgb(0.5, 0, 0.5);
|
|
||||||
|
|
||||||
pub const red = rgb(1, 0, 0);
|
|
||||||
|
|
||||||
pub fn rgb(r: f32, g: f32, b: f32) Color {
|
|
||||||
return .{r, g, b, 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const white = greyscale(1);
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn poll(loop: ona.Write(ona.Loop), events: ona.Send(hid.Event)) !void {
|
|
||||||
var event = @as(ext.SDL_Event, undefined);
|
|
||||||
|
|
||||||
while (ext.SDL_PollEvent(&event) != 0) {
|
|
||||||
switch (event.type) {
|
|
||||||
ext.SDL_QUIT => {
|
|
||||||
loop.state.quit();
|
|
||||||
},
|
|
||||||
|
|
||||||
ext.SDL_KEYUP => {
|
|
||||||
try events.push(.{.key_up = @enumFromInt(event.key.keysym.scancode)});
|
|
||||||
},
|
|
||||||
|
|
||||||
ext.SDL_KEYDOWN => {
|
|
||||||
try events.push(.{.key_down = @enumFromInt(event.key.keysym.scancode)});
|
|
||||||
},
|
|
||||||
|
|
||||||
ext.SDL_MOUSEBUTTONUP => {
|
|
||||||
try events.push(.{
|
|
||||||
.mouse_up = switch (event.button.button) {
|
|
||||||
ext.SDL_BUTTON_LEFT => .left,
|
|
||||||
ext.SDL_BUTTON_RIGHT => .right,
|
|
||||||
ext.SDL_BUTTON_MIDDLE => .middle,
|
|
||||||
else => unreachable,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
ext.SDL_MOUSEBUTTONDOWN => {
|
|
||||||
try events.push(.{
|
|
||||||
.mouse_down = switch (event.button.button) {
|
|
||||||
ext.SDL_BUTTON_LEFT => .left,
|
|
||||||
ext.SDL_BUTTON_RIGHT => .right,
|
|
||||||
ext.SDL_BUTTON_MIDDLE => .middle,
|
|
||||||
else => unreachable,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
ext.SDL_MOUSEMOTION => {
|
|
||||||
try events.push(.{
|
|
||||||
.mouse_motion = .{
|
|
||||||
.relative_position = .{@floatFromInt(event.motion.xrel), @floatFromInt(event.motion.yrel)},
|
|
||||||
.absolute_position = .{@floatFromInt(event.motion.x), @floatFromInt(event.motion.y)},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup(world: *ona.World) !void {
|
|
||||||
if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) {
|
|
||||||
return error.Unsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
const display = try world.set_get_state(Display{});
|
|
||||||
|
|
||||||
const assets = create: {
|
|
||||||
var assets = try Assets.init(display.width, display.height);
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
assets.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
break: create try world.set_get_state(assets);
|
|
||||||
};
|
|
||||||
|
|
||||||
assets.frame_rendered.set();
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
assets.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{
|
|
||||||
&assets.pending_work,
|
|
||||||
assets.window,
|
|
||||||
});
|
|
||||||
|
|
||||||
try world.on_event(.pre_update, ona.system_fn(poll), .{.label = "poll gfx"});
|
|
||||||
try world.on_event(.exit, ona.system_fn(stop), .{.label = "stop gfx"});
|
|
||||||
try world.on_event(.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(assets: ona.Write(Assets)) void {
|
|
||||||
assets.state.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void {
|
|
||||||
const assets, const display = exclusive.states;
|
|
||||||
|
|
||||||
assets.frame_rendered.wait();
|
|
||||||
assets.frame_rendered.reset();
|
|
||||||
|
|
||||||
var has_command_param = exclusive.world.get_params(Commands).has_head;
|
|
||||||
|
|
||||||
while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
|
|
||||||
command_param.param.rotate();
|
|
||||||
}
|
|
||||||
|
|
||||||
var display_width, var display_height = [_]c_int{0, 0};
|
|
||||||
|
|
||||||
ext.SDL_GL_GetDrawableSize(assets.window, &display_width, &display_height);
|
|
||||||
|
|
||||||
if (display.width != display_width or display.height != display_height) {
|
|
||||||
ext.SDL_SetWindowSize(assets.window, display.width, display.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exclusive.world.get_params(Commands).has_head) |has_commands| {
|
|
||||||
assets.pending_work.enqueue(.{
|
|
||||||
.render_frame = .{
|
|
||||||
.has_commands = has_commands,
|
|
||||||
.width = display.width,
|
|
||||||
.height = display.height,
|
|
||||||
.clear_color = display.clear_color,
|
|
||||||
.finished = &assets.frame_rendered,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
assets.frame_rendered.set();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,471 +0,0 @@
|
|||||||
const Resources = @import("./Resources.zig");
|
|
||||||
|
|
||||||
const gfx = @import("./gfx.zig");
|
|
||||||
|
|
||||||
const ext = @cImport({
|
|
||||||
@cInclude("SDL2/SDL.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
|
||||||
|
|
||||||
const spirv = @import("./spirv.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const Frame = struct {
|
|
||||||
texture_batch_buffers: ona.stack.Sequential(sokol.gfx.Buffer),
|
|
||||||
quad_index_buffer: sokol.gfx.Buffer,
|
|
||||||
quad_vertex_buffer: sokol.gfx.Buffer,
|
|
||||||
drawn_count: usize = 0,
|
|
||||||
flushed_count: usize = 0,
|
|
||||||
current_source_texture: gfx.Texture = .default,
|
|
||||||
current_target_texture: ?gfx.Texture = null,
|
|
||||||
current_effect: gfx.Effect = .default,
|
|
||||||
transform_matrix: Matrix = identity_matrix,
|
|
||||||
width: u16 = 0,
|
|
||||||
height: u16 = 0,
|
|
||||||
|
|
||||||
const DrawTexture = extern struct {
|
|
||||||
rotation: f32,
|
|
||||||
depth: f32,
|
|
||||||
position: gfx.Vector2,
|
|
||||||
size: gfx.Vector2,
|
|
||||||
anchor: gfx.Vector2,
|
|
||||||
source_clip: gfx.Rect,
|
|
||||||
tint: gfx.Color,
|
|
||||||
};
|
|
||||||
|
|
||||||
const View = extern struct {
|
|
||||||
projection_matrix: Matrix,
|
|
||||||
transform_matrix: Matrix,
|
|
||||||
};
|
|
||||||
|
|
||||||
const batches_per_buffer = 512;
|
|
||||||
|
|
||||||
pub fn deinit(self: *Frame) void {
|
|
||||||
for (self.texture_batch_buffers.values) |buffer| {
|
|
||||||
sokol.gfx.destroyBuffer(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.texture_batch_buffers.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init() !Frame {
|
|
||||||
const Vertex = struct {
|
|
||||||
xy: @Vector(2, f32),
|
|
||||||
uv: @Vector(2, f32),
|
|
||||||
};
|
|
||||||
|
|
||||||
const quad_index_buffer = sokol.gfx.makeBuffer(.{
|
|
||||||
.data = sokol.gfx.asRange(&[_]u16{0, 1, 2, 0, 2, 3}),
|
|
||||||
.type = .INDEXBUFFER,
|
|
||||||
});
|
|
||||||
|
|
||||||
const quad_vertex_buffer = sokol.gfx.makeBuffer(.{
|
|
||||||
.data = sokol.gfx.asRange(&[_]Vertex{
|
|
||||||
.{.xy = .{-0.5, -0.5}, .uv = .{0, 1}},
|
|
||||||
.{.xy = .{0.5, -0.5}, .uv = .{1, 1}},
|
|
||||||
.{.xy = .{0.5, 0.5}, .uv = .{1, 0}},
|
|
||||||
.{.xy = .{-0.5, 0.5}, .uv = .{0, 0}},
|
|
||||||
}),
|
|
||||||
|
|
||||||
.type = .VERTEXBUFFER,
|
|
||||||
});
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.texture_batch_buffers = .{},
|
|
||||||
.quad_index_buffer = quad_index_buffer,
|
|
||||||
.quad_vertex_buffer = quad_vertex_buffer,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_font(self: *Frame, resources: *Resources, command: gfx.Command.DrawFont) !void {
|
|
||||||
_ = self;
|
|
||||||
_ = resources;
|
|
||||||
_ = command;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Command.DrawTexture) !void {
|
|
||||||
if (self.current_source_texture != command.texture) {
|
|
||||||
self.flush(resources);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_source_texture = command.texture;
|
|
||||||
|
|
||||||
const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0;
|
|
||||||
const buffer_count = self.drawn_count / batches_per_buffer;
|
|
||||||
|
|
||||||
if (has_filled_current_buffer and buffer_count == self.texture_batch_buffers.len()) {
|
|
||||||
const instance_buffer = sokol.gfx.makeBuffer(.{
|
|
||||||
.size = @sizeOf(DrawTexture) * batches_per_buffer,
|
|
||||||
.usage = .STREAM,
|
|
||||||
});
|
|
||||||
|
|
||||||
errdefer sokol.gfx.destroyBuffer(instance_buffer);
|
|
||||||
|
|
||||||
try self.texture_batch_buffers.push_grow(instance_buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
const texture = resources.get_texture(command.texture).?;
|
|
||||||
|
|
||||||
_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{
|
|
||||||
.size = command.size orelse .{@floatFromInt(texture.width), @floatFromInt(texture.height)},
|
|
||||||
.anchor = command.anchor,
|
|
||||||
.rotation = command.rotation,
|
|
||||||
.depth = command.depth,
|
|
||||||
.source_clip = command.source,
|
|
||||||
.tint = command.tint,
|
|
||||||
.position = command.position,
|
|
||||||
}));
|
|
||||||
|
|
||||||
self.drawn_count += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(self: *Frame, resources: *Resources) void {
|
|
||||||
self.flush(resources);
|
|
||||||
sokol.gfx.endPass();
|
|
||||||
sokol.gfx.commit();
|
|
||||||
|
|
||||||
self.drawn_count = 0;
|
|
||||||
self.flushed_count = 0;
|
|
||||||
self.current_source_texture = .default;
|
|
||||||
self.current_target_texture = null;
|
|
||||||
self.current_effect = .default;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn flush(self: *Frame, resources: *Resources) void {
|
|
||||||
if (self.flushed_count == self.drawn_count) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var bindings = sokol.gfx.Bindings{
|
|
||||||
.index_buffer = self.quad_index_buffer,
|
|
||||||
};
|
|
||||||
|
|
||||||
bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer;
|
|
||||||
|
|
||||||
switch (resources.get_texture(self.current_source_texture).?.access) {
|
|
||||||
.render => |render| {
|
|
||||||
bindings.fs.images[0] = render.color_image;
|
|
||||||
bindings.fs.samplers[0] = default_sampler;
|
|
||||||
},
|
|
||||||
|
|
||||||
.static => |static| {
|
|
||||||
bindings.fs.images[0] = static.image;
|
|
||||||
bindings.fs.samplers[0] = default_sampler;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const effect = resources.get_effect(self.current_effect).?;
|
|
||||||
|
|
||||||
sokol.gfx.applyPipeline(effect.pipeline);
|
|
||||||
|
|
||||||
if (self.current_target_texture) |target_texture| {
|
|
||||||
const texture = resources.get_texture(target_texture).?;
|
|
||||||
|
|
||||||
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{
|
|
||||||
.projection_matrix = orthographic_projection(-1.0, 1.0, .{
|
|
||||||
.left = 0,
|
|
||||||
.top = 0,
|
|
||||||
.right = @floatFromInt(texture.width),
|
|
||||||
.bottom = @floatFromInt(texture.height),
|
|
||||||
}),
|
|
||||||
|
|
||||||
.transform_matrix = self.transform_matrix,
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{
|
|
||||||
.projection_matrix = orthographic_projection(-1.0, 1.0, .{
|
|
||||||
.left = 0,
|
|
||||||
.top = 0,
|
|
||||||
.right = @floatFromInt(self.width),
|
|
||||||
.bottom = @floatFromInt(self.height),
|
|
||||||
}),
|
|
||||||
|
|
||||||
.transform_matrix = self.transform_matrix,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (effect.properties.len != 0) {
|
|
||||||
sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties));
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const buffer_index = self.flushed_count / batches_per_buffer;
|
|
||||||
const buffer_offset = self.flushed_count % batches_per_buffer;
|
|
||||||
const instances_to_flush = @min(batches_per_buffer - buffer_offset, self.drawn_count - self.flushed_count);
|
|
||||||
|
|
||||||
self.flushed_count += instances_to_flush;
|
|
||||||
bindings.vertex_buffers[vertex_indices.instance] = self.texture_batch_buffers.values[buffer_index];
|
|
||||||
bindings.vertex_buffer_offsets[vertex_indices.instance] = @intCast(@sizeOf(DrawTexture) * buffer_offset);
|
|
||||||
|
|
||||||
sokol.gfx.applyBindings(bindings);
|
|
||||||
sokol.gfx.draw(0, 6, @intCast(instances_to_flush));
|
|
||||||
|
|
||||||
if (self.flushed_count == self.drawn_count) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Command.SetEffect) void {
|
|
||||||
if (command.effect != self.current_effect) {
|
|
||||||
self.flush(resources);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_effect = command.effect;
|
|
||||||
|
|
||||||
if (resources.get_effect(self.current_effect)) |effect| {
|
|
||||||
@memcpy(effect.properties, command.properties);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_scissor_rect(self: *Frame, resources: *Resources, has_rect: ?gfx.Rect) void {
|
|
||||||
self.flush(resources);
|
|
||||||
|
|
||||||
if (has_rect) |rect| {
|
|
||||||
const width, const height = rect.size();
|
|
||||||
|
|
||||||
sokol.gfx.applyScissorRect(
|
|
||||||
@intFromFloat(rect.left),
|
|
||||||
@intFromFloat(rect.top),
|
|
||||||
@intFromFloat(width),
|
|
||||||
@intFromFloat(height),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
sokol.gfx.applyScissorRect(0, 0, self.width, self.height, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Command.SetTarget) void {
|
|
||||||
sokol.gfx.endPass();
|
|
||||||
|
|
||||||
var pass = sokol.gfx.Pass{
|
|
||||||
.action = .{
|
|
||||||
.stencil = .{
|
|
||||||
.load_action = .CLEAR,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (command.clear_color) |color| {
|
|
||||||
pass.action.colors[0] = .{
|
|
||||||
.load_action = .CLEAR,
|
|
||||||
.clear_value = @bitCast(color),
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
pass.action.colors[0] = .{.load_action = .LOAD};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (command.clear_depth) |depth| {
|
|
||||||
pass.action.depth = .{
|
|
||||||
.load_action = .CLEAR,
|
|
||||||
.clear_value = depth,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
pass.action.depth = .{.load_action = .LOAD};
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_target_texture = command.texture;
|
|
||||||
|
|
||||||
if (self.current_target_texture) |target_texture| {
|
|
||||||
pass.attachments = switch (resources.get_texture(target_texture).?.access) {
|
|
||||||
.static => @panic("Cannot render to static textures"),
|
|
||||||
.render => |render| render.attachments,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
pass.swapchain = .{
|
|
||||||
.width = self.width,
|
|
||||||
.height = self.height,
|
|
||||||
.sample_count = 1,
|
|
||||||
.color_format = .RGBA8,
|
|
||||||
.depth_format = .DEPTH_STENCIL,
|
|
||||||
.gl = .{.framebuffer = 0},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
sokol.gfx.beginPass(pass);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start(self: *Frame, width: u16, height: u16) void {
|
|
||||||
self.width = width;
|
|
||||||
self.height = height;
|
|
||||||
|
|
||||||
sokol.gfx.beginPass(pass: {
|
|
||||||
var pass = sokol.gfx.Pass{
|
|
||||||
.swapchain = .{
|
|
||||||
.width = width,
|
|
||||||
.height = height,
|
|
||||||
.sample_count = 1,
|
|
||||||
.color_format = .RGBA8,
|
|
||||||
.depth_format = .DEPTH_STENCIL,
|
|
||||||
.gl = .{.framebuffer = 0},
|
|
||||||
},
|
|
||||||
|
|
||||||
.action = .{
|
|
||||||
.stencil = .{.load_action = .CLEAR},
|
|
||||||
.depth = .{.load_action = .CLEAR},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pass.action.colors[0] = .{.load_action = .CLEAR};
|
|
||||||
|
|
||||||
break: pass pass;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Matrix = [4]@Vector(4, f32);
|
|
||||||
|
|
||||||
var default_sampler: sokol.gfx.Sampler = undefined;
|
|
||||||
|
|
||||||
const identity_matrix = Matrix{
|
|
||||||
.{1, 0, 0, 0},
|
|
||||||
.{0, 1, 0, 0},
|
|
||||||
.{0, 0, 1, 0},
|
|
||||||
.{0, 0, 0, 1},
|
|
||||||
};
|
|
||||||
|
|
||||||
const vertex_indices = .{
|
|
||||||
.mesh = 0,
|
|
||||||
.instance = 1,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix {
|
|
||||||
const width = viewport.right - viewport.left;
|
|
||||||
const height = viewport.bottom - viewport.top;
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.{2 / width, 0, 0, 0},
|
|
||||||
.{0, 2 / height, 0, 0},
|
|
||||||
.{0, 0, 1 / (far - near), 0},
|
|
||||||
.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void {
|
|
||||||
const context = configure_and_create: {
|
|
||||||
var result = @as(c_int, 0);
|
|
||||||
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE);
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
|
||||||
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
|
|
||||||
|
|
||||||
if (result != 0) {
|
|
||||||
return error.Unsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
break: configure_and_create ext.SDL_GL_CreateContext(window);
|
|
||||||
};
|
|
||||||
|
|
||||||
defer ext.SDL_GL_DeleteContext(context);
|
|
||||||
|
|
||||||
sokol.gfx.setup(.{
|
|
||||||
.environment = .{
|
|
||||||
.defaults = .{
|
|
||||||
.color_format = .RGBA8,
|
|
||||||
.depth_format = .DEPTH_STENCIL,
|
|
||||||
.sample_count = 1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
.logger = .{
|
|
||||||
.func = sokol.log.func,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
defer {
|
|
||||||
sokol.gfx.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
var resources = try Resources.init();
|
|
||||||
|
|
||||||
defer {
|
|
||||||
resources.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
var frame = try Frame.init();
|
|
||||||
|
|
||||||
defer {
|
|
||||||
frame.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
default_sampler = sokol.gfx.makeSampler(.{});
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
switch (pending_work.dequeue()) {
|
|
||||||
.load_effect => |load| {
|
|
||||||
const effect = try resources.create_effect(load.desc);
|
|
||||||
|
|
||||||
if (!load.loaded.resolve(effect)) {
|
|
||||||
std.debug.assert(resources.destroy_effect(effect));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.load_texture => |load| {
|
|
||||||
const texture = try resources.create_texture(load.desc);
|
|
||||||
|
|
||||||
if (!load.loaded.resolve(texture)) {
|
|
||||||
std.debug.assert(resources.destroy_texture(texture));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.render_frame => |*render_frame| {
|
|
||||||
frame.start(render_frame.width, render_frame.height);
|
|
||||||
|
|
||||||
var has_commands = render_frame.has_commands;
|
|
||||||
|
|
||||||
while (has_commands) |commands| : (has_commands = commands.has_next) {
|
|
||||||
for (commands.param.submitted_commands()) |command| {
|
|
||||||
try switch (command) {
|
|
||||||
.draw_font => |draw_font| frame.draw_font(&resources, draw_font),
|
|
||||||
.draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture),
|
|
||||||
.set_effect => |set_effect| frame.set_effect(&resources, set_effect),
|
|
||||||
.set_scissor_rect => |has_rect| frame.set_scissor_rect(&resources, has_rect),
|
|
||||||
.set_target => |set_target| frame.set_target(&resources, set_target),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.flush(&resources);
|
|
||||||
|
|
||||||
if (frame.current_target_texture != null) {
|
|
||||||
frame.set_target(&resources, .{
|
|
||||||
.clear_color = null,
|
|
||||||
.clear_depth = null,
|
|
||||||
.clear_stencil = null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
frame.finish(&resources);
|
|
||||||
ext.SDL_GL_SwapWindow(window);
|
|
||||||
render_frame.finished.set();
|
|
||||||
},
|
|
||||||
|
|
||||||
.shutdown => {
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
|
|
||||||
.unload_effect => |unload| {
|
|
||||||
if (!resources.destroy_effect(unload.handle)) {
|
|
||||||
@panic("Attempt to unload a non-existent effect");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.unload_texture => |unload| {
|
|
||||||
if (!resources.destroy_texture(unload.handle)) {
|
|
||||||
@panic("Attempt to unload a non-existent texture");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var work_thread: std.Thread = undefined;
|
|
@ -1,16 +0,0 @@
|
|||||||
#version 430
|
|
||||||
|
|
||||||
layout (binding = 0) uniform sampler2D sprite;
|
|
||||||
|
|
||||||
layout (location = 0) in vec4 color;
|
|
||||||
layout (location = 1) in vec2 uv;
|
|
||||||
|
|
||||||
layout (location = 0) out vec4 texel;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
texel = texture(sprite, uv) * color;
|
|
||||||
|
|
||||||
if (texel.a == 0) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
#version 430
|
|
||||||
|
|
||||||
layout (location = 0) in vec2 batch_xy;
|
|
||||||
layout (location = 1) in vec2 batch_uv;
|
|
||||||
|
|
||||||
layout (location = 2) in float instance_rotation;
|
|
||||||
layout (location = 3) in float instance_depth;
|
|
||||||
layout (location = 4) in vec2 instance_position;
|
|
||||||
layout (location = 5) in vec2 instance_scale;
|
|
||||||
layout (location = 6) in vec2 instance_anchor;
|
|
||||||
layout (location = 7) in vec4 instance_clip;
|
|
||||||
layout (location = 8) in vec4 instance_tint;
|
|
||||||
|
|
||||||
layout (location = 0) out vec4 color;
|
|
||||||
layout (location = 1) out vec2 uv;
|
|
||||||
|
|
||||||
layout (binding = 0) uniform View {
|
|
||||||
mat4 projection_matrix;
|
|
||||||
mat4 transform_matrix;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
const vec2 normalized_position = (batch_xy + 0.5) - instance_anchor;
|
|
||||||
const float cos_theta = cos(instance_rotation);
|
|
||||||
const float sin_theta = sin(instance_rotation);
|
|
||||||
|
|
||||||
const mat2 rotation_matrix = mat2(
|
|
||||||
cos_theta, -sin_theta,
|
|
||||||
sin_theta, cos_theta
|
|
||||||
);
|
|
||||||
|
|
||||||
const vec2 rotated_position = rotation_matrix * (normalized_position * instance_scale);
|
|
||||||
const vec2 world_position = instance_position + rotated_position;
|
|
||||||
const vec4 clip_space_position = projection_matrix * transform_matrix * vec4(world_position, 0.0, 1.0);
|
|
||||||
|
|
||||||
gl_Position = vec4(clip_space_position.xy, instance_depth, 1.0);
|
|
||||||
|
|
||||||
const vec2 clip_size = instance_clip.zw - instance_clip.xy;
|
|
||||||
|
|
||||||
uv = instance_clip.xy + (batch_uv * clip_size);
|
|
||||||
color = instance_tint;
|
|
||||||
}
|
|
@ -1,411 +0,0 @@
|
|||||||
const gfx = @import("./gfx.zig");
|
|
||||||
|
|
||||||
const ext = @cImport({
|
|
||||||
@cInclude("spirv-cross/spirv_cross_c.h");
|
|
||||||
});
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Error = std.mem.Allocator.Error || error {
|
|
||||||
InvalidSPIRV,
|
|
||||||
UnsupportedSPIRV,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Options = struct {
|
|
||||||
target: Target,
|
|
||||||
vertex_source: Source,
|
|
||||||
fragment_source: Source,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Program = struct {
|
|
||||||
vertex_stage: Stage,
|
|
||||||
fragment_stage: Stage,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Source = struct {
|
|
||||||
entry_point: []const u8 = "main",
|
|
||||||
ops: []const u32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Stage = struct {
|
|
||||||
entry_point: [:0]const u8,
|
|
||||||
source: [:0]const u8,
|
|
||||||
has_image_sampler_pairs: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs,
|
|
||||||
has_images: [max_images]?Image = [_]?Image{null} ** max_images,
|
|
||||||
has_samplers: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers,
|
|
||||||
has_uniform_blocks: [max_uniform_blocks]?UniformBlock = [_]?UniformBlock{null} ** max_uniform_blocks,
|
|
||||||
|
|
||||||
pub const Image = struct {
|
|
||||||
is_multisampled: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ImageSamplerPair = struct {
|
|
||||||
image_slot: usize,
|
|
||||||
sampler_slot: usize,
|
|
||||||
name: [:0]const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Layout = enum {
|
|
||||||
std140,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Sampler = enum {
|
|
||||||
filtering,
|
|
||||||
non_filtering,
|
|
||||||
comparison,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Uniform = struct {
|
|
||||||
name: [:0]const u8,
|
|
||||||
type: Type,
|
|
||||||
len: u16,
|
|
||||||
|
|
||||||
pub const Type = enum {
|
|
||||||
float,
|
|
||||||
float2,
|
|
||||||
float3,
|
|
||||||
float4,
|
|
||||||
integer,
|
|
||||||
integer2,
|
|
||||||
integer3,
|
|
||||||
integer4,
|
|
||||||
matrix4,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const UniformBlock = struct {
|
|
||||||
layout: Layout,
|
|
||||||
uniforms: []const Uniform,
|
|
||||||
|
|
||||||
pub const max_uniforms = 16;
|
|
||||||
|
|
||||||
pub fn size(self: UniformBlock) usize {
|
|
||||||
var accumulated_size: usize = 0;
|
|
||||||
|
|
||||||
for (self.uniforms) |uniform| {
|
|
||||||
accumulated_size += @max(1, uniform.len) * @as(usize, switch (uniform.type) {
|
|
||||||
.float => 4,
|
|
||||||
.float2 => 8,
|
|
||||||
.float3 => 12,
|
|
||||||
.float4 => 16,
|
|
||||||
.integer => 4,
|
|
||||||
.integer2 => 8,
|
|
||||||
.integer3 => 12,
|
|
||||||
.integer4 => 16,
|
|
||||||
.matrix4 => 64,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const alignment: usize = switch (self.layout) {
|
|
||||||
.std140 => 16,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (accumulated_size + (alignment - 1)) & ~(alignment - 1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const max_images = 12;
|
|
||||||
|
|
||||||
pub const max_image_sampler_pairs = 12;
|
|
||||||
|
|
||||||
pub const max_samplers = 8;
|
|
||||||
|
|
||||||
pub const max_storage_buffers = 8;
|
|
||||||
|
|
||||||
pub const max_uniform_blocks = 8;
|
|
||||||
|
|
||||||
fn reflect_images_samplers(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
|
|
||||||
const arena_allocator = arena.allocator();
|
|
||||||
var sampled_images: []const ext.spvc_reflected_resource = &.{};
|
|
||||||
|
|
||||||
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
|
||||||
resources,
|
|
||||||
ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
|
|
||||||
@ptrCast(&sampled_images.ptr),
|
|
||||||
&sampled_images.len,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (sampled_images.len > max_image_sampler_pairs) {
|
|
||||||
return error.UnsupportedSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. sampled_images.len, sampled_images) |i, sampled_image| {
|
|
||||||
const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id);
|
|
||||||
|
|
||||||
if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) {
|
|
||||||
return error.InvalidSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) {
|
|
||||||
ext.SpvDim2D => {},
|
|
||||||
else => error.InvalidSPIRV,
|
|
||||||
};
|
|
||||||
|
|
||||||
try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) {
|
|
||||||
ext.SPVC_BASETYPE_FP32 => {},
|
|
||||||
else => error.InvalidSPIRV,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.has_images[i] = .{
|
|
||||||
.is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.has_samplers[i] = .filtering;
|
|
||||||
|
|
||||||
self.has_image_sampler_pairs[i] = .{
|
|
||||||
.name = try arena_allocator.dupeZ(u8, std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id))),
|
|
||||||
.image_slot = @intCast(i),
|
|
||||||
.sampler_slot = @intCast(i),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reflect_uniform_blocks(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
|
|
||||||
const arena_allocator = arena.allocator();
|
|
||||||
var reflected_resources: []const ext.spvc_reflected_resource = &.{};
|
|
||||||
|
|
||||||
try to_error(ext.spvc_resources_get_resource_list_for_type(
|
|
||||||
resources,
|
|
||||||
ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
|
|
||||||
@ptrCast(&reflected_resources.ptr),
|
|
||||||
&reflected_resources.len,
|
|
||||||
));
|
|
||||||
|
|
||||||
if (reflected_resources.len > max_uniform_blocks) {
|
|
||||||
return error.InvalidSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. reflected_resources.len, reflected_resources) |i, reflected_resource| {
|
|
||||||
const type_handle = ext.spvc_compiler_get_type_handle(compiler, reflected_resource.base_type_id);
|
|
||||||
const is_std430 = ext.spvc_compiler_has_decoration(compiler, reflected_resource.id, ext.SpvDecorationBufferBlock) == ext.SPVC_TRUE;
|
|
||||||
|
|
||||||
if (is_std430) {
|
|
||||||
return error.UnsupportedSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uniform_count = ext.spvc_type_get_num_member_types(type_handle);
|
|
||||||
|
|
||||||
if (uniform_count > UniformBlock.max_uniforms) {
|
|
||||||
return error.UnsupportedSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
const uniforms = try arena_allocator.alloc(Uniform, uniform_count);
|
|
||||||
|
|
||||||
for (uniforms, 0 .. uniform_count) |*uniform, j| {
|
|
||||||
const member_index: c_uint = @intCast(j);
|
|
||||||
const member_type_handle = ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_member_type(type_handle, member_index));
|
|
||||||
|
|
||||||
if (ext.spvc_type_get_num_array_dimensions(member_type_handle) > 1) {
|
|
||||||
return error.UnsupportedSPIRV;
|
|
||||||
}
|
|
||||||
|
|
||||||
uniform.* = .{
|
|
||||||
.name = try ona.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{
|
|
||||||
.id = reflected_resource.id,
|
|
||||||
.member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)),
|
|
||||||
}),
|
|
||||||
|
|
||||||
.type = try switch (ext.spvc_type_get_basetype(member_type_handle)) {
|
|
||||||
ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) {
|
|
||||||
4 => switch (ext.spvc_type_get_columns(member_type_handle)) {
|
|
||||||
4 => Uniform.Type.matrix4,
|
|
||||||
1 => Uniform.Type.float4,
|
|
||||||
else => error.UnsupportedSPIRV,
|
|
||||||
},
|
|
||||||
|
|
||||||
1 => Uniform.Type.float,
|
|
||||||
2 => Uniform.Type.float2,
|
|
||||||
3 => Uniform.Type.float3,
|
|
||||||
else => error.UnsupportedSPIRV,
|
|
||||||
},
|
|
||||||
|
|
||||||
ext.SPVC_BASETYPE_INT32 => try switch (ext.spvc_type_get_vector_size(member_type_handle)) {
|
|
||||||
1 => Uniform.Type.integer,
|
|
||||||
2 => Uniform.Type.integer2,
|
|
||||||
3 => Uniform.Type.integer3,
|
|
||||||
4 => Uniform.Type.integer4,
|
|
||||||
else => error.UnsupportedSPIRV,
|
|
||||||
},
|
|
||||||
|
|
||||||
else => error.UnsupportedSPIRV,
|
|
||||||
},
|
|
||||||
|
|
||||||
.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
self.has_uniform_blocks[i] = .{
|
|
||||||
.uniforms = uniforms,
|
|
||||||
.layout = .std140,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Target = enum {
|
|
||||||
glsl,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn analyze(arena: *std.heap.ArenaAllocator, options: Options) Error!Program {
|
|
||||||
var context: ext.spvc_context = null;
|
|
||||||
|
|
||||||
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
|
|
||||||
return error.OutOfMemory;
|
|
||||||
}
|
|
||||||
|
|
||||||
defer {
|
|
||||||
ext.spvc_context_destroy(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
ext.spvc_context_set_error_callback(context, log_errors, null);
|
|
||||||
|
|
||||||
const arena_allocator = arena.allocator();
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.vertex_stage = vertex_stage: {
|
|
||||||
const compiler = try parse(arena, context, options.target, options.vertex_source);
|
|
||||||
|
|
||||||
var stage = Stage{
|
|
||||||
.entry_point = try arena_allocator.dupeZ(u8, options.vertex_source.entry_point),
|
|
||||||
.source = try compile(arena, compiler),
|
|
||||||
};
|
|
||||||
|
|
||||||
const resources = create: {
|
|
||||||
var resources: ext.spvc_resources = null;
|
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
|
|
||||||
|
|
||||||
break: create resources;
|
|
||||||
};
|
|
||||||
|
|
||||||
try stage.reflect_images_samplers(arena, compiler, resources);
|
|
||||||
try stage.reflect_uniform_blocks(arena, compiler, resources);
|
|
||||||
|
|
||||||
if (ona.launch_arg(.dump_shader_translations) != null) {
|
|
||||||
std.log.info("Vertex translated:\n{s}", .{stage.source});
|
|
||||||
}
|
|
||||||
|
|
||||||
break: vertex_stage stage;
|
|
||||||
},
|
|
||||||
|
|
||||||
.fragment_stage = fragment_stage: {
|
|
||||||
const compiler = try parse(arena, context, options.target, options.fragment_source);
|
|
||||||
|
|
||||||
var stage = Stage{
|
|
||||||
.entry_point = try arena_allocator.dupeZ(u8, options.fragment_source.entry_point),
|
|
||||||
.source = try compile(arena, compiler),
|
|
||||||
};
|
|
||||||
|
|
||||||
const resources = create: {
|
|
||||||
var resources: ext.spvc_resources = null;
|
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
|
|
||||||
|
|
||||||
break: create resources;
|
|
||||||
};
|
|
||||||
|
|
||||||
try stage.reflect_images_samplers(arena, compiler, resources);
|
|
||||||
try stage.reflect_uniform_blocks(arena, compiler, resources);
|
|
||||||
|
|
||||||
if (ona.launch_arg(.dump_shader_translations) != null) {
|
|
||||||
std.log.info("Fragment translated:\n{s}", .{stage.source});
|
|
||||||
}
|
|
||||||
|
|
||||||
break: fragment_stage stage;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile(arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler) Error![:0]const u8 {
|
|
||||||
const arena_allocator = arena.allocator();
|
|
||||||
var source: [*:0]const u8 = "";
|
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&source)));
|
|
||||||
|
|
||||||
return arena_allocator.dupeZ(u8, std.mem.span(source));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void {
|
|
||||||
std.debug.assert(userdata == null);
|
|
||||||
std.log.err("{s}", .{error_message});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Target, source: Source) Error!ext.spvc_compiler {
|
|
||||||
var parsed_ir: ext.spvc_parsed_ir = null;
|
|
||||||
|
|
||||||
try to_error(ext.spvc_context_parse_spirv(context, source.ops.ptr, source.ops.len, &parsed_ir));
|
|
||||||
|
|
||||||
var compiler: ext.spvc_compiler = null;
|
|
||||||
|
|
||||||
const spvc_target = switch (target) {
|
|
||||||
.glsl => ext.SPVC_BACKEND_GLSL,
|
|
||||||
};
|
|
||||||
|
|
||||||
try to_error(ext.spvc_context_create_compiler(context, spvc_target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler));
|
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_build_combined_image_samplers(compiler));
|
|
||||||
|
|
||||||
var combined_image_samplers: []const ext.spvc_combined_image_sampler = &.{};
|
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_get_combined_image_samplers(compiler, @ptrCast(&combined_image_samplers.ptr), &combined_image_samplers.len));
|
|
||||||
|
|
||||||
const arena_allocator = arena.allocator();
|
|
||||||
var binding: u32 = 0;
|
|
||||||
|
|
||||||
for (combined_image_samplers) |combined_image_sampler| {
|
|
||||||
const name = try ona.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
|
|
||||||
.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
|
|
||||||
.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
|
|
||||||
});
|
|
||||||
|
|
||||||
ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name);
|
|
||||||
ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding);
|
|
||||||
|
|
||||||
binding += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const option_values: []const struct {ext.spvc_compiler_option, c_uint} = switch (target) {
|
|
||||||
.glsl => &.{
|
|
||||||
.{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430},
|
|
||||||
.{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)},
|
|
||||||
.{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)},
|
|
||||||
.{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)},
|
|
||||||
.{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
var compiler_options: ext.spvc_compiler_options = null;
|
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options));
|
|
||||||
|
|
||||||
for (option_values) |option_value| {
|
|
||||||
const entry, const value = option_value;
|
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
try to_error(ext.spvc_compiler_install_compiler_options(compiler, compiler_options));
|
|
||||||
|
|
||||||
return compiler;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_error(result: ext.spvc_result) Error!void {
|
|
||||||
return switch (result) {
|
|
||||||
ext.SPVC_SUCCESS => {},
|
|
||||||
ext.SPVC_ERROR_INVALID_SPIRV => error.InvalidSPIRV,
|
|
||||||
ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSPIRV,
|
|
||||||
ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory,
|
|
||||||
ext.SPVC_ERROR_INVALID_ARGUMENT, ext.SPVC_ERROR_INT_MAX => unreachable,
|
|
||||||
else => unreachable,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_ops(raw: anytype) [raw.len / @alignOf(u32)]u32 {
|
|
||||||
var ops: [raw.len / @alignOf(u32)]u32 = undefined;
|
|
||||||
|
|
||||||
@memcpy(std.mem.sliceAsBytes(&ops), raw);
|
|
||||||
|
|
||||||
return ops;
|
|
||||||
}
|
|
234
src/gui/gui.zig
234
src/gui/gui.zig
@ -1,234 +0,0 @@
|
|||||||
const gfx = @import("gfx");
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Box = struct {
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
width: f32,
|
|
||||||
height: f32,
|
|
||||||
|
|
||||||
pub fn rect(self: Box) gfx.Rect {
|
|
||||||
return .{
|
|
||||||
.left = self.x,
|
|
||||||
.top = self.y,
|
|
||||||
.right = self.x + self.width,
|
|
||||||
.bottom = self.y + self.height,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Canvas = struct {
|
|
||||||
nodes: NodeMap = NodeMap.init(ona.heap.allocator),
|
|
||||||
drawable_blocks: DrawableBlockMap = DrawableBlockMap.init(ona.heap.allocator),
|
|
||||||
drawable_labels: DrawableLabelMap = DrawableLabelMap.init(ona.heap.allocator),
|
|
||||||
|
|
||||||
fn Drawable(comptime State: type) type {
|
|
||||||
return struct {
|
|
||||||
clip: gfx.Rect,
|
|
||||||
box: Box,
|
|
||||||
depth: f32,
|
|
||||||
state: State,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const DrawableBlockMap = ona.SlotMap(Drawable(struct {
|
|
||||||
background: gfx.Texture = .default,
|
|
||||||
color: gfx.Color = gfx.colors.white,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const DrawableLabelMap = ona.SlotMap(Drawable(struct {
|
|
||||||
text: ona.stack.Sequential(u8) = .{},
|
|
||||||
color: gfx.Color = gfx.colors.black,
|
|
||||||
font: gfx.Font = .default,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const NodeMap = ona.SlotMap(struct {
|
|
||||||
name: []u8,
|
|
||||||
box: Box,
|
|
||||||
parent_index: u32 = unused_index,
|
|
||||||
block_index: u32 = unused_index,
|
|
||||||
label_index: u32 = unused_index,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
fn clip(self: Self, nodes: NodeMap) gfx.Rect {
|
|
||||||
if (self.parent_index == unused_index) {
|
|
||||||
return self.box.rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
return nodes.get(self.parent_index).?.box.rect();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn depth(self: Self, nodes: NodeMap) f32 {
|
|
||||||
var parent_index = self.parent_index;
|
|
||||||
var value: f32 = 0;
|
|
||||||
|
|
||||||
while (parent_index != unused_index) : (parent_index = nodes.get(parent_index).?.parent_index) {
|
|
||||||
value += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
pub fn append(self: *Canvas, name: []const u8, box: Box) std.mem.Allocator.Error!Item {
|
|
||||||
const duped_name = try ona.heap.allocator.dupe(u8, name);
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
ona.heap.allocator.free(duped_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return @enumFromInt(try self.nodes.insert(.{
|
|
||||||
.name = duped_name,
|
|
||||||
.box = box,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: *Canvas) void {
|
|
||||||
self.nodes.deinit();
|
|
||||||
self.drawable_blocks.deinit();
|
|
||||||
|
|
||||||
for (self.drawable_labels.values()) |*drawable_label| {
|
|
||||||
drawable_label.state.text.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.drawable_labels.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_block(self: *Canvas, item: Item, set: SetBlock) std.mem.Allocator.Error!void {
|
|
||||||
const node = self.nodes.get(@intFromEnum(item)).?;
|
|
||||||
|
|
||||||
if (node.block_index == unused_index) {
|
|
||||||
node.block_index = @intCast(try self.drawable_blocks.insert(.{
|
|
||||||
.clip = node.clip(self.nodes),
|
|
||||||
.depth = node.depth(self.nodes),
|
|
||||||
.box = node.box,
|
|
||||||
.state = .{},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const block = self.drawable_blocks.get(node.block_index).?;
|
|
||||||
|
|
||||||
if (set.has_background) |background| {
|
|
||||||
block.state.background = background;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (set.has_color) |color| {
|
|
||||||
block.state.color = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_label(self: *Canvas, item: Item, set: SetLabel) std.mem.Allocator.Error!void {
|
|
||||||
const node = self.nodes.get(@intFromEnum(item)).?;
|
|
||||||
|
|
||||||
if (node.label_index == unused_index) {
|
|
||||||
node.label_index = @intCast(try self.drawable_labels.insert(.{
|
|
||||||
.clip = node.clip(self.nodes),
|
|
||||||
.depth = node.depth(self.nodes),
|
|
||||||
.box = node.box,
|
|
||||||
.state = .{},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
const label = self.drawable_labels.get(node.label_index).?;
|
|
||||||
|
|
||||||
if (set.has_text) |text| {
|
|
||||||
label.state.text.pop_all();
|
|
||||||
|
|
||||||
try label.state.text.grow(ona.scalars.sub(text.len, label.state.text.cap) orelse 0);
|
|
||||||
|
|
||||||
std.debug.assert(label.state.text.push_all(text));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (set.has_font) |font| {
|
|
||||||
label.state.font = font;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (set.has_color) |color| {
|
|
||||||
label.state.color = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_parent(self: *Canvas, item: Item, has_parent: ?Item) void {
|
|
||||||
const node = self.nodes.get(@intFromEnum(item)).?;
|
|
||||||
|
|
||||||
node.parent_index = if (has_parent) |parent| @intFromEnum(parent) else unused_index;
|
|
||||||
|
|
||||||
const depth = node.depth(self.nodes);
|
|
||||||
const clip = node.clip(self.nodes);
|
|
||||||
|
|
||||||
if (node.block_index != unused_index) {
|
|
||||||
const block = self.drawable_blocks.get(node.block_index).?;
|
|
||||||
|
|
||||||
block.depth = depth;
|
|
||||||
block.clip = clip;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.label_index != unused_index) {
|
|
||||||
const label = self.drawable_labels.get(node.label_index).?;
|
|
||||||
|
|
||||||
label.depth = depth;
|
|
||||||
label.clip = clip;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const unused_index = std.math.maxInt(u32);
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Item = enum (u32) {
|
|
||||||
_,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SetBlock = struct {
|
|
||||||
has_background: ?gfx.Texture = null,
|
|
||||||
has_color: ?gfx.Color = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SetLabel = struct {
|
|
||||||
has_text: ?[]const u8 = null,
|
|
||||||
has_color: ?gfx.Color = null,
|
|
||||||
has_font: ?gfx.Font = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn exit(canvas: ona.Write(Canvas)) !void {
|
|
||||||
canvas.state.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(commands: gfx.Commands, canvas: ona.Read(Canvas)) !void {
|
|
||||||
// TODO: Investigate if scissor rects are necessary for clipping content that overflows.
|
|
||||||
for (canvas.state.drawable_blocks.values()) |block| {
|
|
||||||
try commands.set_scissor_rect(block.clip);
|
|
||||||
|
|
||||||
try commands.draw_texture(.{
|
|
||||||
.size = .{block.box.width, block.box.height},
|
|
||||||
.position = .{block.box.x, block.box.y},
|
|
||||||
.depth = block.depth,
|
|
||||||
.texture = block.state.background,
|
|
||||||
.tint = block.state.color,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (canvas.state.drawable_labels.values()) |label| {
|
|
||||||
try commands.set_scissor_rect(label.clip);
|
|
||||||
|
|
||||||
try commands.draw_font(.{
|
|
||||||
.size = .{label.box.width, label.box.height},
|
|
||||||
.depth = label.depth,
|
|
||||||
.text = label.state.text.values,
|
|
||||||
.position = .{label.box.x, label.box.y},
|
|
||||||
.tint = label.state.color,
|
|
||||||
.font = label.state.font,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup(world: *ona.World) !void {
|
|
||||||
try world.set_state(Canvas{});
|
|
||||||
try world.on_event(.render, ona.system_fn(render), .{.label = "render gui"});
|
|
||||||
try world.on_event(.exit, ona.system_fn(exit), .{.label = "cleanup gui"});
|
|
||||||
}
|
|
335
src/hid/hid.zig
335
src/hid/hid.zig
@ -1,335 +0,0 @@
|
|||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Axis = struct {
|
|
||||||
has_key_scancodes: ?[2]KeyScancode = null,
|
|
||||||
has_mouse_buttons: ?[2]MouseButton = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Event = union (enum) {
|
|
||||||
key_up: KeyScancode,
|
|
||||||
key_down: KeyScancode,
|
|
||||||
mouse_up: MouseButton,
|
|
||||||
mouse_down: MouseButton,
|
|
||||||
mouse_motion: MouseMotion,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const KeyScancode = enum (u32) {
|
|
||||||
no_event = 0x00,
|
|
||||||
error_rollover = 0x01,
|
|
||||||
post_fail = 0x02,
|
|
||||||
error_undefined = 0x03,
|
|
||||||
a = 0x04,
|
|
||||||
b = 0x05,
|
|
||||||
c = 0x06,
|
|
||||||
d = 0x07,
|
|
||||||
e = 0x08,
|
|
||||||
f = 0x09,
|
|
||||||
g = 0x0A,
|
|
||||||
h = 0x0B,
|
|
||||||
i = 0x0C,
|
|
||||||
j = 0x0D,
|
|
||||||
k = 0x0E,
|
|
||||||
l = 0x0F,
|
|
||||||
m = 0x10,
|
|
||||||
n = 0x11,
|
|
||||||
o = 0x12,
|
|
||||||
p = 0x13,
|
|
||||||
q = 0x14,
|
|
||||||
r = 0x15,
|
|
||||||
s = 0x16,
|
|
||||||
t = 0x17,
|
|
||||||
u = 0x18,
|
|
||||||
v = 0x19,
|
|
||||||
w = 0x1A,
|
|
||||||
x = 0x1B,
|
|
||||||
y = 0x1C,
|
|
||||||
z = 0x1D,
|
|
||||||
one = 0x1E,
|
|
||||||
two = 0x1F,
|
|
||||||
three = 0x20,
|
|
||||||
four = 0x21,
|
|
||||||
five = 0x22,
|
|
||||||
six = 0x23,
|
|
||||||
seven = 0x24,
|
|
||||||
eight = 0x25,
|
|
||||||
nine = 0x26,
|
|
||||||
zero = 0x27,
|
|
||||||
enter = 0x28,
|
|
||||||
escape = 0x29,
|
|
||||||
backspace = 0x2A,
|
|
||||||
tab = 0x2B,
|
|
||||||
space = 0x2C,
|
|
||||||
minus = 0x2D,
|
|
||||||
equal = 0x2E,
|
|
||||||
left_bracket = 0x2F,
|
|
||||||
right_bracket = 0x30,
|
|
||||||
backslash = 0x31,
|
|
||||||
non_us_pound = 0x32,
|
|
||||||
semicolon = 0x33,
|
|
||||||
quote = 0x34,
|
|
||||||
grave = 0x35,
|
|
||||||
comma = 0x36,
|
|
||||||
period = 0x37,
|
|
||||||
slash = 0x38,
|
|
||||||
caps_lock = 0x39,
|
|
||||||
f1 = 0x3A,
|
|
||||||
f2 = 0x3B,
|
|
||||||
f3 = 0x3C,
|
|
||||||
f4 = 0x3D,
|
|
||||||
f5 = 0x3E,
|
|
||||||
f6 = 0x3F,
|
|
||||||
f7 = 0x40,
|
|
||||||
f8 = 0x41,
|
|
||||||
f9 = 0x42,
|
|
||||||
f10 = 0x43,
|
|
||||||
f11 = 0x44,
|
|
||||||
f12 = 0x45,
|
|
||||||
print_screen = 0x46,
|
|
||||||
scroll_lock = 0x47,
|
|
||||||
pause = 0x48,
|
|
||||||
insert = 0x49,
|
|
||||||
home = 0x4A,
|
|
||||||
page_up = 0x4B,
|
|
||||||
delete = 0x4C,
|
|
||||||
end = 0x4D,
|
|
||||||
page_down = 0x4E,
|
|
||||||
right_arrow = 0x4F,
|
|
||||||
left_arrow = 0x50,
|
|
||||||
down_arrow = 0x51,
|
|
||||||
up_arrow = 0x52,
|
|
||||||
num_lock = 0x53,
|
|
||||||
keypad_slash = 0x54,
|
|
||||||
keypad_asterisk = 0x55,
|
|
||||||
keypad_minus = 0x56,
|
|
||||||
keypad_plus = 0x57,
|
|
||||||
keypad_enter = 0x58,
|
|
||||||
keypad_one = 0x59,
|
|
||||||
keypad_two = 0x5A,
|
|
||||||
keypad_three = 0x5B,
|
|
||||||
keypad_four = 0x5C,
|
|
||||||
keypad_five = 0x5D,
|
|
||||||
keypad_six = 0x5E,
|
|
||||||
keypad_seven = 0x5F,
|
|
||||||
keypad_eight = 0x60,
|
|
||||||
keypad_nine = 0x61,
|
|
||||||
keypad_zero = 0x62,
|
|
||||||
keypad_period = 0x63,
|
|
||||||
non_us_backslash = 0x64,
|
|
||||||
application = 0x65,
|
|
||||||
power = 0x66,
|
|
||||||
keypad_equal = 0x67,
|
|
||||||
f13 = 0x68,
|
|
||||||
f14 = 0x69,
|
|
||||||
f15 = 0x6A,
|
|
||||||
f16 = 0x6B,
|
|
||||||
f17 = 0x6C,
|
|
||||||
f18 = 0x6D,
|
|
||||||
f19 = 0x6E,
|
|
||||||
f20 = 0x6F,
|
|
||||||
f21 = 0x70,
|
|
||||||
f22 = 0x71,
|
|
||||||
f23 = 0x72,
|
|
||||||
f24 = 0x73,
|
|
||||||
execute = 0x74,
|
|
||||||
help = 0x75,
|
|
||||||
menu = 0x76,
|
|
||||||
select = 0x77,
|
|
||||||
stop = 0x78,
|
|
||||||
again = 0x79,
|
|
||||||
undo = 0x7A,
|
|
||||||
cut = 0x7B,
|
|
||||||
copy = 0x7C,
|
|
||||||
paste = 0x7D,
|
|
||||||
find = 0x7E,
|
|
||||||
mute = 0x7F,
|
|
||||||
volume_up = 0x80,
|
|
||||||
volume_down = 0x81,
|
|
||||||
lock_caps_lock = 0x82,
|
|
||||||
lock_num_lock = 0x83,
|
|
||||||
lock_scroll_lock = 0x84,
|
|
||||||
keypad_comma = 0x85,
|
|
||||||
keypad_equal_sign = 0x86,
|
|
||||||
international1 = 0x87,
|
|
||||||
international2 = 0x88,
|
|
||||||
international3 = 0x89,
|
|
||||||
international4 = 0x8A,
|
|
||||||
international5 = 0x8B,
|
|
||||||
international6 = 0x8C,
|
|
||||||
international7 = 0x8D,
|
|
||||||
international8 = 0x8E,
|
|
||||||
international9 = 0x8F,
|
|
||||||
lang1 = 0x90,
|
|
||||||
lang2 = 0x91,
|
|
||||||
lang3 = 0x92,
|
|
||||||
lang4 = 0x93,
|
|
||||||
lang5 = 0x94,
|
|
||||||
lang6 = 0x95,
|
|
||||||
lang7 = 0x96,
|
|
||||||
lang8 = 0x97,
|
|
||||||
lang9 = 0x98,
|
|
||||||
alternate_erase = 0x99,
|
|
||||||
sys_req_attention = 0x9A,
|
|
||||||
cancel = 0x9B,
|
|
||||||
clear = 0x9C,
|
|
||||||
prior = 0x9D,
|
|
||||||
return_key = 0x9E,
|
|
||||||
separator = 0x9F,
|
|
||||||
out = 0xA0,
|
|
||||||
oper = 0xA1,
|
|
||||||
clear_again = 0xA2,
|
|
||||||
cr_sel_props = 0xA3,
|
|
||||||
ex_sel = 0xA4,
|
|
||||||
left_control = 0xE0,
|
|
||||||
left_shift = 0xE1,
|
|
||||||
left_alt = 0xE2,
|
|
||||||
left_gui = 0xE3,
|
|
||||||
right_control = 0xE4,
|
|
||||||
right_shift = 0xE5,
|
|
||||||
right_alt = 0xE6,
|
|
||||||
right_gui = 0xE7,
|
|
||||||
_,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Mapping = struct {
|
|
||||||
key_scancodes: ActionSets(512) = .{},
|
|
||||||
mouse_buttons: ActionSets(std.enums.values(MouseButton).len) = .{},
|
|
||||||
mouse_position: MousePosition = @splat(0),
|
|
||||||
|
|
||||||
fn ActionSets(comptime len: usize) type {
|
|
||||||
const Set = std.bit_set.StaticBitSet(len);
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
released: Set = Set.initEmpty(),
|
|
||||||
pressed: Set = Set.initEmpty(),
|
|
||||||
held: Set = Set.initEmpty(),
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
fn expire_frame(self: *Self) void {
|
|
||||||
self.pressed = Set.initEmpty();
|
|
||||||
self.released = Set.initEmpty();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn axis_strength(self: Mapping, axis: Axis) f32 {
|
|
||||||
if (axis.has_key_scancodes) |key_scancodes| {
|
|
||||||
const neg_scancode, const pos_scancode = key_scancodes;
|
|
||||||
const is_neg_held = self.key_scancodes.held.isSet(@intFromEnum(neg_scancode));
|
|
||||||
const is_pos_held = self.key_scancodes.held.isSet(@intFromEnum(pos_scancode));
|
|
||||||
|
|
||||||
if (is_neg_held or is_pos_held) {
|
|
||||||
return
|
|
||||||
@as(f32, @floatFromInt(@intFromBool(is_pos_held))) -
|
|
||||||
@as(f32, @floatFromInt(@intFromBool(is_neg_held)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (axis.has_mouse_buttons) |mouse_buttons| {
|
|
||||||
const neg_button, const pos_button = mouse_buttons;
|
|
||||||
const is_neg_held = self.mouse_buttons.held.isSet(@intFromEnum(neg_button));
|
|
||||||
const is_pos_held = self.mouse_buttons.held.isSet(@intFromEnum(pos_button));
|
|
||||||
|
|
||||||
if (is_neg_held or is_pos_held) {
|
|
||||||
return
|
|
||||||
@as(f32, @floatFromInt(@intFromBool(is_pos_held))) -
|
|
||||||
@as(f32, @floatFromInt(@intFromBool(is_neg_held)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const MouseButton = enum {
|
|
||||||
left,
|
|
||||||
right,
|
|
||||||
middle,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const MouseMotion = struct {
|
|
||||||
relative_position: MousePosition,
|
|
||||||
absolute_position: MousePosition,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const MousePosition = @Vector(2, f32);
|
|
||||||
|
|
||||||
test "mapping values" {
|
|
||||||
const axis = Axis{
|
|
||||||
.has_key_scancodes = .{.minus, .equal},
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
var mapping = Mapping{};
|
|
||||||
|
|
||||||
try std.testing.expectEqual(mapping.axis_strength(axis), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var mapping = Mapping{};
|
|
||||||
|
|
||||||
mapping.key_scancodes.held.set(@intFromEnum(KeyScancode.equal));
|
|
||||||
|
|
||||||
try std.testing.expectEqual(mapping.axis_strength(axis), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var mapping = Mapping{};
|
|
||||||
|
|
||||||
mapping.key_scancodes.held.set(@intFromEnum(KeyScancode.minus));
|
|
||||||
|
|
||||||
try std.testing.expectEqual(mapping.axis_strength(axis), -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var mapping = Mapping{};
|
|
||||||
|
|
||||||
mapping.key_scancodes.held.set(@intFromEnum(KeyScancode.minus));
|
|
||||||
mapping.key_scancodes.held.set(@intFromEnum(KeyScancode.equal));
|
|
||||||
|
|
||||||
try std.testing.expectEqual(mapping.axis_strength(axis), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup(world: *ona.World) std.mem.Allocator.Error!void {
|
|
||||||
try world.set_state(Mapping{});
|
|
||||||
|
|
||||||
try world.on_event(.pre_update, ona.system_fn(update), .{
|
|
||||||
.label = "update actions",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(inputs: ona.Receive(Event), mapping: ona.Write(Mapping)) void {
|
|
||||||
mapping.state.key_scancodes.expire_frame();
|
|
||||||
mapping.state.mouse_buttons.expire_frame();
|
|
||||||
|
|
||||||
for (inputs.messages()) |message| {
|
|
||||||
switch (message) {
|
|
||||||
.key_down => |scancode| {
|
|
||||||
mapping.state.key_scancodes.pressed.set(@intFromEnum(scancode));
|
|
||||||
mapping.state.key_scancodes.held.set(@intFromEnum(scancode));
|
|
||||||
},
|
|
||||||
|
|
||||||
.key_up => |scancode| {
|
|
||||||
mapping.state.key_scancodes.held.unset(@intFromEnum(scancode));
|
|
||||||
mapping.state.key_scancodes.released.set(@intFromEnum(scancode));
|
|
||||||
},
|
|
||||||
|
|
||||||
.mouse_down => |button| {
|
|
||||||
mapping.state.mouse_buttons.pressed.unset(@intFromEnum(button));
|
|
||||||
mapping.state.mouse_buttons.held.set(@intFromEnum(button));
|
|
||||||
},
|
|
||||||
|
|
||||||
.mouse_up => |button| {
|
|
||||||
mapping.state.key_scancodes.held.unset(@intFromEnum(button));
|
|
||||||
mapping.state.key_scancodes.released.set(@intFromEnum(button));
|
|
||||||
},
|
|
||||||
|
|
||||||
.mouse_motion => |motion| {
|
|
||||||
mapping.state.mouse_position = motion.absolute_position;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
106
src/ona/App.zig
Normal file
106
src/ona/App.zig
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
pub const Behavior = @import("./App/Behavior.zig");
|
||||||
|
|
||||||
|
pub const Setup = @import("./App/Setup.zig");
|
||||||
|
|
||||||
|
const SystemGraph = @import("./App/SystemGraph.zig");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
initialized_states: coral.map.Hashed(*const coral.TypeId, coral.Box, coral.map.ptrTraits(*const coral.TypeId)),
|
||||||
|
named_systems: coral.map.Hashed([]const u8, SystemGraph, coral.map.string_traits),
|
||||||
|
is_running: bool,
|
||||||
|
|
||||||
|
pub const RunError = std.mem.Allocator.Error || error{
|
||||||
|
MissingDependency,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Time = struct {
|
||||||
|
elapsed: f64,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
{
|
||||||
|
var entries = self.named_systems.entries();
|
||||||
|
|
||||||
|
while (entries.next()) |entry| {
|
||||||
|
entry.value.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.named_systems.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var entries = self.initialized_states.entries();
|
||||||
|
|
||||||
|
while (entries.next()) |entry| {
|
||||||
|
entry.value.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.initialized_states.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hasState(self: *Self, comptime State: type) ?*State {
|
||||||
|
if (self.initialized_states.get(.of(State))) |boxed_state| {
|
||||||
|
return boxed_state.has(State).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() std.mem.Allocator.Error!Self {
|
||||||
|
var self = Self{
|
||||||
|
.initialized_states = .empty,
|
||||||
|
.named_systems = .empty,
|
||||||
|
.is_running = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
try self.setState(Time{
|
||||||
|
.elapsed = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on(self: *Self, comptime schedule: anytype, behavior: *const Behavior) std.mem.Allocator.Error!void {
|
||||||
|
try (try self.systemNamed(schedule)).insert(self, behavior);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn putState(self: *Self, state: anytype) std.mem.Allocator.Error!*@TypeOf(state) {
|
||||||
|
try self.setState(state);
|
||||||
|
|
||||||
|
return self.hasState(@TypeOf(state)).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(self: *Self, tasks: *coral.asio.TaskQueue, comptime schedule: anytype) RunError!void {
|
||||||
|
try (try self.systemNamed(schedule)).run(self, tasks);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setState(self: *Self, state: anytype) std.mem.Allocator.Error!void {
|
||||||
|
var boxed_state = try coral.Box.init(state);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
boxed_state.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
var replaced = try self.initialized_states.replace(.of(@TypeOf(state)), boxed_state);
|
||||||
|
|
||||||
|
if (replaced) |*entry| {
|
||||||
|
entry.value.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn systemNamed(self: *Self, comptime schedule: anytype) std.mem.Allocator.Error!*SystemGraph {
|
||||||
|
const schedule_name = switch (@typeInfo(@TypeOf(schedule))) {
|
||||||
|
.enum_literal => @tagName(schedule),
|
||||||
|
else => @compileError("paramater `schedule` must be an enum literal type"),
|
||||||
|
};
|
||||||
|
|
||||||
|
return self.named_systems.get(schedule_name) orelse (try self.named_systems.emplace(schedule_name, .empty)).?;
|
||||||
|
}
|
249
src/ona/App/Behavior.zig
Normal file
249
src/ona/App/Behavior.zig
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
const SystemGraph = @import("./SystemGraph.zig");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const ona = @import("../ona.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
label: [*:0]const u8,
|
||||||
|
on_insertion: *const fn (*const Self, *ona.App, *SystemGraph) std.mem.Allocator.Error!void,
|
||||||
|
on_run: *const fn (*ona.App) void,
|
||||||
|
traits: ona.Traits,
|
||||||
|
|
||||||
|
const Param = struct {
|
||||||
|
init_params: []const InitParam,
|
||||||
|
has_bind: ?fn (*ona.App) void = null,
|
||||||
|
traits: ona.Traits = .{},
|
||||||
|
|
||||||
|
const InitParam = struct {
|
||||||
|
type: type,
|
||||||
|
is_required: bool,
|
||||||
|
is_read_only: bool,
|
||||||
|
traits: ona.Traits,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn has_fn(comptime SystemParam: type, comptime name: []const u8) ?std.builtin.Type.Fn {
|
||||||
|
switch (@typeInfo(SystemParam)) {
|
||||||
|
.@"struct", .@"opaque" => {},
|
||||||
|
else => @compileError("system fn params must be either struct or opaque types"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(SystemParam, name)) {
|
||||||
|
return switch (@typeInfo(@TypeOf(@field(SystemParam, name)))) {
|
||||||
|
.@"fn" => |@"fn"| @"fn",
|
||||||
|
else => @compileError(std.fmt.comptimePrint("system params with a .{s} declaration must be made an fn type", .{name})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(comptime fn_param: std.builtin.Type.Fn.Param) Param {
|
||||||
|
if (fn_param.is_generic) {
|
||||||
|
@compileError("generic params on behavior fns are disallowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const init_fn = has_fn(fn_param.type.?, "init") orelse {
|
||||||
|
@compileError("behavior params must declare a .init fn");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (init_fn.return_type != fn_param.type.?) {
|
||||||
|
const param_type_name = @typeName(fn_param.type.?);
|
||||||
|
|
||||||
|
@compileError(std.fmt.comptimePrint("{s}.init must return {s}", .{ param_type_name, param_type_name }));
|
||||||
|
}
|
||||||
|
|
||||||
|
const init_params = struct {
|
||||||
|
const instance = get: {
|
||||||
|
var buffer: [init_fn.params.len]InitParam = undefined;
|
||||||
|
|
||||||
|
for (&buffer, init_fn.params) |*init_param, init_fn_param| {
|
||||||
|
if (init_fn_param.is_generic) {
|
||||||
|
@compileError("generic params on Param.init fns are disallowed");
|
||||||
|
}
|
||||||
|
|
||||||
|
init_param.* = switch (@typeInfo(init_fn_param.type.?)) {
|
||||||
|
.pointer => |pointer| .{
|
||||||
|
.type = pointer.child,
|
||||||
|
.is_required = true,
|
||||||
|
.is_read_only = pointer.is_const,
|
||||||
|
.traits = if (@hasDecl(pointer.child, "traits") and @TypeOf(pointer.child.traits) == ona.Traits) pointer.child.traits else .{},
|
||||||
|
},
|
||||||
|
|
||||||
|
.optional => |optional| switch (@typeInfo(optional.child)) {
|
||||||
|
.pointer => |pointer| .{
|
||||||
|
.type = pointer.child,
|
||||||
|
.is_required = false,
|
||||||
|
.is_read_only = pointer.is_const,
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("non-pointer optional params on Param.init fns are disallowed"),
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("params on Param.init fns must be optionals or pointers"),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
break :get buffer;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
var param = Param{
|
||||||
|
.init_params = &init_params.instance,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (has_fn(fn_param.type.?, "bind")) |bind_fn| {
|
||||||
|
const type_error_message = std.fmt.comptimePrint("{s}.bind must match the signature fn ({s}) !{s}", .{
|
||||||
|
@typeName(@TypeOf(fn_param.type.?.bind)),
|
||||||
|
@typeName(*ona.App),
|
||||||
|
@typeName(void),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bind_fn.params.len != 1) {
|
||||||
|
@compileError(type_error_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind_fn.params[0].type.? != *ona.App) {
|
||||||
|
@compileError(type_error_message);
|
||||||
|
}
|
||||||
|
|
||||||
|
const binding = struct {
|
||||||
|
fn bind(app: *ona.App) void {
|
||||||
|
return switch (@typeInfo(bind_fn.return_type.?)) {
|
||||||
|
.error_union => coral.require(fn_param.type.?.bind, .{app}),
|
||||||
|
.void => fn_param.type.?.bind(app),
|
||||||
|
else => @compileError(type_error_message),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
param.has_bind = binding.bind;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (@hasDecl(fn_param.type.?, "traits") and @TypeOf(fn_param.type.?.traits) == ona.Traits) {
|
||||||
|
param.traits = fn_param.type.?.traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline for (param.init_params) |init_param| {
|
||||||
|
param.traits = param.traits.derived(init_param.traits);
|
||||||
|
}
|
||||||
|
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn after(comptime self: *const Self, comptime dependency: *const Self) *const Self {
|
||||||
|
const afters = struct {
|
||||||
|
fn on_insertion(behavior: *const Self, app: *ona.App, systems: *SystemGraph) std.mem.Allocator.Error!void {
|
||||||
|
try self.on_insertion(behavior, app, systems);
|
||||||
|
try systems.depend_on_behavior(app, behavior, dependency);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_run(app: *ona.App) void {
|
||||||
|
self.on_run(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = Self{
|
||||||
|
.label = std.fmt.comptimePrint("({s} after {s})", .{ self.label, dependency.label }),
|
||||||
|
.on_insertion = on_insertion,
|
||||||
|
.on_run = on_run,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return &afters.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn before(comptime self: *const Self, comptime dependant: *const Self) *const Self {
|
||||||
|
const afters = struct {
|
||||||
|
fn on_insertion(behavior: *const Self, app: *ona.App, systems: *SystemGraph) std.mem.Allocator.Error!void {
|
||||||
|
try systems.depend_on_behavior(app, dependant, behavior);
|
||||||
|
try systems.insert(app, behavior);
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = Self{
|
||||||
|
.label = std.fmt.comptimePrint("({s} before {s})", .{ self.label, dependant.label }),
|
||||||
|
.on_insertion = on_insertion,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return &afters.instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn of(comptime call: anytype) *const Self {
|
||||||
|
const Call = @TypeOf(call);
|
||||||
|
|
||||||
|
const call_fn = switch (@typeInfo(Call)) {
|
||||||
|
.@"fn" => |@"fn"| @"fn",
|
||||||
|
else => @compileError("`call` parameter must be an fn type"),
|
||||||
|
};
|
||||||
|
|
||||||
|
const behaviors = struct {
|
||||||
|
fn on_insertion(behavior: *const Self, app: *ona.App, systems: *SystemGraph) std.mem.Allocator.Error!void {
|
||||||
|
inline for (call_fn.params) |call_param| {
|
||||||
|
const behavior_param = Param.init(call_param);
|
||||||
|
|
||||||
|
inline for (behavior_param.init_params) |init_param| {
|
||||||
|
try systems.dependOnType(app, behavior, .{
|
||||||
|
.id = .of(init_param.type),
|
||||||
|
.is_read_only = init_param.is_read_only,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (behavior_param.has_bind) |bind| {
|
||||||
|
bind(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_run(app: *ona.App) void {
|
||||||
|
var call_args: std.meta.ArgsTuple(Call) = undefined;
|
||||||
|
|
||||||
|
inline for (&call_args, call_fn.params) |*call_arg, call_fn_param| {
|
||||||
|
const call_param = Param.init(call_fn_param);
|
||||||
|
var init_args: std.meta.ArgsTuple(@TypeOf(call_fn_param.type.?.init)) = undefined;
|
||||||
|
|
||||||
|
inline for (&init_args, call_param.init_params) |*init_arg, init_param| {
|
||||||
|
if (init_param.is_required) {
|
||||||
|
init_arg.* = app.hasState(init_param.type) orelse {
|
||||||
|
@panic(std.fmt.comptimePrint("{s} is a required state but not present in the App", .{@typeName(init_param.type)}));
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
init_arg.* = app.hasState(init_param.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
call_arg.* = @call(.auto, call_fn_param.type.?.init, init_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (@typeInfo(call_fn.return_type.?)) {
|
||||||
|
.void => @call(.auto, call, call_args),
|
||||||
|
.error_union => coral.require(call, call_args),
|
||||||
|
|
||||||
|
else => @compileError(std.fmt.comptimePrint("parameter `call` must be fn (*ona.App) void or fn (*ona.App) !void, not {s}", .{
|
||||||
|
@typeName(Call),
|
||||||
|
})),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const instance = Self{
|
||||||
|
.label = @typeName(Call),
|
||||||
|
.on_insertion = on_insertion,
|
||||||
|
.on_run = on_run,
|
||||||
|
|
||||||
|
.traits = derive: {
|
||||||
|
var derived_traits = ona.Traits{};
|
||||||
|
|
||||||
|
for (call_fn.params) |call_param| {
|
||||||
|
derived_traits = derived_traits.derived(Param.init(call_param).traits);
|
||||||
|
}
|
||||||
|
|
||||||
|
break :derive derived_traits;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return &behaviors.instance;
|
||||||
|
}
|
80
src/ona/App/Setup.zig
Normal file
80
src/ona/App/Setup.zig
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const ona = @import("../ona.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
action: fn (*ona.App) void,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn init(comptime start: anytype) Self {
|
||||||
|
const start_fn = switch (@typeInfo(@TypeOf(start))) {
|
||||||
|
.@"fn" => |@"fn"| @"fn",
|
||||||
|
else => @compileError("parameter `start` must be a function type"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (start_fn.params.len > 1 or (start_fn.params.len == 1 and start_fn.params[0].type != *ona.App)) {
|
||||||
|
@compileError(std.fmt.comptimePrint("parameter `start` must accept {s} as the one and only parameter, not {s}", .{
|
||||||
|
@typeName(*ona.World),
|
||||||
|
@typeName(start_fn.params[0].type.?),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
const application = struct {
|
||||||
|
fn apply(app: *ona.App) void {
|
||||||
|
const error_message = std.fmt.comptimePrint("parameter `start` must return void or !void, not {s}", .{
|
||||||
|
@typeName(start_fn.return_type.?),
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (@typeInfo(start_fn.return_type.?)) {
|
||||||
|
.error_union => |error_union| switch (error_union.payload) {
|
||||||
|
void => coral.require(start, .{app}),
|
||||||
|
else => @compileError(error_message),
|
||||||
|
},
|
||||||
|
|
||||||
|
.void => start(app),
|
||||||
|
else => @compileError(error_message),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.action = application.apply,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn initModule(comptime namespace: anytype) Self {
|
||||||
|
if (!@hasDecl(namespace, "setup")) {
|
||||||
|
@compileError("parameter `namespace` must contain a `setup` declaration");
|
||||||
|
}
|
||||||
|
|
||||||
|
return init(namespace.setup);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(self: Self) void {
|
||||||
|
defer {
|
||||||
|
coral.heap.traceLeaks();
|
||||||
|
}
|
||||||
|
|
||||||
|
var app = coral.require(ona.App.init, .{});
|
||||||
|
|
||||||
|
defer {
|
||||||
|
app.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.action(&app);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with(self: Self, comptime other: Self) Self {
|
||||||
|
const combination = struct {
|
||||||
|
fn apply(app: *ona.App) void {
|
||||||
|
other.action(app);
|
||||||
|
self.action(app);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.action = combination.apply,
|
||||||
|
};
|
||||||
|
}
|
189
src/ona/App/SystemGraph.zig
Normal file
189
src/ona/App/SystemGraph.zig
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const ona = @import("../ona.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
dependants_edges: Map(BehaviorSet),
|
||||||
|
processed: Map(void),
|
||||||
|
state_readers: AccessMap,
|
||||||
|
state_writers: AccessMap,
|
||||||
|
blocking_work: BehaviorSet,
|
||||||
|
parallel_work: BehaviorSet,
|
||||||
|
parallel_work_ranges: coral.Stack(usize),
|
||||||
|
|
||||||
|
const AccessMap = coral.map.Hashed(*const coral.TypeId, BehaviorSet, coral.map.ptrTraits(*const coral.TypeId));
|
||||||
|
|
||||||
|
const BehaviorSet = coral.Stack(*const ona.App.Behavior);
|
||||||
|
|
||||||
|
fn Map(comptime Payload: type) type {
|
||||||
|
return coral.map.Hashed(*const ona.App.Behavior, Payload, coral.map.ptrTraits(*const ona.App.Behavior));
|
||||||
|
}
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const RunError = error{
|
||||||
|
MissingDependency,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TypeDependency = struct {
|
||||||
|
id: *const coral.TypeId,
|
||||||
|
is_read_only: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.processed.deinit();
|
||||||
|
self.parallel_work.deinit();
|
||||||
|
self.parallel_work_ranges.deinit();
|
||||||
|
self.blocking_work.deinit();
|
||||||
|
|
||||||
|
inline for (.{ &self.dependants_edges, &self.state_readers, &self.state_writers }) |map| {
|
||||||
|
var entries = map.entries();
|
||||||
|
|
||||||
|
while (entries.next()) |entry| {
|
||||||
|
entry.value.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
map.deinit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dependOnBehavior(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: *const ona.App.Behavior) std.mem.Allocator.Error!void {
|
||||||
|
try self.insert(app, dependant);
|
||||||
|
|
||||||
|
const edges = self.dependants_edges.get(dependant).?;
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(*const ona.App.Behavior, edges.values, dependency) == null) {
|
||||||
|
try edges.pushGrow(dependency);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dependOnType(self: *Self, app: *ona.App, dependant: *const ona.App.Behavior, dependency: TypeDependency) std.mem.Allocator.Error!void {
|
||||||
|
var readers = self.state_readers.get(dependency.id) orelse (try self.state_readers.emplace(dependency.id, .empty)).?;
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(*const ona.App.Behavior, readers.values, dependant)) |index| {
|
||||||
|
for (readers.values[0..index]) |reader| {
|
||||||
|
try self.dependOnBehavior(app, dependant, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (readers.values[index + 1 ..]) |reader| {
|
||||||
|
try self.dependOnBehavior(app, dependant, reader);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (readers.values) |reader| {
|
||||||
|
try self.dependOnBehavior(app, dependant, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
try readers.pushGrow(dependant);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dependency.is_read_only) {
|
||||||
|
const writers = self.state_writers.get(dependency.id) orelse (try self.state_writers.emplace(dependency.id, .empty)).?;
|
||||||
|
|
||||||
|
if (std.mem.indexOfScalar(*const ona.App.Behavior, writers.values, dependant)) |index| {
|
||||||
|
for (writers.values[0..index]) |reader| {
|
||||||
|
try self.dependOnBehavior(app, dependant, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (writers.values[index..]) |reader| {
|
||||||
|
try self.dependOnBehavior(app, dependant, reader);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (writers.values) |reader| {
|
||||||
|
try self.dependOnBehavior(app, dependant, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
try writers.pushGrow(dependant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const empty = Self{
|
||||||
|
.processed = .empty,
|
||||||
|
.dependants_edges = .empty,
|
||||||
|
.state_readers = .empty,
|
||||||
|
.state_writers = .empty,
|
||||||
|
.blocking_work = .empty,
|
||||||
|
.parallel_work = .empty,
|
||||||
|
.parallel_work_ranges = .empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn insert(self: *Self, app: *ona.App, behavior: *const ona.App.Behavior) std.mem.Allocator.Error!void {
|
||||||
|
self.processed.clear();
|
||||||
|
|
||||||
|
if ((try self.dependants_edges.emplace(behavior, .empty)) != null) {
|
||||||
|
try behavior.on_insertion(behavior, app, self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parallelRun(app: *ona.App, behaviors: []const *const ona.App.Behavior) void {
|
||||||
|
for (behaviors) |behavior| {
|
||||||
|
behavior.on_run(app);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process(self: *Self, behavior: *const ona.App.Behavior, dependencies: BehaviorSet) !ona.Traits {
|
||||||
|
var inherited_traits = behavior.traits;
|
||||||
|
|
||||||
|
for (dependencies.values) |dependency| {
|
||||||
|
const traits = try self.process(dependency, (self.dependants_edges.get(dependency) orelse {
|
||||||
|
return error.MissingDependency;
|
||||||
|
}).*);
|
||||||
|
|
||||||
|
inherited_traits = inherited_traits.derived(traits);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try self.processed.emplace(behavior, {})) {
|
||||||
|
try switch (inherited_traits.is_thread_unsafe) {
|
||||||
|
true => self.blocking_work.pushGrow(behavior),
|
||||||
|
false => self.parallel_work.pushGrow(behavior),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return inherited_traits;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(self: *Self, app: *ona.App, tasks: *coral.asio.TaskQueue) (std.mem.Allocator.Error || RunError)!void {
|
||||||
|
if (self.processed.isEmpty()) {
|
||||||
|
errdefer {
|
||||||
|
self.processed.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.blocking_work.clear();
|
||||||
|
self.parallel_work.clear();
|
||||||
|
self.parallel_work_ranges.clear();
|
||||||
|
|
||||||
|
var dependents_edges = self.dependants_edges.entries();
|
||||||
|
|
||||||
|
while (dependents_edges.next()) |entry| {
|
||||||
|
const parallel_work_offset = self.parallel_work.values.len;
|
||||||
|
const flags = try self.process(entry.key, entry.value);
|
||||||
|
|
||||||
|
if (flags.is_thread_unsafe) {
|
||||||
|
std.debug.assert(parallel_work_offset == self.parallel_work.values.len);
|
||||||
|
} else {
|
||||||
|
const parallel_work_added = self.parallel_work.values.len - parallel_work_offset;
|
||||||
|
|
||||||
|
if (parallel_work_added != 0) {
|
||||||
|
try self.parallel_work_ranges.pushGrow(parallel_work_added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var parallel_work_offset: usize = 0;
|
||||||
|
|
||||||
|
for (self.parallel_work_ranges.values) |parallel_work_range| {
|
||||||
|
const work = self.parallel_work.values[parallel_work_offset .. parallel_work_offset + parallel_work_range];
|
||||||
|
|
||||||
|
try tasks.execute(coral.asio.CallTask(parallelRun).init(.{ app, work }));
|
||||||
|
|
||||||
|
parallel_work_offset += parallel_work_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.finish();
|
||||||
|
|
||||||
|
for (self.blocking_work.values) |behavior| {
|
||||||
|
behavior.on_run(app);
|
||||||
|
}
|
||||||
|
}
|
133
src/ona/Assets.zig
Normal file
133
src/ona/Assets.zig
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const ext = @import("ext");
|
||||||
|
|
||||||
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
storage: *ext.SDL_Storage,
|
||||||
|
|
||||||
|
pub const Error = error{
|
||||||
|
AssetNotFound,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Instance = struct {
|
||||||
|
storage: *ext.SDL_Storage,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Instance) void {
|
||||||
|
while (!ext.SDL_StorageReady(self.storage)) {
|
||||||
|
ext.SDL_Delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ext.SDL_CloseStorage(self.storage)) {
|
||||||
|
std.log.warn("Failed to close title storage", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.storage = undefined;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn bind(app: *ona.App) !void {
|
||||||
|
const title_storage = ext.SDL_OpenTitleStorage(null, 0) orelse {
|
||||||
|
std.log.err("Failed to open title storage", .{});
|
||||||
|
|
||||||
|
return error.SdlFailure;
|
||||||
|
};
|
||||||
|
|
||||||
|
try app.setState(Instance{
|
||||||
|
.storage = title_storage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(instance: *Instance) Self {
|
||||||
|
return .{
|
||||||
|
.storage = instance.storage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load(self: Self, path: []const u8, allocator: std.mem.Allocator) (std.mem.Allocator.Error || Error)![]u8 {
|
||||||
|
const path_buffer = try normalize_path(path);
|
||||||
|
|
||||||
|
while (!ext.SDL_StorageReady(self.storage)) {
|
||||||
|
ext.SDL_Delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
var asset_size: u64 = 0;
|
||||||
|
|
||||||
|
if (!ext.SDL_GetStorageFileSize(self.storage, path_buffer.ptr(), &asset_size)) {
|
||||||
|
return error.AssetNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
const file_buffer = try allocator.alloc(u8, asset_size);
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
allocator.free(file_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ext.SDL_ReadStorageFile(self.storage, path_buffer.ptr(), file_buffer.ptr, file_buffer.len)) {
|
||||||
|
return error.AssetNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
return file_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn normalize_path(data: []const u8) Error!coral.ShortString {
|
||||||
|
const separator = '/';
|
||||||
|
var result = coral.ShortString.empty;
|
||||||
|
|
||||||
|
for (0..data.len) |i| {
|
||||||
|
if (data[i] == separator) {
|
||||||
|
const next_i = i + 1;
|
||||||
|
|
||||||
|
if (next_i == data.len) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[next_i] == separator) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.append(data[i]) catch |append_error| {
|
||||||
|
return switch (append_error) {
|
||||||
|
error.StringTooLong => error.AssetNotFound,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
return error.AssetNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(self: Self, path: []const u8, output: []u8) Error!void {
|
||||||
|
const path_buffer = try normalize_path(path);
|
||||||
|
|
||||||
|
while (!ext.SDL_StorageReady(self.storage)) {
|
||||||
|
ext.SDL_Delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ext.SDL_ReadStorageFile(self.storage, path_buffer.ptr(), output.ptr, output.len)) {
|
||||||
|
return error.AssetNotFound;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(self: Self, path: []const u8) Error!usize {
|
||||||
|
const path_buffer = try normalize_path(path);
|
||||||
|
var asset_size: u64 = 0;
|
||||||
|
|
||||||
|
while (!ext.SDL_StorageReady(self.storage)) {
|
||||||
|
ext.SDL_Delay(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ext.SDL_GetStorageFileSize(self.storage, path_buffer.ptr(), &asset_size)) {
|
||||||
|
return error.AssetNotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
return asset_size;
|
||||||
|
}
|
@ -1,629 +0,0 @@
|
|||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
thread_pool: ?*std.Thread.Pool = null,
|
|
||||||
thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable,
|
|
||||||
event_schedules: ona.map.Hashed([]const u8, *Schedule, ona.map.string_traits),
|
|
||||||
|
|
||||||
pub const BindContext = struct {
|
|
||||||
node: ona.dag.Node,
|
|
||||||
systems: *Schedule,
|
|
||||||
world: *Self,
|
|
||||||
|
|
||||||
pub fn accesses_state(self: BindContext, access: std.meta.Tag(StateAccess), id: ona.TypeID) bool {
|
|
||||||
const resource_accesses = &self.systems.graph.get(self.node).?.resource_accesses;
|
|
||||||
|
|
||||||
for (resource_accesses.values) |resource_access| {
|
|
||||||
switch (resource_access) {
|
|
||||||
.read_only => |resource_id| {
|
|
||||||
if (access == .read_only and resource_id == id) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.read_write => |resource_id| {
|
|
||||||
if (access == .read_write and resource_id == id) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_writable_state_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*Resource {
|
|
||||||
const value = self.world.get_state(Resource) orelse {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const id = ona.type_id(Resource);
|
|
||||||
|
|
||||||
if (!self.accesses_state(.read_write, id)) {
|
|
||||||
try self.systems.graph.get(self.node).?.resource_accesses.push_grow(.{.read_write = id});
|
|
||||||
}
|
|
||||||
|
|
||||||
const read_write_resource_nodes = lazily_create: {
|
|
||||||
break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: {
|
|
||||||
std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
|
|
||||||
.allocator = ona.heap.allocator,
|
|
||||||
}));
|
|
||||||
|
|
||||||
break: insert self.systems.read_write_resource_id_nodes.get(id).?;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if (std.mem.indexOfScalar(ona.dag.Node, read_write_resource_nodes.values, self.node) == null) {
|
|
||||||
try read_write_resource_nodes.push_grow(self.node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn register_readable_state_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*const Resource {
|
|
||||||
const value = self.world.get_state(Resource) orelse {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const id = ona.type_id(Resource);
|
|
||||||
|
|
||||||
if (!self.accesses_state(.read_only, id)) {
|
|
||||||
try self.systems.graph.get(self.node).?.resource_accesses.push_grow(.{.read_only = id});
|
|
||||||
}
|
|
||||||
|
|
||||||
const read_only_resource_nodes = lazily_create: {
|
|
||||||
break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: {
|
|
||||||
std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
|
|
||||||
.allocator = ona.heap.allocator,
|
|
||||||
}));
|
|
||||||
|
|
||||||
break: insert self.systems.read_only_resource_id_nodes.get(id).?;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
if (std.mem.indexOfScalar(ona.dag.Node, read_only_resource_nodes.values, self.node) == null) {
|
|
||||||
try read_only_resource_nodes.push_grow(self.node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Dependency = struct {
|
|
||||||
kind: Kind,
|
|
||||||
id: usize,
|
|
||||||
|
|
||||||
const Kind = enum {
|
|
||||||
after,
|
|
||||||
before,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const Graph = ona.dag.Graph(struct {
|
|
||||||
info: *const ona.SystemInfo,
|
|
||||||
label: [:0]u8,
|
|
||||||
dependencies: []Dependency,
|
|
||||||
parameter_states: [ona.SystemInfo.max_parameters]*anyopaque = [_]*anyopaque{undefined} ** ona.SystemInfo.max_parameters,
|
|
||||||
resource_accesses: ona.stack.Sequential(StateAccess),
|
|
||||||
});
|
|
||||||
|
|
||||||
const ParallelNodeBundles = ona.stack.Sequential(NodeBundle);
|
|
||||||
|
|
||||||
const NodeBundle = ona.stack.Sequential(ona.dag.Node);
|
|
||||||
|
|
||||||
const StateAccess = union (enum) {
|
|
||||||
read_only: ona.TypeID,
|
|
||||||
read_write: ona.TypeID,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ResourceNodeBundle = ona.map.Hashed(ona.TypeID, NodeBundle, ona.map.enum_traits(ona.TypeID));
|
|
||||||
|
|
||||||
const Schedule = struct {
|
|
||||||
label: [:0]const u8,
|
|
||||||
graph: Graph,
|
|
||||||
arena: std.heap.ArenaAllocator,
|
|
||||||
system_id_nodes: ona.map.Hashed(usize, NodeBundle, ona.map.usize_traits),
|
|
||||||
inserted_systems: ona.stack.Sequential(InsertedSystem) = .{},
|
|
||||||
read_write_resource_id_nodes: ResourceNodeBundle,
|
|
||||||
read_only_resource_id_nodes: ResourceNodeBundle,
|
|
||||||
parallel_work_bundles: ParallelNodeBundles,
|
|
||||||
blocking_work: NodeBundle,
|
|
||||||
|
|
||||||
const InsertedSystem = struct {
|
|
||||||
info: *const ona.SystemInfo,
|
|
||||||
order: ona.SystemOrder,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn deinit(self: *Schedule, world: *Self) void {
|
|
||||||
{
|
|
||||||
var nodes = self.system_id_nodes.entries();
|
|
||||||
|
|
||||||
while (nodes.next()) |node| {
|
|
||||||
node.value.deinit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var nodes = self.read_write_resource_id_nodes.entries();
|
|
||||||
|
|
||||||
while (nodes.next()) |node| {
|
|
||||||
node.value.deinit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
var nodes = self.read_only_resource_id_nodes.entries();
|
|
||||||
|
|
||||||
while (nodes.next()) |node| {
|
|
||||||
node.value.deinit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var nodes = self.graph.nodes();
|
|
||||||
|
|
||||||
while (nodes.next()) |node| {
|
|
||||||
const system = self.graph.get(node).?;
|
|
||||||
|
|
||||||
for (system.info.used_parameters(), system.parameter_states[0 .. system.info.parameter_count]) |parameter, state| {
|
|
||||||
parameter.unbind(self.arena.allocator(), state, .{
|
|
||||||
.world = world,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
system.resource_accesses.deinit();
|
|
||||||
ona.heap.allocator.free(system.dependencies);
|
|
||||||
ona.heap.allocator.free(system.label);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (self.parallel_work_bundles.values) |*bundle| {
|
|
||||||
bundle.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.parallel_work_bundles.deinit();
|
|
||||||
self.blocking_work.deinit();
|
|
||||||
self.graph.deinit();
|
|
||||||
self.system_id_nodes.deinit();
|
|
||||||
self.read_write_resource_id_nodes.deinit();
|
|
||||||
self.read_only_resource_id_nodes.deinit();
|
|
||||||
self.inserted_systems.deinit();
|
|
||||||
self.arena.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run(self: *Schedule, world: *Self) anyerror!void {
|
|
||||||
if (self.is_invalidated()) {
|
|
||||||
const work = struct {
|
|
||||||
fn bind_systems(schedule: *Schedule, world_: *Self) !void {
|
|
||||||
for (schedule.inserted_systems.values) |system| {
|
|
||||||
const nodes = lazily_create: {
|
|
||||||
const system_id = @intFromPtr(system.info);
|
|
||||||
|
|
||||||
break: lazily_create schedule.system_id_nodes.get(system_id) orelse insert: {
|
|
||||||
std.debug.assert(try schedule.system_id_nodes.emplace(system_id, .{
|
|
||||||
.allocator = schedule.system_id_nodes.allocator,
|
|
||||||
}));
|
|
||||||
|
|
||||||
break: insert schedule.system_id_nodes.get(system_id).?;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const dependencies = init: {
|
|
||||||
const total_run_orders = system.order.run_after.len + system.order.run_before.len;
|
|
||||||
const dependencies = try ona.heap.allocator.alloc(Dependency, total_run_orders);
|
|
||||||
var dependencies_written = @as(usize, 0);
|
|
||||||
|
|
||||||
for (system.order.run_after) |after_system| {
|
|
||||||
dependencies[dependencies_written] = .{
|
|
||||||
.id = @intFromPtr(after_system),
|
|
||||||
.kind = .after,
|
|
||||||
};
|
|
||||||
|
|
||||||
dependencies_written += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (system.order.run_before) |before_system| {
|
|
||||||
dependencies[dependencies_written] = .{
|
|
||||||
.id = @intFromPtr(before_system),
|
|
||||||
.kind = .before,
|
|
||||||
};
|
|
||||||
|
|
||||||
dependencies_written += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
break: init dependencies;
|
|
||||||
};
|
|
||||||
|
|
||||||
errdefer ona.heap.allocator.free(dependencies);
|
|
||||||
|
|
||||||
const label = try ona.heap.allocator.dupeZ(u8, if (system.order.label.len == 0) "anonymous system" else system.order.label);
|
|
||||||
|
|
||||||
errdefer ona.heap.allocator.free(label);
|
|
||||||
|
|
||||||
const node = try schedule.graph.append(.{
|
|
||||||
.info = system.info,
|
|
||||||
.label = label,
|
|
||||||
.dependencies = dependencies,
|
|
||||||
.resource_accesses = .{.allocator = ona.heap.allocator},
|
|
||||||
});
|
|
||||||
|
|
||||||
const system_node = schedule.graph.get(node).?;
|
|
||||||
const system_parameter_states = system_node.parameter_states[0 .. system.info.parameter_count];
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
for (system.info.used_parameters(), system_parameter_states) |parameter, state| {
|
|
||||||
parameter.unbind(schedule.arena.allocator(), state, .{
|
|
||||||
.world = world_,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
std.debug.assert(schedule.graph.remove_node(node) != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (system_parameter_states, system.info.used_parameters()) |*state, parameter| {
|
|
||||||
state.* = try parameter.bind(schedule.arena.allocator(), .{
|
|
||||||
.world = world_,
|
|
||||||
.node = node,
|
|
||||||
.systems = schedule,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try nodes.push_grow(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn regenerate_graph(schedule: *Schedule) !void {
|
|
||||||
schedule.graph.clear_edges();
|
|
||||||
|
|
||||||
var nodes = schedule.graph.nodes();
|
|
||||||
|
|
||||||
while (nodes.next()) |node| {
|
|
||||||
const system = schedule.graph.get(node).?;
|
|
||||||
|
|
||||||
for (system.dependencies) |order| {
|
|
||||||
const dependencies = schedule.system_id_nodes.get(@intFromPtr(system.info)) orelse {
|
|
||||||
@panic("unable to resolve missing explicit system dependency");
|
|
||||||
};
|
|
||||||
|
|
||||||
if (dependencies.is_empty()) {
|
|
||||||
@panic("unable to resolve missing explicit system dependency");
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (order.kind) {
|
|
||||||
.after => {
|
|
||||||
for (dependencies.values) |dependency_node| {
|
|
||||||
std.debug.assert(try schedule.graph.insert_edge(node, dependency_node));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.before => {
|
|
||||||
for (dependencies.values) |dependency_node| {
|
|
||||||
std.debug.assert(try schedule.graph.insert_edge(dependency_node, node));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (system.resource_accesses.values) |resource_access| {
|
|
||||||
switch (resource_access) {
|
|
||||||
.read_write => |resource_id| {
|
|
||||||
const read_write_dependencies = schedule.read_write_resource_id_nodes.get(resource_id) orelse {
|
|
||||||
@panic("unable to resolve missing implicit read-write parameter dependency");
|
|
||||||
};
|
|
||||||
|
|
||||||
for (read_write_dependencies.values) |dependency_node| {
|
|
||||||
std.debug.assert(try schedule.graph.insert_edge(node, dependency_node));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (schedule.read_only_resource_id_nodes.get(resource_id)) |dependencies| {
|
|
||||||
for (dependencies.values) |dependency_node| {
|
|
||||||
std.debug.assert(try schedule.graph.insert_edge(node, dependency_node));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.read_only => |resource_id| {
|
|
||||||
if (schedule.read_only_resource_id_nodes.get(resource_id)) |dependencies| {
|
|
||||||
for (dependencies.values) |dependency_node| {
|
|
||||||
std.debug.assert(try schedule.graph.insert_edge(node, dependency_node));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: ona.dag.Node) !void {
|
|
||||||
std.debug.assert(graph.mark_visited(node));
|
|
||||||
|
|
||||||
for (graph.edge_nodes(node).?) |edge| {
|
|
||||||
if (graph.visited(edge).?) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try populate_bundle(bundle, graph, edge);
|
|
||||||
}
|
|
||||||
|
|
||||||
try bundle.push_grow(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sort(schedule: *Schedule) !void {
|
|
||||||
defer schedule.graph.reset_visited();
|
|
||||||
|
|
||||||
var nodes = schedule.graph.nodes();
|
|
||||||
|
|
||||||
while (nodes.next()) |node| {
|
|
||||||
if (schedule.graph.visited(node).?) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
try schedule.parallel_work_bundles.push_grow(.{.allocator = ona.heap.allocator});
|
|
||||||
|
|
||||||
const bundle = schedule.parallel_work_bundles.get().?;
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
bundle.deinit();
|
|
||||||
std.debug.assert(schedule.parallel_work_bundles.pop() != null);
|
|
||||||
}
|
|
||||||
|
|
||||||
try populate_bundle(bundle, &schedule.graph, node);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (schedule.parallel_work_bundles.values) |*work| {
|
|
||||||
var index = @as(usize, 0);
|
|
||||||
|
|
||||||
while (index < work.len()) : (index += 1) {
|
|
||||||
const node = work.values[index];
|
|
||||||
|
|
||||||
switch (schedule.graph.get(node).?.info.thread_restriction) {
|
|
||||||
.none => continue,
|
|
||||||
|
|
||||||
.main => {
|
|
||||||
const extracted_work = work.values[index ..];
|
|
||||||
|
|
||||||
try schedule.blocking_work.grow(extracted_work.len);
|
|
||||||
|
|
||||||
std.debug.assert(schedule.blocking_work.push_all(extracted_work));
|
|
||||||
std.debug.assert(work.pop_many(extracted_work.len));
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try work.bind_systems(self, world);
|
|
||||||
try work.regenerate_graph(self);
|
|
||||||
try work.sort(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Refactor so the thread pool is a global resource rather than owned per-world.
|
|
||||||
if (world.thread_pool) |thread_pool| {
|
|
||||||
const parallel = struct {
|
|
||||||
fn run(work_group: *std.Thread.WaitGroup, graph: Graph, bundle: NodeBundle) void {
|
|
||||||
defer work_group.finish();
|
|
||||||
|
|
||||||
for (bundle.values) |node| {
|
|
||||||
const system = graph.get(node).?;
|
|
||||||
|
|
||||||
// TODO: std lib thread pool sucks for many reasons and this is one of them.
|
|
||||||
system.info.execute(system.info.used_parameters(), &system.parameter_states) catch unreachable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var work_group = std.Thread.WaitGroup{};
|
|
||||||
|
|
||||||
for (self.parallel_work_bundles.values) |bundle| {
|
|
||||||
work_group.start();
|
|
||||||
|
|
||||||
try thread_pool.spawn(parallel.run, .{&work_group, self.graph, bundle});
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_pool.waitAndWork(&work_group);
|
|
||||||
} else {
|
|
||||||
for (self.parallel_work_bundles.values) |bundle| {
|
|
||||||
for (bundle.values) |node| {
|
|
||||||
const system = self.graph.get(node).?;
|
|
||||||
|
|
||||||
try system.info.execute(system.info.used_parameters(), &system.parameter_states);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (self.blocking_work.values) |node| {
|
|
||||||
const system = self.graph.get(node).?;
|
|
||||||
|
|
||||||
try system.info.execute(system.info.used_parameters(), &system.parameter_states);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(comptime label: [:0]const u8) std.mem.Allocator.Error!Schedule {
|
|
||||||
return .{
|
|
||||||
.graph = Graph.init(ona.heap.allocator),
|
|
||||||
.arena = std.heap.ArenaAllocator.init(ona.heap.allocator),
|
|
||||||
.label = label,
|
|
||||||
.system_id_nodes = .{},
|
|
||||||
.read_write_resource_id_nodes = .{},
|
|
||||||
.read_only_resource_id_nodes = .{},
|
|
||||||
.parallel_work_bundles = .{},
|
|
||||||
.blocking_work = .{},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(self: *Schedule, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
|
|
||||||
try self.inserted_systems.push_grow(.{
|
|
||||||
.info = info,
|
|
||||||
.order = order,
|
|
||||||
});
|
|
||||||
|
|
||||||
self.blocking_work.clear();
|
|
||||||
|
|
||||||
for (self.parallel_work_bundles.values) |*bundle| {
|
|
||||||
bundle.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.parallel_work_bundles.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_invalidated(self: Schedule) bool {
|
|
||||||
return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const StateTable = struct {
|
|
||||||
arena: std.heap.ArenaAllocator,
|
|
||||||
table: ona.map.Hashed(ona.TypeID, Entry, ona.map.enum_traits(ona.TypeID)),
|
|
||||||
|
|
||||||
const Entry = struct {
|
|
||||||
ptr: *anyopaque,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn deinit(self: *StateTable) void {
|
|
||||||
self.table.deinit();
|
|
||||||
self.arena.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get(self: StateTable, comptime Resource: type) ?*Resource {
|
|
||||||
if (self.table.get(ona.type_id(Resource))) |entry| {
|
|
||||||
return @ptrCast(@alignCast(entry.ptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init() StateTable {
|
|
||||||
return .{
|
|
||||||
.arena = std.heap.ArenaAllocator.init(ona.heap.allocator),
|
|
||||||
.table = .{.allocator = ona.heap.allocator},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_get(self: *StateTable, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
|
|
||||||
try self.set(value);
|
|
||||||
|
|
||||||
return self.get(@TypeOf(value)).?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set(self: *StateTable, value: anytype) std.mem.Allocator.Error!void {
|
|
||||||
const Value = @TypeOf(value);
|
|
||||||
const value_id = ona.type_id(Value);
|
|
||||||
|
|
||||||
if (self.table.get(value_id)) |entry| {
|
|
||||||
@as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value;
|
|
||||||
} else {
|
|
||||||
const resource_allocator = self.arena.allocator();
|
|
||||||
const allocated_resource = try resource_allocator.create(Value);
|
|
||||||
|
|
||||||
errdefer resource_allocator.destroy(allocated_resource);
|
|
||||||
|
|
||||||
std.debug.assert(try self.table.emplace(value_id, .{
|
|
||||||
.ptr = allocated_resource,
|
|
||||||
}));
|
|
||||||
|
|
||||||
allocated_resource.* = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const UnbindContext = struct {
|
|
||||||
world: *Self,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
var event_schedules = self.event_schedules.entries();
|
|
||||||
|
|
||||||
while (event_schedules.next()) |schedule| {
|
|
||||||
schedule.value.deinit(self);
|
|
||||||
ona.heap.allocator.destroy(schedule.value);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (&self.thread_restricted_resources) |*resources| {
|
|
||||||
resources.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.thread_pool) |thread_pool| {
|
|
||||||
thread_pool.deinit();
|
|
||||||
ona.heap.allocator.destroy(thread_pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.event_schedules.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_params(self: Self, comptime Value: type) ona.Params(Value) {
|
|
||||||
const params = self.get_state(ona.Params(Value)) orelse {
|
|
||||||
return .{};
|
|
||||||
};
|
|
||||||
|
|
||||||
return params.*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_state(self: Self, comptime Value: type) ?*Value {
|
|
||||||
return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(Value))].get(Value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
|
|
||||||
return self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(@TypeOf(value)))].set_get(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
|
|
||||||
var world = Self{
|
|
||||||
.thread_restricted_resources = .{StateTable.init(), StateTable.init()},
|
|
||||||
.event_schedules = .{.allocator = ona.heap.allocator},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (thread_count != 0 and !builtin.single_threaded) {
|
|
||||||
const thread_pool = try ona.heap.allocator.create(std.Thread.Pool);
|
|
||||||
|
|
||||||
try thread_pool.init(.{
|
|
||||||
.allocator = ona.heap.allocator,
|
|
||||||
.n_jobs = thread_count,
|
|
||||||
});
|
|
||||||
|
|
||||||
world.thread_pool = thread_pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
return world;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_event(self: *Self, comptime event: anytype, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
|
|
||||||
const event_name = @tagName(event);
|
|
||||||
|
|
||||||
if (self.event_schedules.get(event_name)) |schedule| {
|
|
||||||
try schedule.*.insert(info, order);
|
|
||||||
} else {
|
|
||||||
const schedule = try ona.heap.allocator.create(Schedule);
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
ona.heap.allocator.destroy(schedule);
|
|
||||||
}
|
|
||||||
|
|
||||||
schedule.* = try Schedule.init(event_name);
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
schedule.deinit(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
try schedule.insert(info, order);
|
|
||||||
|
|
||||||
std.debug.assert(try self.event_schedules.emplace(event_name, schedule));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_event(self: *Self, comptime event: anytype) anyerror!void {
|
|
||||||
if (self.event_schedules.get(@tagName(event))) |schedule| {
|
|
||||||
try schedule.*.run(self);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void {
|
|
||||||
try self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(@TypeOf(value)))].set(value);
|
|
||||||
}
|
|
@ -1,192 +0,0 @@
|
|||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const DecimalFormat = struct {
|
|
||||||
delimiter: []const u8 = "",
|
|
||||||
positive_prefix: enum {none, plus, space} = .none,
|
|
||||||
|
|
||||||
pub fn parse(self: DecimalFormat, utf8: []const u8, comptime Decimal: type) ?Decimal {
|
|
||||||
if (utf8.len == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (@typeInfo(Decimal)) {
|
|
||||||
.Int => |int| {
|
|
||||||
const has_sign = switch (utf8[0]) {
|
|
||||||
'-', '+', ' ' => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
var result = @as(Decimal, 0);
|
|
||||||
|
|
||||||
for (@intFromBool(has_sign) .. utf8.len) |index| {
|
|
||||||
const radix = 10;
|
|
||||||
const code = utf8[index];
|
|
||||||
|
|
||||||
switch (code) {
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
|
||||||
const offset_code = ona.scalars.sub(code, '0') orelse return null;
|
|
||||||
|
|
||||||
result = ona.scalars.mul(result, radix) orelse return null;
|
|
||||||
result = ona.scalars.add(result, offset_code) orelse return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
if (self.delimiter.len == 0 or !std.mem.eql(u8, self.delimiter, utf8[index ..])) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (int.signedness) {
|
|
||||||
.signed => {
|
|
||||||
return result * @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1);
|
|
||||||
},
|
|
||||||
|
|
||||||
.unsigned => {
|
|
||||||
if (has_sign and utf8[0] == '-') {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
.Float => {
|
|
||||||
const has_sign = switch (utf8[0]) {
|
|
||||||
'-', '+', ' ' => true,
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
// "-"
|
|
||||||
if (has_sign and utf8.len == 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sign_offset = @intFromBool(has_sign);
|
|
||||||
var has_decimal = utf8[sign_offset] == '.';
|
|
||||||
|
|
||||||
// "-."
|
|
||||||
if (has_decimal and (utf8.len == 2)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = @as(Decimal, 0);
|
|
||||||
var factor = @as(Decimal, if (has_sign and utf8[0] == '-') -1 else 1);
|
|
||||||
|
|
||||||
for (utf8[sign_offset + @intFromBool(has_decimal) .. utf8.len]) |code| {
|
|
||||||
switch (code) {
|
|
||||||
'.' => {
|
|
||||||
if (has_decimal) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
has_decimal = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
|
||||||
if (has_decimal) {
|
|
||||||
factor /= 10.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
result = ((result * 10.0) + @as(Decimal, @floatFromInt(code - '0')));
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return null,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result * factor;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => @compileError("`" ++ @typeName(Decimal) ++ "` cannot be parsed from a decimal string"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format(self: DecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void {
|
|
||||||
if (value == 0) {
|
|
||||||
return ona.io.write_all(writer, switch (self.positive_prefix) {
|
|
||||||
.none => "0",
|
|
||||||
.plus => "+0",
|
|
||||||
.space => " 0",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const Value = @TypeOf(value);
|
|
||||||
|
|
||||||
switch (@typeInfo(Value)) {
|
|
||||||
.Int => |int| {
|
|
||||||
const radix = 10;
|
|
||||||
var buffer = [_]u8{0} ** (1 + @max(int.bits, 1));
|
|
||||||
var buffer_start = buffer.len - 1;
|
|
||||||
|
|
||||||
{
|
|
||||||
var decomposable_value = value;
|
|
||||||
|
|
||||||
while (decomposable_value != 0) : (buffer_start -= 1) {
|
|
||||||
buffer[buffer_start] = @intCast(@mod(decomposable_value, radix) + '0');
|
|
||||||
decomposable_value = @divTrunc(decomposable_value, radix);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (int.signedness == .unsigned and value < 0) {
|
|
||||||
buffer[buffer_start] = '-';
|
|
||||||
} else {
|
|
||||||
switch (self.positive_prefix) {
|
|
||||||
.none => buffer_start += 1,
|
|
||||||
.plus => buffer[buffer_start] = '+',
|
|
||||||
.space => buffer[buffer_start] = ' ',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ona.io.write_all(writer, buffer[buffer_start ..]);
|
|
||||||
},
|
|
||||||
|
|
||||||
.Float => |float| {
|
|
||||||
if (value < 0) {
|
|
||||||
try ona.io.write_all(writer, "-");
|
|
||||||
}
|
|
||||||
|
|
||||||
const Float = @TypeOf(value);
|
|
||||||
const Int = std.meta.Int(.unsigned, float.bits);
|
|
||||||
const integer = @as(Int, @intFromFloat(value));
|
|
||||||
|
|
||||||
try self.format(writer, integer);
|
|
||||||
try ona.io.write_all(writer, ".");
|
|
||||||
try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
|
|
||||||
},
|
|
||||||
|
|
||||||
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be formatted to a decimal string"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
test "decimal parsing" {
|
|
||||||
const format = DecimalFormat{};
|
|
||||||
|
|
||||||
try std.testing.expectEqual(format.parse("69", i64), 69);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const HexadecimalFormat = struct {
|
|
||||||
delimiter: []const u8 = "",
|
|
||||||
positive_prefix: enum {none, plus, space} = .none,
|
|
||||||
casing: enum {lower, upper} = .lower,
|
|
||||||
|
|
||||||
pub const default = HexadecimalFormat{
|
|
||||||
.delimiter = "",
|
|
||||||
.positive_prefix = .none,
|
|
||||||
.casing = .lower,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn format(self: HexadecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void {
|
|
||||||
// TODO: Implement.
|
|
||||||
_ = self;
|
|
||||||
_ = writer;
|
|
||||||
_ = value;
|
|
||||||
|
|
||||||
unreachable;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,76 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn BlockingQueue(comptime max: usize, comptime T: type) type {
|
|
||||||
return struct {
|
|
||||||
buffer: [max]T = undefined,
|
|
||||||
head: usize = 0,
|
|
||||||
tail: usize = 0,
|
|
||||||
mutex: std.Thread.Mutex = .{},
|
|
||||||
not_empty: std.Thread.Condition = .{},
|
|
||||||
not_full: std.Thread.Condition = .{},
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn enqueue(self: *Self, item: T) void {
|
|
||||||
self.mutex.lock();
|
|
||||||
|
|
||||||
defer self.mutex.unlock();
|
|
||||||
|
|
||||||
while (((self.tail + 1) % max) == self.head) {
|
|
||||||
self.not_full.wait(&self.mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.buffer[self.tail] = item;
|
|
||||||
self.tail = (self.tail + 1) % max;
|
|
||||||
|
|
||||||
self.not_empty.signal();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dequeue(self: *Self) T {
|
|
||||||
self.mutex.lock();
|
|
||||||
|
|
||||||
defer self.mutex.unlock();
|
|
||||||
|
|
||||||
while (self.head == self.tail) {
|
|
||||||
self.not_empty.wait(&self.mutex);
|
|
||||||
}
|
|
||||||
|
|
||||||
const item = self.buffer[self.head];
|
|
||||||
|
|
||||||
self.buffer[self.head] = undefined;
|
|
||||||
self.head = (self.head + 1) % max;
|
|
||||||
|
|
||||||
self.not_full.signal();
|
|
||||||
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Future(comptime T: type) type {
|
|
||||||
return struct {
|
|
||||||
value: ?T = null,
|
|
||||||
resolved: std.Thread.ResetEvent = .{},
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn get(self: *Self) *T {
|
|
||||||
// TODO: Make async.
|
|
||||||
self.resolved.wait();
|
|
||||||
|
|
||||||
return &self.value.?;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resolve(self: *Self, value: T) bool {
|
|
||||||
if (self.resolved.isSet()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.value = value;
|
|
||||||
|
|
||||||
self.resolved.set();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
160
src/ona/dag.zig
160
src/ona/dag.zig
@ -1,160 +0,0 @@
|
|||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn Graph(comptime Payload: type) type {
|
|
||||||
return struct {
|
|
||||||
node_count: usize = 0,
|
|
||||||
table: NodeTables,
|
|
||||||
|
|
||||||
const NodeTables = ona.stack.Parallel(struct {
|
|
||||||
payload: Payload,
|
|
||||||
edges: ona.stack.Sequential(Node),
|
|
||||||
is_occupied: bool = true,
|
|
||||||
is_visited: bool = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn append(self: *Self, payload: Payload) std.mem.Allocator.Error!Node {
|
|
||||||
const node = @as(Node, @enumFromInt(self.table.len()));
|
|
||||||
|
|
||||||
try self.table.push_grow(.{
|
|
||||||
.payload = payload,
|
|
||||||
.edges = .{.allocator = self.table.allocator},
|
|
||||||
});
|
|
||||||
|
|
||||||
self.node_count += 1;
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear_edges(self: *Self) void {
|
|
||||||
for (self.table.values.get(.edges)) |*edges| {
|
|
||||||
edges.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
for (self.table.values.get(.edges)) |*edges| {
|
|
||||||
edges.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.table.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn edge_nodes(self: Self, node: Node) ?[]const Node {
|
|
||||||
if (!self.exists(node)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.table.values.get(.edges)[@intFromEnum(node)].values;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn exists(self: Self, node: Node) bool {
|
|
||||||
const node_index = @intFromEnum(node);
|
|
||||||
|
|
||||||
if (node_index >= self.table.values.len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.table.values.get(.is_occupied)[node_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: Self, node: Node) ?*Payload {
|
|
||||||
if (!self.exists(node)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return &self.table.values.get(.payload)[@intFromEnum(node)];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) Self {
|
|
||||||
return .{
|
|
||||||
.table = .{.allocator = allocator},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_edge(self: *Self, dependant_node: Node, edge_node: Node) std.mem.Allocator.Error!bool {
|
|
||||||
if (!self.exists(edge_node) or !self.exists(dependant_node)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const edges = &self.table.values.get(.edges)[@intFromEnum(dependant_node)];
|
|
||||||
|
|
||||||
if (std.mem.indexOfScalar(Node, edges.values, edge_node) == null) {
|
|
||||||
try edges.push_grow(edge_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(self: Self) bool {
|
|
||||||
return self.node_count != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn nodes(self: *const Self) Nodes {
|
|
||||||
return .{
|
|
||||||
.occupied_table = self.table.values.get(.is_occupied),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mark_visited(self: *Self, node: Node) bool {
|
|
||||||
if (!self.exists(node)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.table.values.get(.is_visited)[@intFromEnum(node)] = true;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_node(self: *Self, node: Node) ?Payload {
|
|
||||||
if (!self.exists(node)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const node_index = @intFromEnum(node);
|
|
||||||
|
|
||||||
self.table.values.get(.is_occupied)[node_index] = false;
|
|
||||||
self.node_count -= 1;
|
|
||||||
|
|
||||||
return self.table.values.get(.payload)[node_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reset_visited(self: *Self) void {
|
|
||||||
@memset(self.table.values.get(.is_visited), false);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn visited(self: Self, node: Node) ?bool {
|
|
||||||
if (!self.exists(node)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.table.values.get(.is_visited)[@intFromEnum(node)];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Node = enum (usize) { _ };
|
|
||||||
|
|
||||||
pub const Nodes = struct {
|
|
||||||
occupied_table: []const bool,
|
|
||||||
iterations: usize = 0,
|
|
||||||
|
|
||||||
pub fn next(self: *Nodes) ?Node {
|
|
||||||
std.debug.assert(self.iterations <= self.occupied_table.len);
|
|
||||||
|
|
||||||
while (self.iterations != self.occupied_table.len) {
|
|
||||||
defer self.iterations += 1;
|
|
||||||
|
|
||||||
if (self.occupied_table[self.iterations]) {
|
|
||||||
return @enumFromInt(self.iterations);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,159 +0,0 @@
|
|||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const io = @import("./io.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const AccessError = error {
|
|
||||||
FileNotFound,
|
|
||||||
FileInaccessible,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ReadAllError = AccessError || error {
|
|
||||||
ReadIncomplete,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ReadAllOptions = struct {
|
|
||||||
offset: u64 = 0,
|
|
||||||
limit: u64 = std.math.maxInt(u64),
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Stat = struct {
|
|
||||||
size: u64,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Storage = struct {
|
|
||||||
userdata: *anyopaque,
|
|
||||||
vtable: *const VTable,
|
|
||||||
|
|
||||||
pub const VTable = struct {
|
|
||||||
stat: *const fn (*anyopaque, []const u8) AccessError!Stat,
|
|
||||||
read: *const fn (*anyopaque, []const u8, usize, []u8) AccessError!usize,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn read(self: Storage, path: []const u8, output: []u8, offset: u64) AccessError!usize {
|
|
||||||
return self.vtable.read(self.userdata, path, offset, output);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_all(self: Storage, path: []const u8, output: []u8, options: ReadAllOptions) ReadAllError![]const u8 {
|
|
||||||
const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output);
|
|
||||||
|
|
||||||
if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) {
|
|
||||||
return error.ReadIncomplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
return output[0 .. bytes_read];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]u8 {
|
|
||||||
const buffer = try allocator.alloc(u8, @min((try self.stat(path)).size, options.limit));
|
|
||||||
|
|
||||||
errdefer allocator.free(buffer);
|
|
||||||
|
|
||||||
if (try self.vtable.read(self.userdata, path, options.offset, buffer) != buffer.len) {
|
|
||||||
return error.ReadIncomplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_foreign(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
|
|
||||||
const decoded = (try self.read_native(path, offset, Type)) orelse {
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
return switch (@typeInfo(Type)) {
|
|
||||||
.Struct => std.mem.byteSwapAllFields(Type, &decoded),
|
|
||||||
else => @byteSwap(decoded),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
|
|
||||||
var buffer = @as([@sizeOf(Type)]u8, undefined);
|
|
||||||
|
|
||||||
if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return @as(*align(1) const Type, @ptrCast(&buffer)).*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stat(self: Storage, path: []const u8) AccessError!Stat {
|
|
||||||
return self.vtable.stat(self.userdata, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const read_little = switch (native_endian) {
|
|
||||||
.little => read_native,
|
|
||||||
.big => read_foreign,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const read_big = switch (native_endian) {
|
|
||||||
.little => read_foreign,
|
|
||||||
.big => read_native,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const bundle = init: {
|
|
||||||
const Bundle = struct {
|
|
||||||
fn full_path(path: []const u8) AccessError![4095:0]u8 {
|
|
||||||
var buffer = [_:0]u8{0} ** 4095;
|
|
||||||
|
|
||||||
_ = std.fs.cwd().realpath(path, &buffer) catch {
|
|
||||||
return error.FileInaccessible;
|
|
||||||
};
|
|
||||||
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read(_: *anyopaque, path: []const u8, offset: usize, output: []u8) AccessError!usize {
|
|
||||||
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
|
||||||
return switch (open_error) {
|
|
||||||
error.FileNotFound => error.FileNotFound,
|
|
||||||
else => error.FileInaccessible,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
if (offset != 0) {
|
|
||||||
file.seekTo(offset) catch {
|
|
||||||
return error.FileInaccessible;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return file.read(output) catch error.FileInaccessible;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn stat(_: *anyopaque, path: []const u8) AccessError!Stat {
|
|
||||||
const file_stat = get: {
|
|
||||||
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
|
||||||
return switch (open_error) {
|
|
||||||
error.FileNotFound => error.FileNotFound,
|
|
||||||
else => error.FileInaccessible,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
defer file.close();
|
|
||||||
|
|
||||||
break: get file.stat() catch {
|
|
||||||
return error.FileInaccessible;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.size = file_stat.size,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
break: init Storage{
|
|
||||||
.userdata = undefined,
|
|
||||||
|
|
||||||
.vtable = &.{
|
|
||||||
.stat = Bundle.stat,
|
|
||||||
.read = Bundle.read,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const native_endian = builtin.cpu.arch.endian();
|
|
237
src/ona/gfx.zig
Normal file
237
src/ona/gfx.zig
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const ext = @import("ext");
|
||||||
|
|
||||||
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Display = struct {
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
is_hidden: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Context = struct {
|
||||||
|
window: *ext.SDL_Window,
|
||||||
|
gpu_device: *ext.SDL_GPUDevice,
|
||||||
|
basic_shader: *ext.SDL_GPUShader = undefined,
|
||||||
|
|
||||||
|
pub fn deinit(self: *Context) void {
|
||||||
|
ext.SDL_DestroyGPUDevice(self.gpu_device);
|
||||||
|
ext.SDL_DestroyWindow(self.window);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const traits = ona.Traits{
|
||||||
|
.is_thread_unsafe = true,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
fn compile_shaders(_: ona.Write(Context), assets: ona.Assets) !void {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
||||||
|
|
||||||
|
defer {
|
||||||
|
arena.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
const shader_path = "graphics_demo.eff";
|
||||||
|
var root = try coral.shaders.Root.init(&arena);
|
||||||
|
|
||||||
|
const Constants = extern struct {
|
||||||
|
screen_width: f32,
|
||||||
|
screen_height: f32,
|
||||||
|
time: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
try root.defineUniform(&arena, .{
|
||||||
|
.identifier = "effect",
|
||||||
|
.semantic = "Effect",
|
||||||
|
.binding = 0,
|
||||||
|
.has_field = .fieldsOf(Constants),
|
||||||
|
});
|
||||||
|
|
||||||
|
try root.defineTexture(&arena, .{
|
||||||
|
.identifier = "albedo",
|
||||||
|
.binding = 0,
|
||||||
|
.layout = .dimensions_2,
|
||||||
|
});
|
||||||
|
|
||||||
|
try root.defineInput(&arena, .{
|
||||||
|
.identifier = "uv",
|
||||||
|
.type = .float2,
|
||||||
|
.location = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
try root.defineOutput(&arena, .{
|
||||||
|
.identifier = "color",
|
||||||
|
.type = .float4,
|
||||||
|
.location = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (try root.parseText(&arena, try assets.load(shader_path, arena.allocator()))) {
|
||||||
|
.ok => {
|
||||||
|
var codes = coral.Stack(u8).empty;
|
||||||
|
|
||||||
|
try coral.shaders.generateSpirv(coral.bytes.stackWriter(&codes), root);
|
||||||
|
|
||||||
|
std.log.info("{s}", .{codes.values});
|
||||||
|
},
|
||||||
|
|
||||||
|
.failure => |failure| {
|
||||||
|
std.log.err("failed to parse shader {s}: {s}", .{ shader_path, failure });
|
||||||
|
|
||||||
|
return error.ShaderParseFailure;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// const basic_shader = ext.SDL_CreateGPUShader(gpu_device, &.{
|
||||||
|
// .code = spirv_code,
|
||||||
|
// .code_size = spirv_code.len,
|
||||||
|
// .stage = ext.SDL_GPU_SHADERSTAGE_FRAGMENT,
|
||||||
|
// .entrypoint = "frag_main",
|
||||||
|
// }) orelse {
|
||||||
|
// return sdl_failure("Failed to create basic shader");
|
||||||
|
// };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll(input_events: ona.Send(ona.hid.Event)) !void {
|
||||||
|
var event: ext.SDL_Event = undefined;
|
||||||
|
|
||||||
|
while (ext.SDL_PollEvent(&event)) {
|
||||||
|
try input_events.push(switch (event.type) {
|
||||||
|
ext.SDL_EVENT_QUIT => .quit,
|
||||||
|
ext.SDL_EVENT_KEY_UP => .{ .key_up = @enumFromInt(event.key.scancode) },
|
||||||
|
ext.SDL_EVENT_KEY_DOWN => .{ .key_down = @enumFromInt(event.key.scancode) },
|
||||||
|
|
||||||
|
ext.SDL_EVENT_MOUSE_BUTTON_UP => .{
|
||||||
|
.mouse_up = switch (event.button.button) {
|
||||||
|
ext.SDL_BUTTON_LEFT => .left,
|
||||||
|
ext.SDL_BUTTON_RIGHT => .right,
|
||||||
|
ext.SDL_BUTTON_MIDDLE => .middle,
|
||||||
|
else => continue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ext.SDL_EVENT_MOUSE_BUTTON_DOWN => .{
|
||||||
|
.mouse_down = switch (event.button.button) {
|
||||||
|
ext.SDL_BUTTON_LEFT => .left,
|
||||||
|
ext.SDL_BUTTON_RIGHT => .right,
|
||||||
|
ext.SDL_BUTTON_MIDDLE => .middle,
|
||||||
|
else => continue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
ext.SDL_EVENT_MOUSE_MOTION => .{
|
||||||
|
.mouse_motion = .{
|
||||||
|
.relative_position = .{ event.motion.xrel, event.motion.yrel },
|
||||||
|
.absolute_position = .{ event.motion.x, event.motion.y },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
else => continue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn prepare(display: ona.Write(Display)) void {
|
||||||
|
display.ptr.is_hidden = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn render(context: ona.Read(Context)) !void {
|
||||||
|
const commands = ext.SDL_AcquireGPUCommandBuffer(context.ptr.gpu_device) orelse {
|
||||||
|
return error.SdlFailure;
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
_ = !ext.SDL_CancelGPUCommandBuffer(commands);
|
||||||
|
}
|
||||||
|
|
||||||
|
var swapchain_texture: *ext.SDL_Texture = undefined;
|
||||||
|
|
||||||
|
if (!ext.SDL_WaitAndAcquireGPUSwapchainTexture(commands, context.ptr.window, @ptrCast(&swapchain_texture), null, null)) {
|
||||||
|
return error.SdlFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const color_targets = [_]ext.SDL_GPUColorTargetInfo{
|
||||||
|
.{
|
||||||
|
.texture = @ptrCast(swapchain_texture),
|
||||||
|
.load_op = ext.SDL_GPU_LOADOP_CLEAR,
|
||||||
|
.clear_color = .{ .r = 0, .g = 0.2, .b = 0.4, .a = 1.0 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const render_pass = ext.SDL_BeginGPURenderPass(commands, &color_targets, color_targets.len, null) orelse {
|
||||||
|
return error.SdlFailure;
|
||||||
|
};
|
||||||
|
|
||||||
|
defer {
|
||||||
|
ext.SDL_EndGPURenderPass(render_pass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ext.SDL_SubmitGPUCommandBuffer(commands)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(app: *ona.App) !void {
|
||||||
|
if (!ext.SDL_Init(ext.SDL_INIT_VIDEO)) {
|
||||||
|
return error.SystemFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
const display = try app.putState(Display{
|
||||||
|
.width = 1280,
|
||||||
|
.height = 720,
|
||||||
|
.is_hidden = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const window = ext.SDL_CreateWindow("Ona", display.width, display.height, ext.SDL_WINDOW_HIDDEN) orelse {
|
||||||
|
return error.SdlFailure;
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
ext.SDL_DestroyWindow(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
const gpu_device = ext.SDL_CreateGPUDevice(ext.SDL_GPU_SHADERFORMAT_SPIRV, builtin.mode == .Debug, null) orelse {
|
||||||
|
return error.SdlFailure;
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
ext.SDL_DestroyGPUDevice(gpu_device);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ext.SDL_ClaimWindowForGPUDevice(gpu_device, window)) {
|
||||||
|
return error.SdlFailure;
|
||||||
|
}
|
||||||
|
|
||||||
|
try app.setState(Context{
|
||||||
|
.window = window,
|
||||||
|
.gpu_device = gpu_device,
|
||||||
|
});
|
||||||
|
|
||||||
|
try app.on(.load, .of(prepare));
|
||||||
|
try app.on(.load, .of(compile_shaders));
|
||||||
|
try app.on(.post_update, .of(poll));
|
||||||
|
try app.on(.render, .of(render));
|
||||||
|
try app.on(.finish, .of(synchronize));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn synchronize(context: ona.Write(Context), display: ona.Read(Display)) !void {
|
||||||
|
const window_flags = ext.SDL_GetWindowFlags(context.ptr.window);
|
||||||
|
const is_window_hidden = (window_flags & ext.SDL_WINDOW_HIDDEN) != 0;
|
||||||
|
|
||||||
|
if (is_window_hidden != display.ptr.is_hidden) {
|
||||||
|
const was_window_changed = switch (display.ptr.is_hidden) {
|
||||||
|
true => ext.SDL_HideWindow(context.ptr.window),
|
||||||
|
false => ext.SDL_ShowWindow(context.ptr.window),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!was_window_changed) {
|
||||||
|
std.log.warn("failed to change window visibility", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,27 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn djb2(comptime int: std.builtin.Type.Int, bytes: []const u8) std.meta.Int(int.signedness, int.bits) {
|
|
||||||
var hash = @as(std.meta.Int(int.signedness, int.bits), 5381);
|
|
||||||
|
|
||||||
for (bytes) |byte| {
|
|
||||||
hash = ((hash << 5) +% hash) +% byte;
|
|
||||||
}
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn jenkins(comptime int: std.builtin.Type.Int, bytes: []const u8) std.meta.Int(int.signedness, int.bits) {
|
|
||||||
var hash = @as(std.meta.Int(int.signedness, int.bits), 0);
|
|
||||||
|
|
||||||
for (bytes) |byte| {
|
|
||||||
hash +%= byte;
|
|
||||||
hash +%= (hash << 10);
|
|
||||||
hash ^= (hash >> 6);
|
|
||||||
}
|
|
||||||
|
|
||||||
hash +%= (hash << 3);
|
|
||||||
hash ^= (hash >> 11);
|
|
||||||
hash +%= (hash << 15);
|
|
||||||
|
|
||||||
return hash;
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const allocator = gpa.allocator();
|
|
||||||
|
|
||||||
var gpa = std.heap.GeneralPurposeAllocator(.{
|
|
||||||
.thread_safe = true,
|
|
||||||
}){};
|
|
||||||
|
|
||||||
pub fn trace_leaks() void {
|
|
||||||
_ = gpa.detectLeaks();
|
|
||||||
}
|
|
196
src/ona/hid.zig
Normal file
196
src/ona/hid.zig
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
pub const Event = union(enum) {
|
||||||
|
quit,
|
||||||
|
key_up: KeyScancode,
|
||||||
|
key_down: KeyScancode,
|
||||||
|
mouse_up: MouseButton,
|
||||||
|
mouse_down: MouseButton,
|
||||||
|
mouse_motion: MouseMotion,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MouseButton = enum {
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
middle,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const MouseMotion = struct {
|
||||||
|
relative_position: @Vector(2, f32),
|
||||||
|
absolute_position: @Vector(2, f32),
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const KeyScancode = enum(u32) {
|
||||||
|
no_event = 0x00,
|
||||||
|
error_rollover = 0x01,
|
||||||
|
post_fail = 0x02,
|
||||||
|
error_undefined = 0x03,
|
||||||
|
a = 0x04,
|
||||||
|
b = 0x05,
|
||||||
|
c = 0x06,
|
||||||
|
d = 0x07,
|
||||||
|
e = 0x08,
|
||||||
|
f = 0x09,
|
||||||
|
g = 0x0A,
|
||||||
|
h = 0x0B,
|
||||||
|
i = 0x0C,
|
||||||
|
j = 0x0D,
|
||||||
|
k = 0x0E,
|
||||||
|
l = 0x0F,
|
||||||
|
m = 0x10,
|
||||||
|
n = 0x11,
|
||||||
|
o = 0x12,
|
||||||
|
p = 0x13,
|
||||||
|
q = 0x14,
|
||||||
|
r = 0x15,
|
||||||
|
s = 0x16,
|
||||||
|
t = 0x17,
|
||||||
|
u = 0x18,
|
||||||
|
v = 0x19,
|
||||||
|
w = 0x1A,
|
||||||
|
x = 0x1B,
|
||||||
|
y = 0x1C,
|
||||||
|
z = 0x1D,
|
||||||
|
one = 0x1E,
|
||||||
|
two = 0x1F,
|
||||||
|
three = 0x20,
|
||||||
|
four = 0x21,
|
||||||
|
five = 0x22,
|
||||||
|
six = 0x23,
|
||||||
|
seven = 0x24,
|
||||||
|
eight = 0x25,
|
||||||
|
nine = 0x26,
|
||||||
|
zero = 0x27,
|
||||||
|
enter = 0x28,
|
||||||
|
escape = 0x29,
|
||||||
|
backspace = 0x2A,
|
||||||
|
tab = 0x2B,
|
||||||
|
space = 0x2C,
|
||||||
|
minus = 0x2D,
|
||||||
|
equal = 0x2E,
|
||||||
|
left_bracket = 0x2F,
|
||||||
|
right_bracket = 0x30,
|
||||||
|
backslash = 0x31,
|
||||||
|
non_us_pound = 0x32,
|
||||||
|
semicolon = 0x33,
|
||||||
|
quote = 0x34,
|
||||||
|
grave = 0x35,
|
||||||
|
comma = 0x36,
|
||||||
|
period = 0x37,
|
||||||
|
slash = 0x38,
|
||||||
|
caps_lock = 0x39,
|
||||||
|
f1 = 0x3A,
|
||||||
|
f2 = 0x3B,
|
||||||
|
f3 = 0x3C,
|
||||||
|
f4 = 0x3D,
|
||||||
|
f5 = 0x3E,
|
||||||
|
f6 = 0x3F,
|
||||||
|
f7 = 0x40,
|
||||||
|
f8 = 0x41,
|
||||||
|
f9 = 0x42,
|
||||||
|
f10 = 0x43,
|
||||||
|
f11 = 0x44,
|
||||||
|
f12 = 0x45,
|
||||||
|
print_screen = 0x46,
|
||||||
|
scroll_lock = 0x47,
|
||||||
|
pause = 0x48,
|
||||||
|
insert = 0x49,
|
||||||
|
home = 0x4A,
|
||||||
|
page_up = 0x4B,
|
||||||
|
delete = 0x4C,
|
||||||
|
end = 0x4D,
|
||||||
|
page_down = 0x4E,
|
||||||
|
right_arrow = 0x4F,
|
||||||
|
left_arrow = 0x50,
|
||||||
|
down_arrow = 0x51,
|
||||||
|
up_arrow = 0x52,
|
||||||
|
num_lock = 0x53,
|
||||||
|
keypad_slash = 0x54,
|
||||||
|
keypad_asterisk = 0x55,
|
||||||
|
keypad_minus = 0x56,
|
||||||
|
keypad_plus = 0x57,
|
||||||
|
keypad_enter = 0x58,
|
||||||
|
keypad_one = 0x59,
|
||||||
|
keypad_two = 0x5A,
|
||||||
|
keypad_three = 0x5B,
|
||||||
|
keypad_four = 0x5C,
|
||||||
|
keypad_five = 0x5D,
|
||||||
|
keypad_six = 0x5E,
|
||||||
|
keypad_seven = 0x5F,
|
||||||
|
keypad_eight = 0x60,
|
||||||
|
keypad_nine = 0x61,
|
||||||
|
keypad_zero = 0x62,
|
||||||
|
keypad_period = 0x63,
|
||||||
|
non_us_backslash = 0x64,
|
||||||
|
application = 0x65,
|
||||||
|
power = 0x66,
|
||||||
|
keypad_equal = 0x67,
|
||||||
|
f13 = 0x68,
|
||||||
|
f14 = 0x69,
|
||||||
|
f15 = 0x6A,
|
||||||
|
f16 = 0x6B,
|
||||||
|
f17 = 0x6C,
|
||||||
|
f18 = 0x6D,
|
||||||
|
f19 = 0x6E,
|
||||||
|
f20 = 0x6F,
|
||||||
|
f21 = 0x70,
|
||||||
|
f22 = 0x71,
|
||||||
|
f23 = 0x72,
|
||||||
|
f24 = 0x73,
|
||||||
|
execute = 0x74,
|
||||||
|
help = 0x75,
|
||||||
|
menu = 0x76,
|
||||||
|
select = 0x77,
|
||||||
|
stop = 0x78,
|
||||||
|
again = 0x79,
|
||||||
|
undo = 0x7A,
|
||||||
|
cut = 0x7B,
|
||||||
|
copy = 0x7C,
|
||||||
|
paste = 0x7D,
|
||||||
|
find = 0x7E,
|
||||||
|
mute = 0x7F,
|
||||||
|
volume_up = 0x80,
|
||||||
|
volume_down = 0x81,
|
||||||
|
lock_caps_lock = 0x82,
|
||||||
|
lock_num_lock = 0x83,
|
||||||
|
lock_scroll_lock = 0x84,
|
||||||
|
keypad_comma = 0x85,
|
||||||
|
keypad_equal_sign = 0x86,
|
||||||
|
international1 = 0x87,
|
||||||
|
international2 = 0x88,
|
||||||
|
international3 = 0x89,
|
||||||
|
international4 = 0x8A,
|
||||||
|
international5 = 0x8B,
|
||||||
|
international6 = 0x8C,
|
||||||
|
international7 = 0x8D,
|
||||||
|
international8 = 0x8E,
|
||||||
|
international9 = 0x8F,
|
||||||
|
lang1 = 0x90,
|
||||||
|
lang2 = 0x91,
|
||||||
|
lang3 = 0x92,
|
||||||
|
lang4 = 0x93,
|
||||||
|
lang5 = 0x94,
|
||||||
|
lang6 = 0x95,
|
||||||
|
lang7 = 0x96,
|
||||||
|
lang8 = 0x97,
|
||||||
|
lang9 = 0x98,
|
||||||
|
alternate_erase = 0x99,
|
||||||
|
sys_req_attention = 0x9A,
|
||||||
|
cancel = 0x9B,
|
||||||
|
clear = 0x9C,
|
||||||
|
prior = 0x9D,
|
||||||
|
return_key = 0x9E,
|
||||||
|
separator = 0x9F,
|
||||||
|
out = 0xA0,
|
||||||
|
oper = 0xA1,
|
||||||
|
clear_again = 0xA2,
|
||||||
|
cr_sel_props = 0xA3,
|
||||||
|
ex_sel = 0xA4,
|
||||||
|
left_control = 0xE0,
|
||||||
|
left_shift = 0xE1,
|
||||||
|
left_alt = 0xE2,
|
||||||
|
left_gui = 0xE3,
|
||||||
|
right_control = 0xE4,
|
||||||
|
right_shift = 0xE5,
|
||||||
|
right_alt = 0xE6,
|
||||||
|
right_gui = 0xE7,
|
||||||
|
_,
|
||||||
|
};
|
194
src/ona/io.zig
194
src/ona/io.zig
@ -1,194 +0,0 @@
|
|||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Error = error {
|
|
||||||
UnavailableResource,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn Functor(comptime Output: type, comptime input_types: []const type) type {
|
|
||||||
const InputTuple = std.meta.Tuple(input_types);
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
context: *const anyopaque,
|
|
||||||
apply_with_context: *const fn (*const anyopaque, InputTuple) Output,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn apply(self: *const Self, inputs: InputTuple) Output {
|
|
||||||
return self.apply_with_context(self.context, inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bind(comptime State: type, state: *const State, comptime invoke: anytype) Self {
|
|
||||||
const is_zero_aligned = @alignOf(State) == 0;
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.context = if (is_zero_aligned) state else @ptrCast(state),
|
|
||||||
|
|
||||||
.apply_with_context = struct {
|
|
||||||
fn invoke_concrete(context: *const anyopaque, inputs: InputTuple) Output {
|
|
||||||
if (is_zero_aligned) {
|
|
||||||
return @call(.auto, invoke, .{@as(*const State, @ptrCast(context))} ++ inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
return switch (@typeInfo(@typeInfo(@TypeOf(invoke)).Fn.return_type.?)) {
|
|
||||||
.ErrorUnion => try @call(.auto, invoke, .{@as(*const State, @ptrCast(@alignCast(context)))} ++ inputs),
|
|
||||||
else => @call(.auto, invoke, .{@as(*const State, @ptrCast(@alignCast(context)))} ++ inputs),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}.invoke_concrete,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bind_fn(comptime invoke: anytype) Self {
|
|
||||||
return .{
|
|
||||||
.context = undefined,
|
|
||||||
|
|
||||||
.apply_with_context = struct {
|
|
||||||
fn invoke_concrete(_: *const anyopaque, inputs: InputTuple) Output {
|
|
||||||
return @call(.auto, invoke, inputs);
|
|
||||||
}
|
|
||||||
}.invoke_concrete,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Generator(comptime Output: type, comptime input_types: []const type) type {
|
|
||||||
const InputTuple = std.meta.Tuple(input_types);
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
context: *anyopaque,
|
|
||||||
yield_with_context: *const fn (*anyopaque, InputTuple) Output,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn bind(comptime State: type, state: *State, comptime invoke: anytype) Self {
|
|
||||||
const is_zero_aligned = @alignOf(State) == 0;
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.context = if (is_zero_aligned) state else @ptrCast(state),
|
|
||||||
|
|
||||||
.yield_with_context = struct {
|
|
||||||
fn invoke_concrete(context: *anyopaque, inputs: InputTuple) Output {
|
|
||||||
if (is_zero_aligned) {
|
|
||||||
return @call(.auto, invoke, .{@as(*State, @ptrCast(context))} ++ inputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
return switch (@typeInfo(@typeInfo(@TypeOf(invoke)).Fn.return_type.?)) {
|
|
||||||
.ErrorUnion => try @call(.auto, invoke, .{@as(*State, @ptrCast(@alignCast(context)))} ++ inputs),
|
|
||||||
else => @call(.auto, invoke, .{@as(*State, @ptrCast(@alignCast(context)))} ++ inputs),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}.invoke_concrete,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn bind_fn(comptime invoke: anytype) Self {
|
|
||||||
return .{
|
|
||||||
.context = undefined,
|
|
||||||
|
|
||||||
.yield_with_context = struct {
|
|
||||||
fn invoke_concrete(_: *const anyopaque, inputs: InputTuple) Output {
|
|
||||||
return @call(.auto, invoke, inputs);
|
|
||||||
}
|
|
||||||
}.invoke_concrete,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn yield(self: *const Self, inputs: InputTuple) Output {
|
|
||||||
return self.yield_with_context(self.context, inputs);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const NullWritable = struct {
|
|
||||||
written: usize = 0,
|
|
||||||
|
|
||||||
pub fn write(self: *NullWritable, buffer: []const u8) Error!usize {
|
|
||||||
self.written += buffer.len;
|
|
||||||
|
|
||||||
return buffer.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn writer(self: *NullWritable) Writer {
|
|
||||||
return Writer.bind(NullWritable, self, write);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Reader = Generator(Error!usize, &.{[]u8});
|
|
||||||
|
|
||||||
pub const Writer = Generator(Error!usize, &.{[]const u8});
|
|
||||||
|
|
||||||
pub fn alloc_read(input: ona.io.Reader, allocator: std.mem.Allocator) []u8 {
|
|
||||||
const buffer = ona.stack.Sequential(u8){.allocator = allocator};
|
|
||||||
|
|
||||||
errdefer buffer.deinit();
|
|
||||||
|
|
||||||
const streamed = try stream_all(input.reader(), buffer.writer());
|
|
||||||
|
|
||||||
return buffer.to_allocation(streamed);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const bits_per_byte = 8;
|
|
||||||
|
|
||||||
pub fn skip_n(input: Reader, distance: u64) Error!void {
|
|
||||||
var buffer = @as([512]u8, undefined);
|
|
||||||
var remaining = distance;
|
|
||||||
|
|
||||||
while (remaining != 0) {
|
|
||||||
const read = try input.yield(.{buffer[0 .. @min(remaining, buffer.len)]});
|
|
||||||
|
|
||||||
if (read == 0) {
|
|
||||||
return error.UnavailableResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining -= read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stream_all(input: Reader, output: Writer) Error!usize {
|
|
||||||
var buffer = @as([512]u8, undefined);
|
|
||||||
var copied = @as(usize, 0);
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const read = try input.apply(.{&buffer});
|
|
||||||
|
|
||||||
if (read == 0) {
|
|
||||||
return copied;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try output.apply(.{buffer[0 .. read]}) != read) {
|
|
||||||
return error.UnavailableResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
copied += read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize {
|
|
||||||
var buffer = @as([512]u8, undefined);
|
|
||||||
var remaining = limit;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const read = try input.yield(.{buffer[0 .. @min(remaining, buffer.len)]});
|
|
||||||
|
|
||||||
if (read == 0) {
|
|
||||||
return limit - remaining;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (try output.yield(.{buffer[0 .. read]}) != read) {
|
|
||||||
return error.UnavailableResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
remaining -= read;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_all(writer: Writer, utf8: []const u8) Error!void {
|
|
||||||
if (try writer.yield(.{utf8}) != utf8.len) {
|
|
||||||
return error.UnavailableResource;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn cross(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child {
|
|
||||||
const multipled = v1 * v2;
|
|
||||||
const vector_info = @typeInfo(@TypeOf(v1)).Vector;
|
|
||||||
var result = multipled[0];
|
|
||||||
comptime var index = @as(usize, 1);
|
|
||||||
|
|
||||||
inline while (index < vector_info.len) : (index += 1) {
|
|
||||||
result -= multipled[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn distance(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child {
|
|
||||||
return length(v1 - v2);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn dot(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child {
|
|
||||||
const multipled = v1 * v2;
|
|
||||||
const vector_info = @typeInfo(@TypeOf(v1)).Vector;
|
|
||||||
var result = multipled[0];
|
|
||||||
comptime var index = @as(usize, 1);
|
|
||||||
|
|
||||||
inline while (index < vector_info.len) : (index += 1) {
|
|
||||||
result += multipled[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn length(v: anytype) @typeInfo(@TypeOf(v)).Vector.child {
|
|
||||||
return @sqrt(length_squared(v));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn length_squared(v: anytype) @typeInfo(@TypeOf(v)).Vector.child {
|
|
||||||
return dot(v, v);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn normal(v: anytype) @TypeOf(v) {
|
|
||||||
const ls = length_squared(v);
|
|
||||||
const Vector = @TypeOf(v);
|
|
||||||
|
|
||||||
if (ls > std.math.floatEps(@typeInfo(Vector).Vector.child)) {
|
|
||||||
return v / @as(Vector, @splat(@sqrt(ls)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
279
src/ona/map.zig
279
src/ona/map.zig
@ -1,279 +0,0 @@
|
|||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(Key)) type {
|
|
||||||
const load_max = 0.75;
|
|
||||||
const max_int = std.math.maxInt(usize);
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
allocator: std.mem.Allocator = ona.heap.allocator,
|
|
||||||
entry_map: []?Entry = &.{},
|
|
||||||
len: usize = 0,
|
|
||||||
|
|
||||||
pub const Entry = struct {
|
|
||||||
key: Key,
|
|
||||||
value: Value,
|
|
||||||
|
|
||||||
fn write_into(self: Entry, table: *Self) bool {
|
|
||||||
const hash_max = @min(max_int, table.entry_map.len);
|
|
||||||
var hashed_key = traits.hash(self.key) % hash_max;
|
|
||||||
var iterations = @as(usize, 0);
|
|
||||||
|
|
||||||
while (true) : (iterations += 1) {
|
|
||||||
std.debug.assert(iterations < table.entry_map.len);
|
|
||||||
|
|
||||||
const table_entry = &(table.entry_map[hashed_key] orelse {
|
|
||||||
table.entry_map[hashed_key] = .{
|
|
||||||
.key = self.key,
|
|
||||||
.value = self.value,
|
|
||||||
};
|
|
||||||
|
|
||||||
table.len += 1;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (traits.are_equal(table_entry.key, self.key)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
hashed_key = (hashed_key +% 1) % hash_max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Entries = struct {
|
|
||||||
table: *const Self,
|
|
||||||
iterations: usize,
|
|
||||||
|
|
||||||
pub fn next(self: *Entries) ?*Entry {
|
|
||||||
while (self.iterations < self.table.entry_map.len) {
|
|
||||||
defer self.iterations += 1;
|
|
||||||
|
|
||||||
if (self.table.entry_map[self.iterations]) |*entry| {
|
|
||||||
return entry;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn entries(self: *const Self) Entries {
|
|
||||||
return .{
|
|
||||||
.table = self,
|
|
||||||
.iterations = 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(self: *Self, key: Key) ?Entry {
|
|
||||||
const hash_max = @min(max_int, self.entry_map.len);
|
|
||||||
var hashed_key = key.hash() % hash_max;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const entry = &(self.entry_map[hashed_key] orelse continue);
|
|
||||||
|
|
||||||
if (self.keys_equal(entry.key, key)) {
|
|
||||||
const original_entry = entry.*;
|
|
||||||
|
|
||||||
self.entry_map[hashed_key] = null;
|
|
||||||
|
|
||||||
return original_entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
hashed_key = (hashed_key +% 1) % hash_max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!?Entry {
|
|
||||||
try self.rehash(load_max);
|
|
||||||
|
|
||||||
std.debug.assert(self.entry_map.len > self.len);
|
|
||||||
|
|
||||||
{
|
|
||||||
const hash_max = @min(max_int, self.entry_map.len);
|
|
||||||
var hashed_key = traits.hash(key) % hash_max;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const entry = &(self.entry_map[hashed_key] orelse {
|
|
||||||
self.entry_map[hashed_key] = .{
|
|
||||||
.key = key,
|
|
||||||
.value = value,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.len += 1;
|
|
||||||
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (traits.are_equal(key, entry.key)) {
|
|
||||||
const original_entry = entry.*;
|
|
||||||
|
|
||||||
entry.* = .{
|
|
||||||
.key = key,
|
|
||||||
.value = value,
|
|
||||||
};
|
|
||||||
|
|
||||||
return original_entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
hashed_key = (hashed_key +% 1) % hash_max;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(self: *Self) void {
|
|
||||||
for (self.entry_map) |*entry| {
|
|
||||||
entry.* = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.len = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
if (self.entry_map.len == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.allocator.free(self.entry_map);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: Self, key: Key) ?*Value {
|
|
||||||
if (self.len == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const hash_max = @min(max_int, self.entry_map.len);
|
|
||||||
var hashed_key = traits.hash(key) % hash_max;
|
|
||||||
var iterations = @as(usize, 0);
|
|
||||||
|
|
||||||
while (iterations < self.len) : (iterations += 1) {
|
|
||||||
const entry = &(self.entry_map[hashed_key] orelse return null);
|
|
||||||
|
|
||||||
if (traits.are_equal(entry.key, key)) {
|
|
||||||
return &entry.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
hashed_key = (hashed_key +% 1) % hash_max;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn emplace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!bool {
|
|
||||||
try self.rehash(load_max);
|
|
||||||
|
|
||||||
std.debug.assert(self.entry_map.len > self.len);
|
|
||||||
|
|
||||||
const entry = Entry{
|
|
||||||
.key = key,
|
|
||||||
.value = value,
|
|
||||||
};
|
|
||||||
|
|
||||||
return entry.write_into(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_factor(self: Self) f32 {
|
|
||||||
return if (self.entry_map.len == 0) 1 else @as(f32, @floatFromInt(self.len)) / @as(f32, @floatFromInt(self.entry_map.len));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rehash(self: *Self, max_load: f32) std.mem.Allocator.Error!void {
|
|
||||||
if (self.load_factor() <= max_load) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var table = Self{
|
|
||||||
.allocator = self.allocator,
|
|
||||||
};
|
|
||||||
|
|
||||||
errdefer table.deinit();
|
|
||||||
|
|
||||||
table.entry_map = allocate: {
|
|
||||||
const min_len = @max(1, self.len);
|
|
||||||
const table_size = min_len * 2;
|
|
||||||
const zeroed_entry_map = try self.allocator.alloc(?Entry, table_size);
|
|
||||||
|
|
||||||
errdefer self.allocator.free(zeroed_entry_map);
|
|
||||||
|
|
||||||
for (zeroed_entry_map) |*entry| {
|
|
||||||
entry.* = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
break: allocate zeroed_entry_map;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (self.entry_map) |maybe_entry| {
|
|
||||||
if (maybe_entry) |entry| {
|
|
||||||
std.debug.assert(entry.write_into(&table));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.deinit();
|
|
||||||
|
|
||||||
self.* = table;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Traits(comptime Key: type) type {
|
|
||||||
return struct {
|
|
||||||
are_equal: fn (Key, Key) bool,
|
|
||||||
hash: fn (Key) usize,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn enum_traits(comptime Enum: type) Traits(Enum) {
|
|
||||||
const enums = struct {
|
|
||||||
fn are_equal(a: Enum, b: Enum) bool {
|
|
||||||
return a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash(value: Enum) usize {
|
|
||||||
return @intFromEnum(value) % std.math.maxInt(usize);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.are_equal = enums.are_equal,
|
|
||||||
.hash = enums.hash,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const string_traits = init: {
|
|
||||||
const strings = struct {
|
|
||||||
fn are_equal(a: []const u8, b: []const u8) bool {
|
|
||||||
return std.mem.eql(u8, a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash(value: []const u8) usize {
|
|
||||||
return ona.hashes.djb2(@typeInfo(usize).Int, value);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
break: init Traits([]const u8){
|
|
||||||
.are_equal = strings.are_equal,
|
|
||||||
.hash = strings.hash,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const usize_traits = init: {
|
|
||||||
const usizes = struct {
|
|
||||||
fn are_equal(a: usize, b: usize) bool {
|
|
||||||
return a == b;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash(value: usize) usize {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
break: init Traits(usize){
|
|
||||||
.are_equal = usizes.are_equal,
|
|
||||||
.hash = usizes.hash,
|
|
||||||
};
|
|
||||||
};
|
|
973
src/ona/ona.zig
973
src/ona/ona.zig
File diff suppressed because it is too large
Load Diff
@ -1,39 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn add(a: anytype, b: anytype) ?@TypeOf(a + b) {
|
|
||||||
const result, const overflow = @addWithOverflow(a, b);
|
|
||||||
|
|
||||||
return if (overflow == 0) result else null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fractional(value: anytype, fraction: anytype) ?@TypeOf(value) {
|
|
||||||
if (fraction < 0 or fraction > 1) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Fraction = @TypeOf(fraction);
|
|
||||||
|
|
||||||
return switch (@typeInfo(Fraction)) {
|
|
||||||
.Float => @intFromFloat(@as(Fraction, @floatFromInt(value)) * fraction),
|
|
||||||
else => @compileError("`fraction` expected float type, not " ++ @typeName(Fraction)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn lerp_angle(origin_angle: anytype, target_angle: anytype, weight: anytype) @TypeOf(origin_angle, target_angle, weight) {
|
|
||||||
const angle_difference = @mod(target_angle - origin_angle, std.math.tau);
|
|
||||||
const distance = @mod(2.0 * angle_difference, std.math.tau) - angle_difference;
|
|
||||||
|
|
||||||
return origin_angle + distance * weight;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn mul(a: anytype, b: anytype) ?@TypeOf(a * b) {
|
|
||||||
const result, const overflow = @mulWithOverflow(a, b);
|
|
||||||
|
|
||||||
return if (overflow == 0) result else null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sub(a: anytype, b: anytype) ?@TypeOf(a - b) {
|
|
||||||
const result, const overflow = @subWithOverflow(a, b);
|
|
||||||
|
|
||||||
return if (overflow == 0) result else null;
|
|
||||||
}
|
|
@ -1,116 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn ElementPtr(comptime Slice: type) type {
|
|
||||||
const pointer_info = @typeInfo(Slice).Pointer;
|
|
||||||
|
|
||||||
return @Type(.{
|
|
||||||
.Pointer = .{
|
|
||||||
.size = .One,
|
|
||||||
.is_const = pointer_info.is_const,
|
|
||||||
.is_volatile = pointer_info.is_volatile,
|
|
||||||
.alignment = pointer_info.alignment,
|
|
||||||
.address_space = pointer_info.address_space,
|
|
||||||
.child = pointer_info.child,
|
|
||||||
.is_allowzero = false,
|
|
||||||
.sentinel = null,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Parallel(comptime Type: type) type {
|
|
||||||
const fields = @typeInfo(Type).Struct.fields;
|
|
||||||
const alignment = @alignOf(Type);
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
len: usize = 0,
|
|
||||||
ptrs: [fields.len][*]align (alignment) u8 = undefined,
|
|
||||||
|
|
||||||
pub fn Element(comptime field: Field) type {
|
|
||||||
return fields[@intFromEnum(field)].type;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const Field = std.meta.FieldEnum(Type);
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
const all_fields = std.enums.values(Field);
|
|
||||||
|
|
||||||
pub fn get(self: Self, comptime field: Field) []align (alignment) Element(field) {
|
|
||||||
return self.ptr(field)[0 .. self.len];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ptr(self: Self, comptime field: Field) [*]align (alignment) Element(field) {
|
|
||||||
return @as([*]align (alignment) Element(field), @ptrCast(self.ptrs[@intFromEnum(field)]));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn slice(self: Self, off: usize, len: usize) ?Self {
|
|
||||||
if (len > self.len or off > len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
var sliced = Self{.len = len};
|
|
||||||
|
|
||||||
inline for (0 .. fields.len) |i| {
|
|
||||||
sliced.ptrs[i] = @ptrFromInt(@intFromPtr(self.ptrs[i]) + (@sizeOf(Element(all_fields[i])) * off));
|
|
||||||
}
|
|
||||||
|
|
||||||
return sliced;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write(self: Self, index: usize, value: Type) bool {
|
|
||||||
if (index >= self.len) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline for (0 .. fields.len) |i| {
|
|
||||||
self.get(all_fields[i])[index] = @field(value, fields[i].name);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(slice: anytype, index: usize) ?@typeInfo(@TypeOf(slice)).Pointer.child {
|
|
||||||
if (index >= slice.len) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return slice[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) {
|
|
||||||
const alignment = @alignOf(Element);
|
|
||||||
const Slices = Parallel(Element);
|
|
||||||
var buffers = @as([std.enums.values(Slices.Field).len][]align (alignment) u8, undefined);
|
|
||||||
var buffers_allocated = @as(usize, 0);
|
|
||||||
var allocated = Slices{.len = n};
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
for (0 .. buffers_allocated) |i| {
|
|
||||||
allocator.free(buffers[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const fields = @typeInfo(Element).Struct.fields;
|
|
||||||
|
|
||||||
inline for (0 .. fields.len) |i| {
|
|
||||||
buffers[i] = try allocator.alignedAlloc(u8, alignment, @sizeOf(fields[i].type) * n);
|
|
||||||
buffers_allocated += 1;
|
|
||||||
allocated.ptrs[i] = buffers[i].ptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return allocated;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parallel_copy(comptime Element: type, target: Parallel(Element), origin: Parallel(Element)) void {
|
|
||||||
inline for (comptime std.enums.values(Parallel(Element).Field)) |field| {
|
|
||||||
@memcpy(target.get(field), origin.get(field));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parallel_free(comptime Element: type, allocator: std.mem.Allocator, buffers: Parallel(Element)) void {
|
|
||||||
inline for (comptime std.enums.values(Parallel(Element).Field)) |field| {
|
|
||||||
allocator.free(buffers.get(field));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,294 +0,0 @@
|
|||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn Sequential(comptime Value: type) type {
|
|
||||||
return struct {
|
|
||||||
allocator: std.mem.Allocator = ona.heap.allocator,
|
|
||||||
values: []Value = &.{},
|
|
||||||
cap: usize = 0,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn clear(self: *Self) void {
|
|
||||||
self.values = self.values[0 .. 0];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
if (self.cap == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.allocator.free(self.values.ptr[0 .. self.cap]);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
|
|
||||||
const grown_capacity = self.values.len + additional;
|
|
||||||
|
|
||||||
if (grown_capacity <= self.cap) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = try self.allocator.alloc(Value, grown_capacity);
|
|
||||||
|
|
||||||
errdefer self.allocator.deallocate(buffer);
|
|
||||||
|
|
||||||
if (self.cap != 0) {
|
|
||||||
@memcpy(buffer[0 .. self.values.len], self.values);
|
|
||||||
self.allocator.free(self.values.ptr[0 .. self.cap]);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.values = @as([*]Value, @ptrCast(@alignCast(buffer)))[0 .. self.values.len];
|
|
||||||
self.cap = grown_capacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(self: Self) bool {
|
|
||||||
return self.values.len == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: Self) ?*Value {
|
|
||||||
if (self.values.len == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return &self.values[self.values.len - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(self: Self) usize {
|
|
||||||
return self.values.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(self: *Self) ?Value {
|
|
||||||
if (self.values.len == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tail_index = self.values.len - 1;
|
|
||||||
|
|
||||||
defer self.values = self.values[0 .. tail_index];
|
|
||||||
|
|
||||||
return self.values[tail_index];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_all(self: *Self) void {
|
|
||||||
self.values = self.values[0 .. 0];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_many(self: *Self, n: usize) bool {
|
|
||||||
const new_length = ona.scalars.sub(self.values.len, n) orelse {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.values = self.values[0 .. new_length];
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(self: *Self, value: Value) bool {
|
|
||||||
if (self.values.len == self.cap) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset_index = self.values.len;
|
|
||||||
|
|
||||||
self.values = self.values.ptr[0 .. self.values.len + 1];
|
|
||||||
|
|
||||||
self.values[offset_index] = value;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_all(self: *Self, values: []const Value) bool {
|
|
||||||
const new_length = self.values.len + values.len;
|
|
||||||
|
|
||||||
if (new_length > self.cap) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset_index = self.values.len;
|
|
||||||
|
|
||||||
self.values = self.values.ptr[0 .. new_length];
|
|
||||||
|
|
||||||
for (0 .. values.len) |index| {
|
|
||||||
self.values[offset_index + index] = values[index];
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
|
|
||||||
try self.grow(@max(1, self.values.len));
|
|
||||||
|
|
||||||
const offset_index = self.values.len;
|
|
||||||
|
|
||||||
self.values = self.values.ptr[0 .. self.values.len + 1];
|
|
||||||
|
|
||||||
self.values[offset_index] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_many(self: *Self, n: usize, value: Value) std.mem.Allocator.Error!void {
|
|
||||||
const new_length = self.values.len + n;
|
|
||||||
|
|
||||||
if (new_length > self.cap) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const offset_index = self.values.len;
|
|
||||||
|
|
||||||
self.values = self.values.ptr[0 .. new_length];
|
|
||||||
|
|
||||||
for (0 .. n) |index| {
|
|
||||||
self.values[offset_index + index] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error!void {
|
|
||||||
if (self.cap == size) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const values = try self.allocator.alloc(Value, size);
|
|
||||||
|
|
||||||
for (0 .. @min(values.len, self.values.len)) |i| {
|
|
||||||
values[i] = self.values[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.len > self.values.len) {
|
|
||||||
for (self.values.len .. values.len) |i| {
|
|
||||||
values[i] = default_value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.values = values[0 .. values.len];
|
|
||||||
self.cap = values.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn to_allocation(self: *Self, size: usize, default_value: Value) std.mem.Allocator.Error![]Value {
|
|
||||||
defer {
|
|
||||||
self.values = &.{};
|
|
||||||
self.cap = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const allocation = try self.allocator.realloc(self.values.ptr[0 .. self.cap], size);
|
|
||||||
|
|
||||||
for (allocation[@min(self.values.len, size) .. size]) |*value| {
|
|
||||||
value.* = default_value;
|
|
||||||
}
|
|
||||||
|
|
||||||
return allocation;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const writer = switch (Value) {
|
|
||||||
u8 => struct {
|
|
||||||
fn writer(self: *Self) ona.io.Writer {
|
|
||||||
return ona.io.Writer.bind(Self, self, write);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write(self: *Self, buffer: []const u8) ona.io.Error!usize {
|
|
||||||
self.push_all(buffer) catch return error.UnavailableResource;
|
|
||||||
|
|
||||||
return buffer.len;
|
|
||||||
}
|
|
||||||
}.writer,
|
|
||||||
|
|
||||||
else => @compileError("only `Stack(Byte)` has a `reader()` method"),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Parallel(comptime Value: type) type {
|
|
||||||
const Slices = ona.slices.Parallel(Value);
|
|
||||||
const alignment = @alignOf(Value);
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
allocator: std.mem.Allocator = ona.heap.allocator,
|
|
||||||
values: Slices = .{},
|
|
||||||
cap: usize = 0,
|
|
||||||
|
|
||||||
pub const Field = std.meta.FieldEnum(Value);
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn clear(self: *Self) void {
|
|
||||||
self.values = self.values.slice_all(0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
var capacity_slice = self.values;
|
|
||||||
|
|
||||||
capacity_slice.len = self.cap;
|
|
||||||
|
|
||||||
ona.slices.parallel_free(Value, self.allocator, capacity_slice);
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: Self, comptime field: Slices.Field) ?*align (alignment) Slices.Element(field) {
|
|
||||||
if (self.len() == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return @alignCast(&self.values.get(field)[self.len() - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
|
|
||||||
const grown_capacity = self.values.len + additional;
|
|
||||||
|
|
||||||
if (grown_capacity <= self.cap) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = try ona.slices.parallel_alloc(Value, self.allocator, grown_capacity);
|
|
||||||
|
|
||||||
if (self.cap != 0) {
|
|
||||||
ona.slices.parallel_copy(Value, buffer.slice(0, self.values.len).?, self.values);
|
|
||||||
ona.slices.parallel_free(Value, self.allocator, self.values.slice(0, self.cap).?);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cap = grown_capacity;
|
|
||||||
self.values = buffer.slice(0, self.values.len).?;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_empty(self: Self) bool {
|
|
||||||
return self.values.len == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(self: Self) usize {
|
|
||||||
return self.values.len;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop(self: *Self) bool {
|
|
||||||
if (self.values.len == 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.values = self.values.slice(0, self.values.len - 1).?;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_many(self: *Self, n: usize) bool {
|
|
||||||
const new_length = ona.scalars.sub(self.values.len, n) orelse {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
|
|
||||||
self.values = self.values.slice(0, new_length).?;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
|
|
||||||
try self.grow(@max(1, self.values.len));
|
|
||||||
|
|
||||||
const tail_index = self.values.len;
|
|
||||||
|
|
||||||
self.values.len += 1;
|
|
||||||
|
|
||||||
std.debug.assert(self.values.write(tail_index, value));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
159
src/ona/utf8.zig
159
src/ona/utf8.zig
@ -1,159 +0,0 @@
|
|||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![:0]u8 {
|
|
||||||
const formatted_len = count_formatted(format, args);
|
|
||||||
const buffer = try allocator.allocSentinel(u8, formatted_len, 0);
|
|
||||||
|
|
||||||
errdefer allocator.free(buffer);
|
|
||||||
|
|
||||||
// TODO: This is messy.
|
|
||||||
return @constCast(print_formatted(buffer, format, args) catch unreachable);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
|
|
||||||
var count = ona.io.NullWritable{};
|
|
||||||
|
|
||||||
write_formatted(count.writer(), format, args) catch unreachable;
|
|
||||||
|
|
||||||
return count.written;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn print_formatted(buffer: [:0]u8, comptime format: []const u8, args: anytype) ona.io.Error![:0]const u8 {
|
|
||||||
const Seekable = struct {
|
|
||||||
buffer: []u8,
|
|
||||||
cursor: usize,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
fn write(self: *Self, input: []const u8) ona.io.Error!usize {
|
|
||||||
const range = @min(input.len, self.buffer.len - self.cursor);
|
|
||||||
const tail = self.cursor + range;
|
|
||||||
|
|
||||||
@memcpy(self.buffer[self.cursor .. tail], input);
|
|
||||||
|
|
||||||
self.cursor = tail;
|
|
||||||
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const len = count_formatted(format, args);
|
|
||||||
|
|
||||||
if (len > buffer.len) {
|
|
||||||
return error.UnavailableResource;
|
|
||||||
}
|
|
||||||
|
|
||||||
var seekable = Seekable{
|
|
||||||
.buffer = buffer,
|
|
||||||
.cursor = 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
try write_formatted(ona.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args);
|
|
||||||
|
|
||||||
if (buffer.len < len) {
|
|
||||||
buffer[len] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer[0 .. len:0];
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_formatted(writer: ona.io.Writer, comptime format: []const u8, args: anytype) ona.io.Error!void {
|
|
||||||
switch (@typeInfo(@TypeOf(args))) {
|
|
||||||
.Struct => |arguments_struct| {
|
|
||||||
comptime var arg_index = 0;
|
|
||||||
comptime var head = 0;
|
|
||||||
comptime var tail = 0;
|
|
||||||
|
|
||||||
inline while (tail < format.len) : (tail += 1) {
|
|
||||||
if (format[tail] == '{') {
|
|
||||||
if (tail > format.len) {
|
|
||||||
@compileError("expected an idenifier after opening `{`");
|
|
||||||
}
|
|
||||||
|
|
||||||
tail += 1;
|
|
||||||
|
|
||||||
switch (format[tail]) {
|
|
||||||
'{' => {
|
|
||||||
try ona.io.print(writer, format[head .. (tail - 1)]);
|
|
||||||
|
|
||||||
tail += 1;
|
|
||||||
head = tail;
|
|
||||||
},
|
|
||||||
|
|
||||||
'}' => {
|
|
||||||
if (!arguments_struct.is_tuple) {
|
|
||||||
@compileError("all format specifiers must be named when using a named struct");
|
|
||||||
}
|
|
||||||
|
|
||||||
try ona.io.print(writer, args[arg_index]);
|
|
||||||
|
|
||||||
arg_index += 1;
|
|
||||||
tail += 1;
|
|
||||||
head = tail;
|
|
||||||
},
|
|
||||||
|
|
||||||
else => {
|
|
||||||
if (arguments_struct.is_tuple) {
|
|
||||||
@compileError("format specifiers cannot be named when using a tuple struct");
|
|
||||||
}
|
|
||||||
|
|
||||||
try ona.io.write_all(writer, format[head .. (tail - 1)]);
|
|
||||||
|
|
||||||
head = tail;
|
|
||||||
tail += 1;
|
|
||||||
|
|
||||||
if (tail >= format.len) {
|
|
||||||
@compileError("expected closing `}` or another `{` after opening `{`");
|
|
||||||
}
|
|
||||||
|
|
||||||
std.debug.assert(tail < format.len);
|
|
||||||
|
|
||||||
inline while (format[tail] != '}') {
|
|
||||||
tail += 1;
|
|
||||||
|
|
||||||
std.debug.assert(tail < format.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
try print_formatted_value(writer, @field(args, format[head .. tail]));
|
|
||||||
|
|
||||||
tail += 1;
|
|
||||||
head = tail;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try ona.io.write_all(writer, format[head .. ]);
|
|
||||||
},
|
|
||||||
|
|
||||||
else => @compileError("`arguments` must be a struct type"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
noinline fn print_formatted_value(writer: ona.io.Writer, value: anytype) ona.io.Error!void {
|
|
||||||
const Value = @TypeOf(value);
|
|
||||||
const hexadecimal_format = ona.ascii.HexadecimalFormat{};
|
|
||||||
const decimal_format = ona.ascii.DecimalFormat{};
|
|
||||||
|
|
||||||
return switch (@typeInfo(Value)) {
|
|
||||||
.Int => decimal_format.format(writer, value),
|
|
||||||
.Float => decimal_format.format(writer, value),
|
|
||||||
.Enum => ona.io.write_all(writer, @tagName(value)),
|
|
||||||
|
|
||||||
.Pointer => |pointer| switch (pointer.size) {
|
|
||||||
.Many, .C => hexadecimal_format.format(writer, @intFromPtr(value)),
|
|
||||||
.One => if (pointer.child == []const u8) ona.io.write_all(writer, *value) else hexadecimal_format.format(writer, @intFromPtr(value)),
|
|
||||||
.Slice => if (pointer.child == u8) ona.io.write_all(writer, value) else @compileError(unformattableMessage(Value)),
|
|
||||||
},
|
|
||||||
|
|
||||||
else => @compileError(unformattableMessage(Value)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const root = @This();
|
|
||||||
|
|
||||||
fn unformattableMessage(comptime Value: type) []const u8 {
|
|
||||||
return "type `" ++ @typeName(Value) ++ "` is not formattable with this formatter";
|
|
||||||
}
|
|
3
src/tests.zig
Normal file
3
src/tests.zig
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
test {
|
||||||
|
_ = @import("coral/bytes/formatting.zig");
|
||||||
|
}
|
BIN
testing.spv
(Stored with Git LFS)
Normal file
BIN
testing.spv
(Stored with Git LFS)
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user