diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 0e455b8..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "ext/spirv-cross"] - path = ext/spirv-cross - url = https://github.com/KhronosGroup/SPIRV-Cross/ diff --git a/.vscode/launch.json b/.vscode/launch.json index a9c3ffc..9549ba8 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,14 +1,24 @@ { - "version": "0.2.0", - "configurations": [ - { - "name": "Runner", - "type": "gdb", - "request": "launch", - "target": "${workspaceRoot}/demos/canvas.out", - "cwd": "${workspaceRoot}/demos/", - "valuesFormatting": "prettyPrinters", - "preLaunchTask": "Build All" - }, - ] -} + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Test", + "type": "gdb", + "request": "launch", + "target": "${workspaceRoot}/zig-out/bin/test", + "cwd": "${workspaceRoot}/src/demos/", + "valuesFormatting": "parseText", + "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" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 1d3cc37..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -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, - }, -} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ad1224d..f47f0ff 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,15 +1,17 @@ { - "version": "2.0.0", - "tasks": [ - { - "label": "Build All", - "type": "shell", - "command": "zig build", - "group": { - "kind": "build", - "isDefault": true - }, - "problemMatcher": "$zig", + "version": "2.0.0", + "tasks": [ + { + "label": "Build", + "type": "shell", + "command": "zig build", + "problemMatcher": "$zig", + + "group": { + "kind": "build", + "isDefault": true + }, + "presentation": { "echo": true, "reveal": "silent", @@ -19,6 +21,6 @@ "clear": true, "revealProblems": "onProblem", } - } - ] -} + }, + ] +} \ No newline at end of file diff --git a/.zigversion b/.zigversion new file mode 100644 index 0000000..0548fb4 --- /dev/null +++ b/.zigversion @@ -0,0 +1 @@ +0.14.0 \ No newline at end of file diff --git a/build.zig b/build.zig index afc70bb..396abde 100644 --- a/build.zig +++ b/build.zig @@ -1,273 +1,132 @@ -const builtin = @import("builtin"); - const std = @import("std"); -const ImportList = std.ArrayList(struct { - name: []const u8, - module: *std.Build.Module -}); - -const Project = struct { - target: std.Build.ResolvedTarget, - optimize: std.builtin.OptimizeMode, - imports: ImportList, - - 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; - } +const CommonArgs = struct { + target: std.Build.ResolvedTarget, + optimize: std.builtin.OptimizeMode, + ona_module: *std.Build.Module, }; -pub fn build(b: *std.Build) !void { - var project = Project{ - .imports = ImportList.init(b.allocator), - .target = b.standardTargetOptions(.{}), - .optimize = b.standardOptimizeOption(.{}), - }; +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); - const sokol_dependency = b.dependency("sokol", .{ - .target = project.target, - .optimize = project.optimize, - }); + const coral_module = b.addModule("coral", .{ + .root_source_file = b.path("src/coral/coral.zig"), + .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"), + .target = target, + .optimize = optimize, + .link_libc = true, + }); - const hid_module = try project.add_module(b, "hid", .{ - .imports = &.{ - .{ - .name = "ona", - .module = ona_module, - }, - }, - }); + ext_module.linkSystemLibrary("SDL3", .{ + .needed = true, + .preferred_link_mode = .dynamic, + }); - const gfx_module = try project.add_module(b, "gfx", .{ - .imports = &.{ - .{ - .name = "sokol", - .module = sokol_dependency.module("sokol"), - }, + const ona_module = b.addModule("ona", .{ + .root_source_file = b.path("src/ona/ona.zig"), + .target = target, + .optimize = optimize, + .link_libc = true, - .{ - .name = "ona", - .module = ona_module, - }, + .imports = &.{ + .{ + .name = "ext", + .module = ext_module, + }, + .{ + .name = "coral", + .module = coral_module, + }, + }, + }); - .{ - .name = "hid", - .module = hid_module, - }, - }, - }); + scan_demos(b, .{ + .target = target, + .optimize = optimize, + .ona_module = ona_module, + }); - gfx_module.addIncludePath(b.path("ext/")); + const test_tests = b.addTest(.{ + .root_module = b.createModule(.{ + .root_source_file = b.path("src/tests.zig"), + .target = target, + .optimize = optimize, - gfx_module.linkLibrary(spirv_cross: { - const dir = "ext/spirv-cross/"; + .imports = &.{ + .{ + .name = "coral", + .module = coral_module, + }, + }, + }), + }); - 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 test_step = b.step("test", "Run unit tests"); - 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, - .preferred_link_mode = .dynamic, - }); - - gfx_module.link_libc = true; - - _ = try project.add_module(b, "gui", .{ - .imports = &.{ - .{ - .name = "ona", - .module = ona_module, - }, - - .{ - .name = "gfx", - .module = gfx_module, - }, - }, - }); - - project.find_demos(b.step("demos", "Build demos")); - project.find_tests(b.step("tests", "Build and run 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); + } } diff --git a/build.zig.zon b/build.zig.zon index c40efa8..2087239 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,25 +1,12 @@ .{ - .name = "ona", - .version = "0.0.1", - .minimum_zig_version = "0.13.0", + .name = .ona, + .version = "0.0.0", + .fingerprint = 0x7d0142e88b22421d, + .minimum_zig_version = "0.14.0", - .paths = .{ - "src", - "ext", - "build.zig", - "build.zig.zon", - "LICENSE", - "README.md", - }, - - .dependencies = .{ - .sokol = .{ - .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27", - .hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569", - }, - - .@"spirv-cross" = . { - .path = "./ext/spirv-cross", - }, - }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, } diff --git a/demos/canvas.zig b/demos/canvas.zig deleted file mode 100644 index c31f7d8..0000000 --- a/demos/canvas.zig +++ /dev/null @@ -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); -} - diff --git a/demos/crt.frag b/demos/crt.frag deleted file mode 100644 index d8d3c8c..0000000 --- a/demos/crt.frag +++ /dev/null @@ -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); -} diff --git a/demos/crt.frag.spv b/demos/crt.frag.spv deleted file mode 100644 index 31a5259..0000000 --- a/demos/crt.frag.spv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43f845adaa07403eae9fa02124fa96f4cf70a5b1107fb45fefdb6caca586ebd7 -size 3352 diff --git a/demos/effects.zig b/demos/effects.zig deleted file mode 100644 index c159fc6..0000000 --- a/demos/effects.zig +++ /dev/null @@ -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}, - }); -} - diff --git a/demos/inputs.zig b/demos/inputs.zig deleted file mode 100644 index e34c59b..0000000 --- a/demos/inputs.zig +++ /dev/null @@ -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)), - }); - } -} diff --git a/ext/spirv-cross b/ext/spirv-cross deleted file mode 160000 index 6fd1f75..0000000 --- a/ext/spirv-cross +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 6fd1f75636b1c424b809ad8a84804654cf5ae48b diff --git a/readme.md b/readme.md index 3842aaf..ef57137 100644 --- a/readme.md +++ b/readme.md @@ -13,9 +13,9 @@ ## 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 @@ -36,32 +36,25 @@ Ona is also the Catalan word for "wave". 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. - * SPIR-V shader compilation system utilities available from the system path, namely `glslangValidator`. * 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. ### 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 (`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. +The unit testing suite for the engine modules may be built and ran via `zig build tests`. ### 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. - 2. 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. - 4. Your `build.zig.zon` should, at minimum, contain the following dependency listed in the dependencies section: + 1. Create a new Zig project if you have not already. + 2. Create a `build.zig.zon` accompanying your `build.zig` if you do not already have one. + 3. Your `build.zig.zon` should, at minimum, contain the following dependency listed in the dependencies section: ```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 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("gfx", ona_dependency.module("gfx")); -app.root_module.addImport("hid", ona_dependency.module("hid")); +app.root_module.addImport("coral", ona_dependency.module("coral")); b.installArtifact(app); ``` - 6. Create a `main.zig` containing a valid Ona app declaration. - 7. Run `zig build` to build your new game application. + 5. Create a `main.zig` containing a valid Ona app declaration. + 6. Run `zig build` to build your new game application. diff --git a/src/coral/Box.zig b/src/coral/Box.zig new file mode 100644 index 0000000..85eb441 --- /dev/null +++ b/src/coral/Box.zig @@ -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, + }; +} diff --git a/src/coral/TypeId.zig b/src/coral/TypeId.zig new file mode 100644 index 0000000..9f5e715 --- /dev/null +++ b/src/coral/TypeId.zig @@ -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; +} diff --git a/src/coral/asio.zig b/src/coral/asio.zig new file mode 100644 index 0000000..6af2a20 --- /dev/null +++ b/src/coral/asio.zig @@ -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(); + } +} diff --git a/src/coral/bytes.zig b/src/coral/bytes.zig new file mode 100644 index 0000000..9c22b9e --- /dev/null +++ b/src/coral/bytes.zig @@ -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]); +} diff --git a/src/coral/bytes/formatting.zig b/src/coral/bytes/formatting.zig new file mode 100644 index 0000000..00e80d0 --- /dev/null +++ b/src/coral/bytes/formatting.zig @@ -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); +} diff --git a/src/coral/coral.zig b/src/coral/coral.zig new file mode 100644 index 0000000..e180d93 --- /dev/null +++ b/src/coral/coral.zig @@ -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)}); + }; +} diff --git a/src/coral/hashes.zig b/src/coral/hashes.zig new file mode 100644 index 0000000..54dddec --- /dev/null +++ b/src/coral/hashes.zig @@ -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; +} diff --git a/src/coral/heap.zig b/src/coral/heap.zig new file mode 100644 index 0000000..a73c3d5 --- /dev/null +++ b/src/coral/heap.zig @@ -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(); +} diff --git a/src/coral/map.zig b/src/coral/map.zig new file mode 100644 index 0000000..5f02523 --- /dev/null +++ b/src/coral/map.zig @@ -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, + }; +}; diff --git a/src/coral/scalars.zig b/src/coral/scalars.zig new file mode 100644 index 0000000..1fce951 --- /dev/null +++ b/src/coral/scalars.zig @@ -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; +} diff --git a/src/coral/shaders.zig b/src/coral/shaders.zig new file mode 100644 index 0000000..77c5d80 --- /dev/null +++ b/src/coral/shaders.zig @@ -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; diff --git a/src/coral/shaders/Field.zig b/src/coral/shaders/Field.zig new file mode 100644 index 0000000..c1e56a4 --- /dev/null +++ b/src/coral/shaders/Field.zig @@ -0,0 +1,1782 @@ +const coral = @import("../coral.zig"); + +const std = @import("std"); + +identifier: [:0]const u8, +type: *const coral.shaders.Type, +has_next: ?*const Self = null, + +const Self = @This(); + +pub fn fieldsOf(comptime Aggregate: type) ?*const Self { + return switch (@typeInfo(Aggregate)) { + .@"union" => |@"union"| unionFields(@"union".fields), + + .@"struct" => |@"struct"| switch (@"struct".is_tuple) { + false => structFields(@"struct".fields), + true => @compileError("tuple types are unsupported as their fields are unidentifierd"), + }, + + else => @compileError(std.fmt.comptimePrint("non-aggregate types like {s} are unsupported as they do not contain fields", .{ + @typeName(Aggregate), + })), + }; +} + +pub fn has(self: *const Self, identifier: []const u8) ?*const Self { + var field = self; + + while (true) { + if (std.mem.eql(u8, field.identifier, identifier)) { + return field; + } + + field = field.has_next orelse { + return null; + }; + } +} + +fn structFields(comptime fields: []const std.builtin.Type.StructField) ?*const Self { + if (fields.len != 0) { + const field = fields[0]; + + const struct_fields = struct { + const instance = Self{ + .identifier = field.name, + .type = typeOf(field.type), + .has_next = structFields(fields[1..]), + }; + }; + + return &struct_fields.instance; + } + + return null; +} + +pub fn typeOf(comptime FieldType: type) *const coral.shaders.Type { + return switch (FieldType) { + i32 => .int, + f32 => .float, + else => @compileError(std.fmt.comptimePrint("{s} is not a valid field type", .{@typeName(FieldType)})), + }; +} + +fn unionFields(comptime fields: []const std.builtin.Type.UnionField) ?*const Self { + if (fields.len != 0) { + const field = fields[0]; + + const uniform_fields = struct { + const instance = Self{ + .identifier = field.name, + .type = typeOf(field.type), + .has_next = structFields(fields[1..]), + }; + }; + + return &uniform_fields.instance; + } + + return null; +} + +pub const vector_x = &Self{ + .identifier = "x", + .type = .float, +}; + +pub const vector_y = &Self{ + .identifier = "y", + .type = .float, +}; + +pub const vector_z = &Self{ + .identifier = "z", + .type = .float, +}; + +pub const vector_w = &Self{ + .identifier = "z", + .type = .float, +}; + +pub const vector_xx = &Self{ + .identifier = "xx", + .type = .float2, +}; + +pub const vector_xy = &Self{ + .identifier = "xy", + .type = .float2, +}; + +pub const vector_xz = &Self{ + .identifier = "xz", + .type = .float2, +}; + +pub const vector_xw = &Self{ + .identifier = "xz", + .type = .float2, +}; + +pub const vector_yx = &Self{ + .identifier = "yx", + .type = .float2, +}; + +pub const vector_yy = &Self{ + .identifier = "yy", + .type = .float2, +}; + +pub const vector_yz = &Self{ + .identifier = "yz", + .type = .float2, +}; + +pub const vector_yw = &Self{ + .identifier = "yz", + .type = .float2, +}; + +pub const vector_zx = &Self{ + .identifier = "zx", + .type = .float2, +}; + +pub const vector_zy = &Self{ + .identifier = "zy", + .type = .float2, +}; + +pub const vector_zz = &Self{ + .identifier = "zz", + .type = .float2, +}; + +pub const vector_zw = &Self{ + .identifier = "zw", + .type = .float2, +}; + +pub const vector_wx = &Self{ + .identifier = "wx", + .type = .float2, +}; + +pub const vector_wy = &Self{ + .identifier = "wy", + .type = .float2, +}; + +pub const vector_wz = &Self{ + .identifier = "wz", + .type = .float2, +}; + +pub const vector_ww = &Self{ + .identifier = "ww", + .type = .float2, +}; + +pub const vector_xxx = &Self{ + .identifier = "xxx", + .type = .float3, +}; + +pub const vector_xyx = &Self{ + .identifier = "xyx", + .type = .float3, +}; + +pub const vector_xzx = &Self{ + .identifier = "xzx", + .type = .float3, +}; + +pub const vector_xwx = &Self{ + .identifier = "xwx", + .type = .float3, +}; + +pub const vector_yxx = &Self{ + .identifier = "yxx", + .type = .float3, +}; + +pub const vector_yyx = &Self{ + .identifier = "yyx", + .type = .float3, +}; + +pub const vector_yzx = &Self{ + .identifier = "yzx", + .type = .float3, +}; + +pub const vector_ywx = &Self{ + .identifier = "ywx", + .type = .float3, +}; + +pub const vector_zxx = &Self{ + .identifier = "zxx", + .type = .float3, +}; + +pub const vector_zyx = &Self{ + .identifier = "zyx", + .type = .float3, +}; + +pub const vector_zzx = &Self{ + .identifier = "zzx", + .type = .float3, +}; + +pub const vector_zwx = &Self{ + .identifier = "zwx", + .type = .float3, +}; + +pub const vector_wxx = &Self{ + .identifier = "wxx", + .type = .float3, +}; + +pub const vector_wyx = &Self{ + .identifier = "wyy", + .type = .float3, +}; + +pub const vector_wzx = &Self{ + .identifier = "wzx", + .type = .float3, +}; + +pub const vector_wwx = &Self{ + .identifier = "wwx", + .type = .float3, +}; + +pub const vector_xxy = &Self{ + .identifier = "xxy", + .type = .float3, +}; + +pub const vector_xyy = &Self{ + .identifier = "xyy", + .type = .float3, +}; + +pub const vector_xzy = &Self{ + .identifier = "xzy", + .type = .float3, +}; + +pub const vector_xwy = &Self{ + .identifier = "xwy", + .type = .float3, +}; + +pub const vector_yxy = &Self{ + .identifier = "yxy", + .type = .float3, +}; + +pub const vector_yyy = &Self{ + .identifier = "yyy", + .type = .float3, +}; + +pub const vector_yzy = &Self{ + .identifier = "yzy", + .type = .float3, +}; + +pub const vector_ywy = &Self{ + .identifier = "ywy", + .type = .float3, +}; + +pub const vector_zxy = &Self{ + .identifier = "zxy", + .type = .float3, +}; + +pub const vector_zyy = &Self{ + .identifier = "zyy", + .type = .float3, +}; + +pub const vector_zzy = &Self{ + .identifier = "zzy", + .type = .float3, +}; + +pub const vector_zwy = &Self{ + .identifier = "zwy", + .type = .float3, +}; + +pub const vector_wxy = &Self{ + .identifier = "wxy", + .type = .float3, +}; + +pub const vector_wyy = &Self{ + .identifier = "wyy", + .type = .float3, +}; + +pub const vector_wzy = &Self{ + .identifier = "wzy", + .type = .float3, +}; + +pub const vector_wwy = &Self{ + .identifier = "wwy", + .type = .float3, +}; + +pub const vector_xxz = &Self{ + .identifier = "xxz", + .type = .float3, +}; + +pub const vector_xyz = &Self{ + .identifier = "xyz", + .type = .float3, +}; + +pub const vector_xzz = &Self{ + .identifier = "xzz", + .type = .float3, +}; + +pub const vector_xwz = &Self{ + .identifier = "xwz", + .type = .float3, +}; + +pub const vector_yxz = &Self{ + .identifier = "yxz", + .type = .float3, +}; + +pub const vector_yyz = &Self{ + .identifier = "yyz", + .type = .float3, +}; + +pub const vector_yzz = &Self{ + .identifier = "yzz", + .type = .float3, +}; + +pub const vector_ywz = &Self{ + .identifier = "ywz", + .type = .float3, +}; + +pub const vector_zxz = &Self{ + .identifier = "zxz", + .type = .float3, +}; + +pub const vector_zyz = &Self{ + .identifier = "zyz", + .type = .float3, +}; + +pub const vector_zzz = &Self{ + .identifier = "zzz", + .type = .float3, +}; + +pub const vector_zwz = &Self{ + .identifier = "zwz", + .type = .float3, +}; + +pub const vector_wxz = &Self{ + .identifier = "wxz", + .type = .float3, +}; + +pub const vector_wyz = &Self{ + .identifier = "wyz", + .type = .float3, +}; + +pub const vector_wzz = &Self{ + .identifier = "wzz", + .type = .float3, +}; + +pub const vector_wwz = &Self{ + .identifier = "wwz", + .type = .float3, +}; + +pub const vector_xxw = &Self{ + .identifier = "xxw", + .type = .float3, +}; + +pub const vector_xyw = &Self{ + .identifier = "xyw", + .type = .float3, +}; + +pub const vector_xzw = &Self{ + .identifier = "xzw", + .type = .float3, +}; + +pub const vector_xww = &Self{ + .identifier = "xww", + .type = .float3, +}; + +pub const vector_yxw = &Self{ + .identifier = "yxw", + .type = .float3, +}; + +pub const vector_yyw = &Self{ + .identifier = "yyw", + .type = .float3, +}; + +pub const vector_yzw = &Self{ + .identifier = "yzw", + .type = .float3, +}; + +pub const vector_yww = &Self{ + .identifier = "yww", + .type = .float3, +}; + +pub const vector_zxw = &Self{ + .identifier = "zxw", + .type = .float3, +}; + +pub const vector_zyw = &Self{ + .identifier = "zyw", + .type = .float3, +}; + +pub const vector_zzw = &Self{ + .identifier = "zzw", + .type = .float3, +}; + +pub const vector_zww = &Self{ + .identifier = "zww", + .type = .float3, +}; + +pub const vector_wxw = &Self{ + .identifier = "wxw", + .type = .float3, +}; + +pub const vector_wyw = &Self{ + .identifier = "wyw", + .type = .float3, +}; + +pub const vector_wzw = &Self{ + .identifier = "wzw", + .type = .float3, +}; + +pub const vector_www = &Self{ + .identifier = "www", + .type = .float3, +}; + +pub const vector_xxxx = &Self{ + .identifier = "xxxx", + .type = .float4, +}; + +pub const vector_xxxy = &Self{ + .identifier = "xxxy", + .type = .float4, +}; + +pub const vector_xxxz = &Self{ + .identifier = "xxxz", + .type = .float4, +}; + +pub const vector_xxxw = &Self{ + .identifier = "xxxw", + .type = .float4, +}; + +pub const vector_xxyx = &Self{ + .identifier = "xxyx", + .type = .float4, +}; + +pub const vector_xxyy = &Self{ + .identifier = "xxyy", + .type = .float4, +}; + +pub const vector_xxyz = &Self{ + .identifier = "xxyz", + .type = .float4, +}; + +pub const vector_xxyw = &Self{ + .identifier = "xxyw", + .type = .float4, +}; + +pub const vector_xxzx = &Self{ + .identifier = "xxzx", + .type = .float4, +}; + +pub const vector_xxzy = &Self{ + .identifier = "xxzy", + .type = .float4, +}; + +pub const vector_xxzz = &Self{ + .identifier = "xxzz", + .type = .float4, +}; + +pub const vector_xxzw = &Self{ + .identifier = "xxzw", + .type = .float4, +}; + +pub const vector_xxwx = &Self{ + .identifier = "xxwx", + .type = .float4, +}; + +pub const vector_xxwy = &Self{ + .identifier = "xxwy", + .type = .float4, +}; + +pub const vector_xxwz = &Self{ + .identifier = "xxwz", + .type = .float4, +}; + +pub const vector_xxww = &Self{ + .identifier = "xxww", + .type = .float4, +}; + +pub const vector_xyxx = &Self{ + .identifier = "xyxx", + .type = .float4, +}; + +pub const vector_xyxy = &Self{ + .identifier = "xyxy", + .type = .float4, +}; + +pub const vector_xyxz = &Self{ + .identifier = "xyxz", + .type = .float4, +}; + +pub const vector_xyxw = &Self{ + .identifier = "xyxw", + .type = .float4, +}; + +pub const vector_xyyx = &Self{ + .identifier = "xyyx", + .type = .float4, +}; + +pub const vector_xyyy = &Self{ + .identifier = "xyyy", + .type = .float4, +}; + +pub const vector_xyyz = &Self{ + .identifier = "xyyz", + .type = .float4, +}; + +pub const vector_xyyw = &Self{ + .identifier = "xyyw", + .type = .float4, +}; + +pub const vector_xyzx = &Self{ + .identifier = "xyzx", + .type = .float4, +}; + +pub const vector_xyzy = &Self{ + .identifier = "xyzy", + .type = .float4, +}; + +pub const vector_xyzz = &Self{ + .identifier = "xyzz", + .type = .float4, +}; + +pub const vector_xyzw = &Self{ + .identifier = "xyzw", + .type = .float4, +}; + +pub const vector_xywx = &Self{ + .identifier = "xywx", + .type = .float4, +}; + +pub const vector_xywy = &Self{ + .identifier = "xywy", + .type = .float4, +}; + +pub const vector_xywz = &Self{ + .identifier = "xywz", + .type = .float4, +}; + +pub const vector_xyww = &Self{ + .identifier = "xyww", + .type = .float4, +}; + +pub const vector_xzxx = &Self{ + .identifier = "xzxx", + .type = .float4, +}; + +pub const vector_xzxy = &Self{ + .identifier = "xzxy", + .type = .float4, +}; + +pub const vector_xzxz = &Self{ + .identifier = "xzxz", + .type = .float4, +}; + +pub const vector_xzxw = &Self{ + .identifier = "xzxw", + .type = .float4, +}; + +pub const vector_xzyx = &Self{ + .identifier = "xzyx", + .type = .float4, +}; + +pub const vector_xzyy = &Self{ + .identifier = "xzyy", + .type = .float4, +}; + +pub const vector_xzyz = &Self{ + .identifier = "xzyz", + .type = .float4, +}; + +pub const vector_xzyw = &Self{ + .identifier = "xzyw", + .type = .float4, +}; + +pub const vector_xzzx = &Self{ + .identifier = "xzzx", + .type = .float4, +}; + +pub const vector_xzzy = &Self{ + .identifier = "xzzy", + .type = .float4, +}; + +pub const vector_xzzz = &Self{ + .identifier = "xzzz", + .type = .float4, +}; + +pub const vector_xzzw = &Self{ + .identifier = "xzzw", + .type = .float4, +}; + +pub const vector_xzwx = &Self{ + .identifier = "xzwx", + .type = .float4, +}; + +pub const vector_xzwy = &Self{ + .identifier = "xzwy", + .type = .float4, +}; + +pub const vector_xzwz = &Self{ + .identifier = "xzwz", + .type = .float4, +}; + +pub const vector_xzww = &Self{ + .identifier = "xzww", + .type = .float4, +}; + +pub const vector_xwxx = &Self{ + .identifier = "xwxx", + .type = .float4, +}; + +pub const vector_xwxy = &Self{ + .identifier = "xwxy", + .type = .float4, +}; + +pub const vector_xwxz = &Self{ + .identifier = "xwxz", + .type = .float4, +}; + +pub const vector_xwxw = &Self{ + .identifier = "xwxw", + .type = .float4, +}; + +pub const vector_xwyx = &Self{ + .identifier = "xwyx", + .type = .float4, +}; + +pub const vector_xwyy = &Self{ + .identifier = "xwyy", + .type = .float4, +}; + +pub const vector_xwyz = &Self{ + .identifier = "xwyz", + .type = .float4, +}; + +pub const vector_xwyw = &Self{ + .identifier = "xwyw", + .type = .float4, +}; + +pub const vector_xwzx = &Self{ + .identifier = "xwzx", + .type = .float4, +}; + +pub const vector_xwzy = &Self{ + .identifier = "xwzy", + .type = .float4, +}; + +pub const vector_xwzz = &Self{ + .identifier = "xwzz", + .type = .float4, +}; + +pub const vector_xwzw = &Self{ + .identifier = "xwzw", + .type = .float4, +}; + +pub const vector_xwwx = &Self{ + .identifier = "xwwx", + .type = .float4, +}; + +pub const vector_xwwy = &Self{ + .identifier = "xwwy", + .type = .float4, +}; + +pub const vector_xwwz = &Self{ + .identifier = "xwwz", + .type = .float4, +}; + +pub const vector_xwww = &Self{ + .identifier = "xwww", + .type = .float4, +}; + +pub const vector_wxxx = &Self{ + .identifier = "wxxx", + .type = .float4, +}; + +pub const vector_wxxy = &Self{ + .identifier = "wxxy", + .type = .float4, +}; + +pub const vector_wxxz = &Self{ + .identifier = "wxxz", + .type = .float4, +}; + +pub const vector_wxxw = &Self{ + .identifier = "wxxw", + .type = .float4, +}; + +pub const vector_wxyx = &Self{ + .identifier = "wxyx", + .type = .float4, +}; + +pub const vector_wxyy = &Self{ + .identifier = "wxyy", + .type = .float4, +}; + +pub const vector_wxyz = &Self{ + .identifier = "wxyz", + .type = .float4, +}; + +pub const vector_wxyw = &Self{ + .identifier = "wxyw", + .type = .float4, +}; + +pub const vector_wxzx = &Self{ + .identifier = "wxzx", + .type = .float4, +}; + +pub const vector_wxzy = &Self{ + .identifier = "wxzy", + .type = .float4, +}; + +pub const vector_wxzz = &Self{ + .identifier = "wxzz", + .type = .float4, +}; + +pub const vector_wxzw = &Self{ + .identifier = "wxzw", + .type = .float4, +}; + +pub const vector_wxwx = &Self{ + .identifier = "wxwx", + .type = .float4, +}; + +pub const vector_wxwy = &Self{ + .identifier = "wxwy", + .type = .float4, +}; + +pub const vector_wxwz = &Self{ + .identifier = "wxwz", + .type = .float4, +}; + +pub const vector_wxww = &Self{ + .identifier = "wxww", + .type = .float4, +}; + +pub const vector_wyxx = &Self{ + .identifier = "wyxx", + .type = .float4, +}; + +pub const vector_wyxy = &Self{ + .identifier = "wyxy", + .type = .float4, +}; + +pub const vector_wyxz = &Self{ + .identifier = "wyxz", + .type = .float4, +}; + +pub const vector_wyxw = &Self{ + .identifier = "wyxw", + .type = .float4, +}; + +pub const vector_wyyx = &Self{ + .identifier = "wyyx", + .type = .float4, +}; + +pub const vector_wyyy = &Self{ + .identifier = "wyyy", + .type = .float4, +}; + +pub const vector_wyyz = &Self{ + .identifier = "wyyz", + .type = .float4, +}; + +pub const vector_wyyw = &Self{ + .identifier = "wyyw", + .type = .float4, +}; + +pub const vector_wyzx = &Self{ + .identifier = "wyzx", + .type = .float4, +}; + +pub const vector_wyzy = &Self{ + .identifier = "wyzy", + .type = .float4, +}; + +pub const vector_wyzz = &Self{ + .identifier = "wyzz", + .type = .float4, +}; + +pub const vector_wyzw = &Self{ + .identifier = "wyzw", + .type = .float4, +}; + +pub const vector_wywx = &Self{ + .identifier = "wywx", + .type = .float4, +}; + +pub const vector_wywy = &Self{ + .identifier = "wywy", + .type = .float4, +}; + +pub const vector_wywz = &Self{ + .identifier = "wywz", + .type = .float4, +}; + +pub const vector_wyww = &Self{ + .identifier = "wyww", + .type = .float4, +}; + +pub const vector_wzxx = &Self{ + .identifier = "wzxx", + .type = .float4, +}; + +pub const vector_wzxy = &Self{ + .identifier = "wzxy", + .type = .float4, +}; + +pub const vector_wzxz = &Self{ + .identifier = "wzxz", + .type = .float4, +}; + +pub const vector_wzxw = &Self{ + .identifier = "wzxw", + .type = .float4, +}; + +pub const vector_wzyx = &Self{ + .identifier = "wzyx", + .type = .float4, +}; + +pub const vector_wzyy = &Self{ + .identifier = "wzyy", + .type = .float4, +}; + +pub const vector_wzyz = &Self{ + .identifier = "wzyz", + .type = .float4, +}; + +pub const vector_wzyw = &Self{ + .identifier = "wzyw", + .type = .float4, +}; + +pub const vector_wzzx = &Self{ + .identifier = "wzzx", + .type = .float4, +}; + +pub const vector_wzzy = &Self{ + .identifier = "wzzy", + .type = .float4, +}; + +pub const vector_wzzz = &Self{ + .identifier = "wzzz", + .type = .float4, +}; + +pub const vector_wzzw = &Self{ + .identifier = "wzzw", + .type = .float4, +}; + +pub const vector_wzwx = &Self{ + .identifier = "wzwx", + .type = .float4, +}; + +pub const vector_wzwy = &Self{ + .identifier = "wzwy", + .type = .float4, +}; + +pub const vector_wzwz = &Self{ + .identifier = "wzwz", + .type = .float4, +}; + +pub const vector_wzww = &Self{ + .identifier = "wzww", + .type = .float4, +}; + +pub const vector_wwxx = &Self{ + .identifier = "wwxx", + .type = .float4, +}; + +pub const vector_wwxy = &Self{ + .identifier = "wwxy", + .type = .float4, +}; + +pub const vector_wwxz = &Self{ + .identifier = "wwxz", + .type = .float4, +}; + +pub const vector_wwxw = &Self{ + .identifier = "wwxw", + .type = .float4, +}; + +pub const vector_wwyx = &Self{ + .identifier = "wwyx", + .type = .float4, +}; + +pub const vector_wwyy = &Self{ + .identifier = "wwyy", + .type = .float4, +}; + +pub const vector_wwyz = &Self{ + .identifier = "wwyz", + .type = .float4, +}; + +pub const vector_wwyw = &Self{ + .identifier = "wwyw", + .type = .float4, +}; + +pub const vector_wwzx = &Self{ + .identifier = "wwzx", + .type = .float4, +}; + +pub const vector_wwzy = &Self{ + .identifier = "wwzy", + .type = .float4, +}; + +pub const vector_wwzz = &Self{ + .identifier = "wwzz", + .type = .float4, +}; + +pub const vector_wwzw = &Self{ + .identifier = "wwzw", + .type = .float4, +}; + +pub const vector_wwwx = &Self{ + .identifier = "wwwx", + .type = .float4, +}; + +pub const vector_wwwy = &Self{ + .identifier = "wwwy", + .type = .float4, +}; + +pub const vector_wwwz = &Self{ + .identifier = "wwwz", + .type = .float4, +}; + +pub const vector_wwww = &Self{ + .identifier = "wwww", + .type = .float4, +}; + +pub const vector_yxxx = &Self{ + .identifier = "yxxx", + .type = .float4, +}; + +pub const vector_yxxy = &Self{ + .identifier = "yxxy", + .type = .float4, +}; + +pub const vector_yxxz = &Self{ + .identifier = "yxxz", + .type = .float4, +}; + +pub const vector_yxxw = &Self{ + .identifier = "yxxw", + .type = .float4, +}; + +pub const vector_yxyx = &Self{ + .identifier = "yxyx", + .type = .float4, +}; + +pub const vector_yxyy = &Self{ + .identifier = "yxyy", + .type = .float4, +}; + +pub const vector_yxyz = &Self{ + .identifier = "yxyz", + .type = .float4, +}; + +pub const vector_yxyw = &Self{ + .identifier = "yxyw", + .type = .float4, +}; + +pub const vector_yxzx = &Self{ + .identifier = "yxzx", + .type = .float4, +}; + +pub const vector_yxzy = &Self{ + .identifier = "yxzy", + .type = .float4, +}; + +pub const vector_yxzz = &Self{ + .identifier = "yxzz", + .type = .float4, +}; + +pub const vector_yxzw = &Self{ + .identifier = "yxzw", + .type = .float4, +}; + +pub const vector_yxwx = &Self{ + .identifier = "yxwx", + .type = .float4, +}; + +pub const vector_yxwy = &Self{ + .identifier = "yxwy", + .type = .float4, +}; + +pub const vector_yxwz = &Self{ + .identifier = "yxwz", + .type = .float4, +}; + +pub const vector_yxww = &Self{ + .identifier = "yxww", + .type = .float4, +}; + +pub const vector_yyxx = &Self{ + .identifier = "yyxx", + .type = .float4, +}; + +pub const vector_yyxy = &Self{ + .identifier = "yyxy", + .type = .float4, +}; + +pub const vector_yyxz = &Self{ + .identifier = "yyxz", + .type = .float4, +}; + +pub const vector_yyxw = &Self{ + .identifier = "yyxw", + .type = .float4, +}; + +pub const vector_yyyx = &Self{ + .identifier = "yyyx", + .type = .float4, +}; + +pub const vector_yyyy = &Self{ + .identifier = "yyyy", + .type = .float4, +}; + +pub const vector_yyyz = &Self{ + .identifier = "yyyz", + .type = .float4, +}; + +pub const vector_yyyw = &Self{ + .identifier = "yyyw", + .type = .float4, +}; + +pub const vector_yyzx = &Self{ + .identifier = "yyzx", + .type = .float4, +}; + +pub const vector_yyzy = &Self{ + .identifier = "yyzy", + .type = .float4, +}; + +pub const vector_yyzz = &Self{ + .identifier = "yyzz", + .type = .float4, +}; + +pub const vector_yyzw = &Self{ + .identifier = "yyzw", + .type = .float4, +}; + +pub const vector_yywx = &Self{ + .identifier = "yywx", + .type = .float4, +}; + +pub const vector_yywy = &Self{ + .identifier = "yywy", + .type = .float4, +}; + +pub const vector_yywz = &Self{ + .identifier = "yywz", + .type = .float4, +}; + +pub const vector_yyww = &Self{ + .identifier = "yyww", + .type = .float4, +}; + +pub const vector_yzxx = &Self{ + .identifier = "yzxx", + .type = .float4, +}; + +pub const vector_yzxy = &Self{ + .identifier = "yzxy", + .type = .float4, +}; + +pub const vector_yzxz = &Self{ + .identifier = "yzxz", + .type = .float4, +}; + +pub const vector_yzxw = &Self{ + .identifier = "yzxw", + .type = .float4, +}; + +pub const vector_yzyx = &Self{ + .identifier = "yzyx", + .type = .float4, +}; + +pub const vector_yzyy = &Self{ + .identifier = "yzyy", + .type = .float4, +}; + +pub const vector_yzyz = &Self{ + .identifier = "yzyz", + .type = .float4, +}; + +pub const vector_yzyw = &Self{ + .identifier = "yzyw", + .type = .float4, +}; + +pub const vector_yzzx = &Self{ + .identifier = "yzzx", + .type = .float4, +}; + +pub const vector_yzzy = &Self{ + .identifier = "yzzy", + .type = .float4, +}; + +pub const vector_yzzz = &Self{ + .identifier = "yzzz", + .type = .float4, +}; + +pub const vector_yzzw = &Self{ + .identifier = "yzzw", + .type = .float4, +}; + +pub const vector_yzwx = &Self{ + .identifier = "yzwx", + .type = .float4, +}; + +pub const vector_yzwy = &Self{ + .identifier = "yzwy", + .type = .float4, +}; + +pub const vector_yzwz = &Self{ + .identifier = "yzwz", + .type = .float4, +}; + +pub const vector_yzww = &Self{ + .identifier = "yzww", + .type = .float4, +}; + +pub const vector_ywxx = &Self{ + .identifier = "ywxx", + .type = .float4, +}; + +pub const vector_ywxy = &Self{ + .identifier = "ywxy", + .type = .float4, +}; + +pub const vector_ywxz = &Self{ + .identifier = "ywxz", + .type = .float4, +}; + +pub const vector_ywxw = &Self{ + .identifier = "ywxw", + .type = .float4, +}; + +pub const vector_ywyx = &Self{ + .identifier = "ywyx", + .type = .float4, +}; + +pub const vector_ywyy = &Self{ + .identifier = "ywyy", + .type = .float4, +}; + +pub const vector_ywyz = &Self{ + .identifier = "ywyz", + .type = .float4, +}; + +pub const vector_ywyw = &Self{ + .identifier = "ywyw", + .type = .float4, +}; + +pub const vector_ywzx = &Self{ + .identifier = "ywzx", + .type = .float4, +}; + +pub const vector_ywzy = &Self{ + .identifier = "ywzy", + .type = .float4, +}; + +pub const vector_ywzz = &Self{ + .identifier = "ywzz", + .type = .float4, +}; + +pub const vector_ywzw = &Self{ + .identifier = "ywzw", + .type = .float4, +}; + +pub const vector_ywwx = &Self{ + .identifier = "ywwx", + .type = .float4, +}; + +pub const vector_ywwy = &Self{ + .identifier = "ywwy", + .type = .float4, +}; + +pub const vector_ywwz = &Self{ + .identifier = "ywwz", + .type = .float4, +}; + +pub const vector_ywww = &Self{ + .identifier = "ywww", + .type = .float4, +}; + +pub const vector_zxxx = &Self{ + .identifier = "zxxx", + .type = .float4, +}; + +pub const vector_zxxy = &Self{ + .identifier = "zxxy", + .type = .float4, +}; + +pub const vector_zxxz = &Self{ + .identifier = "zxxz", + .type = .float4, +}; + +pub const vector_zxxw = &Self{ + .identifier = "zxxw", + .type = .float4, +}; + +pub const vector_zxyx = &Self{ + .identifier = "zxyx", + .type = .float4, +}; + +pub const vector_zxyy = &Self{ + .identifier = "zxyy", + .type = .float4, +}; + +pub const vector_zxyz = &Self{ + .identifier = "zxyz", + .type = .float4, +}; + +pub const vector_zxyw = &Self{ + .identifier = "zxyw", + .type = .float4, +}; + +pub const vector_zxzx = &Self{ + .identifier = "zxzx", + .type = .float4, +}; + +pub const vector_zxzy = &Self{ + .identifier = "zxzy", + .type = .float4, +}; + +pub const vector_zxzz = &Self{ + .identifier = "zxzz", + .type = .float4, +}; + +pub const vector_zxzw = &Self{ + .identifier = "zxzw", + .type = .float4, +}; + +pub const vector_zxwx = &Self{ + .identifier = "zxwx", + .type = .float4, +}; + +pub const vector_zxwy = &Self{ + .identifier = "zxwy", + .type = .float4, +}; + +pub const vector_zxwz = &Self{ + .identifier = "zxwz", + .type = .float4, +}; + +pub const vector_zxww = &Self{ + .identifier = "zxww", + .type = .float4, +}; + +pub const vector_zyxx = &Self{ + .identifier = "zyxx", + .type = .float4, +}; + +pub const vector_zyxy = &Self{ + .identifier = "zyxy", + .type = .float4, +}; + +pub const vector_zyxz = &Self{ + .identifier = "zyxz", + .type = .float4, +}; + +pub const vector_zyxw = &Self{ + .identifier = "zyxw", + .type = .float4, +}; + +pub const vector_zyyx = &Self{ + .identifier = "zyyx", + .type = .float4, +}; + +pub const vector_zyyy = &Self{ + .identifier = "zyyy", + .type = .float4, +}; + +pub const vector_zyyz = &Self{ + .identifier = "zyyz", + .type = .float4, +}; + +pub const vector_zyyw = &Self{ + .identifier = "zyyw", + .type = .float4, +}; + +pub const vector_zyzx = &Self{ + .identifier = "zyzx", + .type = .float4, +}; + +pub const vector_zyzy = &Self{ + .identifier = "zyzy", + .type = .float4, +}; + +pub const vector_zyzz = &Self{ + .identifier = "zyzz", + .type = .float4, +}; + +pub const vector_zyzw = &Self{ + .identifier = "zyzw", + .type = .float4, +}; + +pub const vector_zywx = &Self{ + .identifier = "zywx", + .type = .float4, +}; + +pub const vector_zywy = &Self{ + .identifier = "zywy", + .type = .float4, +}; + +pub const vector_zywz = &Self{ + .identifier = "zywz", + .type = .float4, +}; + +pub const vector_zyww = &Self{ + .identifier = "zyww", + .type = .float4, +}; + +pub const vector_zzxx = &Self{ + .identifier = "zzxx", + .type = .float4, +}; + +pub const vector_zzxy = &Self{ + .identifier = "zzxy", + .type = .float4, +}; + +pub const vector_zzxz = &Self{ + .identifier = "zzxz", + .type = .float4, +}; + +pub const vector_zzxw = &Self{ + .identifier = "zzxw", + .type = .float4, +}; + +pub const vector_zzyx = &Self{ + .identifier = "zzyx", + .type = .float4, +}; + +pub const vector_zzyy = &Self{ + .identifier = "zzyy", + .type = .float4, +}; + +pub const vector_zzyz = &Self{ + .identifier = "zzyz", + .type = .float4, +}; + +pub const vector_zzyw = &Self{ + .identifier = "zzyw", + .type = .float4, +}; + +pub const vector_zzzx = &Self{ + .identifier = "zzzx", + .type = .float4, +}; + +pub const vector_zzzy = &Self{ + .identifier = "zzzy", + .type = .float4, +}; + +pub const vector_zzzz = &Self{ + .identifier = "zzzz", + .type = .float4, +}; + +pub const vector_zzzw = &Self{ + .identifier = "zzzw", + .type = .float4, +}; + +pub const vector_zzwx = &Self{ + .identifier = "zzwx", + .type = .float4, +}; + +pub const vector_zzwy = &Self{ + .identifier = "zzwy", + .type = .float4, +}; + +pub const vector_zzwz = &Self{ + .identifier = "zzwz", + .type = .float4, +}; + +pub const vector_zzww = &Self{ + .identifier = "zzww", + .type = .float4, +}; + +pub const vector_zwxx = &Self{ + .identifier = "zwxx", + .type = .float4, +}; + +pub const vector_zwxy = &Self{ + .identifier = "zwxy", + .type = .float4, +}; + +pub const vector_zwxz = &Self{ + .identifier = "zwxz", + .type = .float4, +}; + +pub const vector_zwxw = &Self{ + .identifier = "zwxw", + .type = .float4, +}; + +pub const vector_zwyx = &Self{ + .identifier = "zwyx", + .type = .float4, +}; + +pub const vector_zwyy = &Self{ + .identifier = "zwyy", + .type = .float4, +}; + +pub const vector_zwyz = &Self{ + .identifier = "zwyz", + .type = .float4, +}; + +pub const vector_zwyw = &Self{ + .identifier = "zwyw", + .type = .float4, +}; + +pub const vector_zwzx = &Self{ + .identifier = "zwzx", + .type = .float4, +}; + +pub const vector_zwzy = &Self{ + .identifier = "zwzy", + .type = .float4, +}; + +pub const vector_zwzz = &Self{ + .identifier = "zwzz", + .type = .float4, +}; + +pub const vector_zwzw = &Self{ + .identifier = "zwzw", + .type = .float4, +}; + +pub const vector_zwwx = &Self{ + .identifier = "zwwx", + .type = .float4, +}; + +pub const vector_zwwy = &Self{ + .identifier = "zwwy", + .type = .float4, +}; + +pub const vector_zwwz = &Self{ + .identifier = "zwwz", + .type = .float4, +}; + +pub const vector_zwww = &Self{ + .identifier = "zwww", + .type = .float4, +}; diff --git a/src/coral/shaders/Root.zig b/src/coral/shaders/Root.zig new file mode 100644 index 0000000..fa31f20 --- /dev/null +++ b/src/coral/shaders/Root.zig @@ -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; +} diff --git a/src/coral/shaders/Scope.zig b/src/coral/shaders/Scope.zig new file mode 100644 index 0000000..05b86d4 --- /dev/null +++ b/src/coral/shaders/Scope.zig @@ -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, + }; +} diff --git a/src/coral/shaders/Type.zig b/src/coral/shaders/Type.zig new file mode 100644 index 0000000..e47ad8e --- /dev/null +++ b/src/coral/shaders/Type.zig @@ -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, +}; diff --git a/src/coral/shaders/glsl.zig b/src/coral/shaders/glsl.zig new file mode 100644 index 0000000..5cf29aa --- /dev/null +++ b/src/coral/shaders/glsl.zig @@ -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); +} diff --git a/src/coral/shaders/spirv.zig b/src/coral/shaders/spirv.zig new file mode 100644 index 0000000..d92d4ba --- /dev/null +++ b/src/coral/shaders/spirv.zig @@ -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); +} diff --git a/src/coral/shaders/tokens.zig b/src/coral/shaders/tokens.zig new file mode 100644 index 0000000..9d14206 --- /dev/null +++ b/src/coral/shaders/tokens.zig @@ -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; +} diff --git a/src/coral/utf8.zig b/src/coral/utf8.zig new file mode 100644 index 0000000..61428ac --- /dev/null +++ b/src/coral/utf8.zig @@ -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, + }, + }; +} diff --git a/src/demos/graphics.eff b/src/demos/graphics.eff new file mode 100644 index 0000000..9f0b446 --- /dev/null +++ b/src/demos/graphics.eff @@ -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 +} diff --git a/src/demos/graphics.zig b/src/demos/graphics.zig new file mode 100644 index 0000000..f8373d7 --- /dev/null +++ b/src/demos/graphics.zig @@ -0,0 +1,9 @@ +const ona = @import("ona"); + +const std = @import("std"); + +pub fn main() void { + ona.realtime_app + .with(.initModule(ona.gfx)) + .run(); +} diff --git a/src/ext/ext.zig b/src/ext/ext.zig new file mode 100644 index 0000000..09e95f1 --- /dev/null +++ b/src/ext/ext.zig @@ -0,0 +1,3 @@ +pub usingnamespace @cImport({ + @cInclude("SDL3/SDL.h"); +}); diff --git a/src/gfx/Resources.zig b/src/gfx/Resources.zig deleted file mode 100644 index 8bd91cc..0000000 --- a/src/gfx/Resources.zig +++ /dev/null @@ -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; -} diff --git a/src/gfx/gfx.zig b/src/gfx/gfx.zig deleted file mode 100644 index 45b02b2..0000000 --- a/src/gfx/gfx.zig +++ /dev/null @@ -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(); - } -} diff --git a/src/gfx/rendering.zig b/src/gfx/rendering.zig deleted file mode 100644 index 0295011..0000000 --- a/src/gfx/rendering.zig +++ /dev/null @@ -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; diff --git a/src/gfx/shaders/draw_texture.frag b/src/gfx/shaders/draw_texture.frag deleted file mode 100644 index 40e5c20..0000000 --- a/src/gfx/shaders/draw_texture.frag +++ /dev/null @@ -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; - } -} diff --git a/src/gfx/shaders/draw_texture.vert b/src/gfx/shaders/draw_texture.vert deleted file mode 100644 index 8838617..0000000 --- a/src/gfx/shaders/draw_texture.vert +++ /dev/null @@ -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; -} diff --git a/src/gfx/spirv.zig b/src/gfx/spirv.zig deleted file mode 100644 index 96f0209..0000000 --- a/src/gfx/spirv.zig +++ /dev/null @@ -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; -} diff --git a/src/gui/gui.zig b/src/gui/gui.zig deleted file mode 100644 index 087be75..0000000 --- a/src/gui/gui.zig +++ /dev/null @@ -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"}); -} diff --git a/src/hid/hid.zig b/src/hid/hid.zig deleted file mode 100644 index 33a25d7..0000000 --- a/src/hid/hid.zig +++ /dev/null @@ -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; - }, - } - } -} diff --git a/src/ona/App.zig b/src/ona/App.zig new file mode 100644 index 0000000..8b758c9 --- /dev/null +++ b/src/ona/App.zig @@ -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)).?; +} diff --git a/src/ona/App/Behavior.zig b/src/ona/App/Behavior.zig new file mode 100644 index 0000000..c83988b --- /dev/null +++ b/src/ona/App/Behavior.zig @@ -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; +} diff --git a/src/ona/App/Setup.zig b/src/ona/App/Setup.zig new file mode 100644 index 0000000..d4b0eb0 --- /dev/null +++ b/src/ona/App/Setup.zig @@ -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, + }; +} diff --git a/src/ona/App/SystemGraph.zig b/src/ona/App/SystemGraph.zig new file mode 100644 index 0000000..f881afe --- /dev/null +++ b/src/ona/App/SystemGraph.zig @@ -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); + } +} diff --git a/src/ona/Assets.zig b/src/ona/Assets.zig new file mode 100644 index 0000000..395f85e --- /dev/null +++ b/src/ona/Assets.zig @@ -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; +} diff --git a/src/ona/World.zig b/src/ona/World.zig deleted file mode 100644 index 56a47c0..0000000 --- a/src/ona/World.zig +++ /dev/null @@ -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); -} diff --git a/src/ona/ascii.zig b/src/ona/ascii.zig deleted file mode 100644 index dbf220c..0000000 --- a/src/ona/ascii.zig +++ /dev/null @@ -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; - } -}; diff --git a/src/ona/asyncio.zig b/src/ona/asyncio.zig deleted file mode 100644 index 485beab..0000000 --- a/src/ona/asyncio.zig +++ /dev/null @@ -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; - } - }; -} diff --git a/src/ona/dag.zig b/src/ona/dag.zig deleted file mode 100644 index b67c2ed..0000000 --- a/src/ona/dag.zig +++ /dev/null @@ -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; - } -}; diff --git a/src/ona/files.zig b/src/ona/files.zig deleted file mode 100644 index ba96484..0000000 --- a/src/ona/files.zig +++ /dev/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(); diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig new file mode 100644 index 0000000..584c98e --- /dev/null +++ b/src/ona/gfx.zig @@ -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", .{}); + } + } +} diff --git a/src/ona/hashes.zig b/src/ona/hashes.zig deleted file mode 100644 index 9af6e7f..0000000 --- a/src/ona/hashes.zig +++ /dev/null @@ -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; -} diff --git a/src/ona/heap.zig b/src/ona/heap.zig deleted file mode 100644 index c76a18d..0000000 --- a/src/ona/heap.zig +++ /dev/null @@ -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(); -} diff --git a/src/ona/hid.zig b/src/ona/hid.zig new file mode 100644 index 0000000..9b6de62 --- /dev/null +++ b/src/ona/hid.zig @@ -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, + _, +}; diff --git a/src/ona/io.zig b/src/ona/io.zig deleted file mode 100644 index 2ba3154..0000000 --- a/src/ona/io.zig +++ /dev/null @@ -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; - } -} diff --git a/src/ona/lina.zig b/src/ona/lina.zig deleted file mode 100644 index f9dfc87..0000000 --- a/src/ona/lina.zig +++ /dev/null @@ -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; -} - diff --git a/src/ona/map.zig b/src/ona/map.zig deleted file mode 100644 index 8aaa3de..0000000 --- a/src/ona/map.zig +++ /dev/null @@ -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, - }; -}; diff --git a/src/ona/ona.zig b/src/ona/ona.zig index ee65e67..aae1573 100644 --- a/src/ona/ona.zig +++ b/src/ona/ona.zig @@ -1,1007 +1,178 @@ -pub const ascii = @import("./ascii.zig"); +pub const App = @import("./App.zig"); -pub const asyncio = @import("./asyncio.zig"); +pub const Assets = @import("./Assets.zig"); -pub const dag = @import("./dag.zig"); +const coral = @import("coral"); -pub const hashes = @import("./hashes.zig"); +pub const gfx = @import("./gfx.zig"); -pub const heap = @import("./heap.zig"); - -pub const files = @import("./files.zig"); - -pub const io = @import("./io.zig"); - -pub const map = @import("./map.zig"); - -pub const scalars = @import("./scalars.zig"); - -pub const slices = @import("./slices.zig"); - -pub const stack = @import("./stack.zig"); +pub const hid = @import("./hid.zig"); const std = @import("std"); -pub const utf8 = @import("./utf8.zig"); - -pub const World = @import("./World.zig"); - -pub const lina = @import("./lina.zig"); - -pub const App = struct { - step: fn (*World) anyerror!void = noop, - - fn noop(_: *World) !void {} - - pub fn build(self: App) fn () anyerror!void { - const Start = struct { - fn main() anyerror!void { - defer { - heap.trace_leaks(); - } - - parse_args: for (std.os.argv[1 ..]) |arg| { - const arg_span = std.mem.span(arg); - const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len; - const arg_name = arg_span[0 .. arg_split_index]; - - for (LaunchFlag.values) |value| { - const name = @tagName(value); - - if (!std.mem.eql(u8, arg_name, name)) { - continue; - } - - LaunchFlag.args[@intFromEnum(value)] = - if (arg_split_index == arg_span.len) - name - else - arg_span[arg_split_index ..]; - - continue: parse_args; - } - } - - const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| { - @panic(switch (cpu_count_error) { - error.PermissionDenied => "permission denied retrieving CPU count", - error.SystemResources => "system resources are preventing retrieval of the CPU count", - error.Unexpected => "unexpected failure retrieving CPU count", - }); - }, 0, std.math.maxInt(u32)))); - - var world = try World.init(scalars.fractional(cpu_count, @as(f32, 0.75)) orelse 0); - - defer world.deinit(); - - try self.step(&world); - } - }; - - return Start.main; - } - - pub const game = App{.step = run_game}; - - fn run_game(world: *World) !void { - const loop = try world.set_get_state(Loop{ - .target_frame_time = 1.0 / 60.0, - .elapsed_time = 0, - .is_running = true, - }); - - try world.run_event(.load); - - const ticks_initial = std.time.milliTimestamp(); - var ticks_previous = ticks_initial; - var accumulated_time = @as(f64, 0); - - while (loop.is_running) { - const ticks_current = std.time.milliTimestamp(); - const milliseconds_per_second = 1000.0; - const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; - - loop.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second; - ticks_previous = ticks_current; - accumulated_time += delta_time; - - try world.run_event(.pre_update); - - while (accumulated_time >= loop.target_frame_time) : (accumulated_time -= loop.target_frame_time) { - try world.run_event(.update); - } - - try world.run_event(.post_update); - try world.run_event(.render); - try world.run_event(.finish); - } - - try world.run_event(.exit); - } - - pub fn with_module(self: App, comptime Module: type) App { - if (!@hasDecl(Module, "setup")) { - @compileError("`Module` argument must have a setup fn declaration"); - } - - const WithState = struct { - fn step(world: *World) !void { - try Module.setup(world); - try self.step(world); - } - }; - - return .{ - .step = WithState.step, - }; - } - - pub fn with_state(self: App, comptime state: anytype) App { - const WithState = struct { - fn step(world: *World) !void { - try world.set_state(state); - try self.step(world); - } - }; - - return .{ - .step = WithState.step, - }; - } - - pub fn with_system(self: App, comptime event: anytype, comptime info: *const SystemInfo, comptime order: SystemOrder) App { - const WithState = struct { - fn step(world: *World) !void { - try world.on_event(event, info, order); - try self.step(world); - } - }; - - return .{ - .step = WithState.step, - }; - } -}; - -pub const Loop = struct { - target_frame_time: f64, - elapsed_time: f64, - is_running: bool, - - pub fn quit(self: *Loop) void { - self.is_running = false; - } -}; - fn Channel(comptime Message: type) type { - return struct { - buffers: [2]stack.Sequential(Message), - swap_index: u1 = 0, - ticks: u1 = 0, + return struct { + buffers: [2]coral.Stack(Message) = .{ .empty, .empty }, + swap_index: u1 = 0, + ticks: u1 = 0, - const Self = @This(); + const Self = @This(); - fn cleanup(channel: Write(Self)) void { - channel.state.deinit(); - } + pub fn bind(app: *App) std.mem.Allocator.Error!void { + if (app.hasState(Self) == null) { + try app.setState(Self{}); + try app.on(.post_update, .of(swap)); + } + } - pub fn deinit(self: *Self) void { - for (&self.buffers) |*buffer| { - buffer.deinit(); - } + pub fn deinit(self: *Self) void { + for (&self.buffers) |*buffer| { + buffer.deinit(); + } + } - self.* = undefined; - } + fn swap(channel: Write(Self)) void { + channel.ptr.ticks = coral.scalars.add(channel.ptr.ticks, 1) orelse 0; - fn swap(channel: Write(Self)) void { - channel.state.ticks = scalars.add(channel.state.ticks, 1) orelse 0; + if (channel.ptr.ticks == 0) { + channel.ptr.swap_index ^= 1; - if (channel.state.ticks == 0) { - channel.state.swap_index ^= 1; + channel.ptr.buffers[channel.ptr.swap_index].clear(); + } + } - channel.state.buffers[channel.state.swap_index].clear(); - } - } + fn messages(self: Self) []const Message { + return self.buffers[self.swap_index ^ 1].values; + } - fn init(allocator: std.mem.Allocator) Self { - return .{ - .buffers = .{ - .{.allocator = allocator}, - .{.allocator = allocator}, - }, - }; - } - - fn messages(self: Self) []const Message { - return self.buffers[self.swap_index ^ 1].values; - } - - fn push(self: *Self, message: Message) std.mem.Allocator.Error!void { - try self.buffers[self.swap_index].push_grow(message); - } - }; + fn push(self: *Self, message: Message) std.mem.Allocator.Error!void { + try self.buffers[self.swap_index].pushGrow(message); + } + }; } -pub fn Exclusive(comptime values: []const type) type { - comptime var qualifieds: [values.len]type = undefined; +pub fn Read(comptime State: type) type { + return struct { + ptr: *const State, - for (&qualifieds, values) |*qualified, Value| { - qualified.* = switch (@typeInfo(Value)) { - .Optional => @Type(.{ - .Optional = .{ - .child = .{ - .Pointer = .{ - .is_allowzero = false, - .sentinel = null, - .address_space = .generic, - .is_volatile = false, - .alignment = @alignOf(Value), - .size = .One, - .child = Value, - .is_const = false, - }, - }, - }, - }), + const Self = @This(); - else => @Type(.{ - .Pointer = .{ - .is_allowzero = false, - .sentinel = null, - .address_space = .generic, - .is_volatile = false, - .alignment = @alignOf(Value), - .size = .One, - .child = Value, - .is_const = false, - }, - }), - }; - } - - const States = std.meta.Tuple(&qualifieds); - - return struct { - states: States, - world: *World, - - const Self = @This(); - - pub const Param = struct { - states: States, - world: *World, - }; - - pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param { - var param = Param{ - .world = context.world, - .states = undefined, - }; - - inline for (¶m.states, values) |*state, Value| { - const has_state = try context.register_writable_state_access(Value); - - state.* = switch (@typeInfo(Value)) { - .Optional => has_state, - - else => has_state orelse { - @panic(std.fmt.comptimePrint("attempt to use exclusive {s} that has not been set yet", .{ - @typeName(Value), - })); - }, - }; - } - - return param; - } - - pub fn init(param: *Param) Self { - return .{ - .states = param.states, - .world = param.world, - }; - } - - pub const thread_restriction = .main; - }; -} - -pub const LaunchFlag = enum { - dump_shader_translations, - - var args = [_]?[]const u8{null} ** std.enums.values(LaunchFlag).len; - - const values = std.enums.values(LaunchFlag); -}; - -pub fn Params(comptime Value: type) type { - if (!@hasDecl(Value, "Param")) { - @compileError(@typeName(Value) ++ - \\ is missing a public `Params` type declaration. - \\This is required for a type to be used as a system parameter - ); - } - - return struct { - has_head: ?*Node = null, - has_tail: ?*Node = null, - - pub const Node = struct { - param: Value.Param, - has_prev: ?*Node = null, - has_next: ?*Node = null, - }; - }; -} - -pub fn Pool(comptime Value: type) type { - return struct { - entries: stack.Sequential(Entry), - first_free_index: usize = 0, - - const Entry = union (enum) { - free_index: usize, - occupied: Value, - }; - - pub const Values = struct { - cursor: usize = 0, - pool: *const Self, - - pub fn next(self: *Values) ?*Value { - while (self.cursor < self.pool.entries.len()) { - defer self.cursor += 1; - - switch (self.pool.entries.values[self.cursor]) { - .free_index => { - continue; - }, - - .occupied => |*occupied| { - return occupied; - }, - } - } - - return null; - } - }; - - const Self = @This(); - - pub fn deinit(self: *Self) void { - self.entries.deinit(); - - self.* = undefined; - } - - pub fn get(self: *Self, key: usize) ?*Value { - if (key >= self.entries.len()) { - return null; - } - - return switch (self.entries.values[key]) { - .free_index => null, - .occupied => |*occupied| occupied, - }; - } - - pub fn init(allocator: std.mem.Allocator) Self { - return .{ - .entries = .{.allocator = allocator}, - }; - } - - pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize { - const entries_count = self.entries.len(); - - if (self.first_free_index == entries_count) { - try self.entries.push_grow(.{.occupied = value}); - - self.first_free_index += 1; - - return entries_count; - } - - const insersion_index = self.first_free_index; - - self.first_free_index = self.entries.values[self.first_free_index].free_index; - self.entries.values[insersion_index] = .{.occupied = value}; - - return insersion_index; - } - - pub fn remove(self: *Self, key: usize) ?Value { - if (key >= self.entries.len()) { - return null; - } - - switch (self.entries.values[key]) { - .free_index => { - return null; - }, - - .occupied => |occupied| { - self.entries.values[key] = .{.free_index = self.first_free_index}; - self.first_free_index = key; - - return occupied; - }, - } - } - - pub fn values(self: *const Self) Values { - return .{ - .pool = self, - }; - } - }; -} - -test "pooling" { - var pool = Pool(i32).init(std.testing.allocator); - - defer pool.deinit(); - - try std.testing.expectEqual(0, pool.first_free_index); - try std.testing.expectEqual(null, pool.get(0)); - try std.testing.expectEqual(null, pool.remove(0)); - try std.testing.expectEqual(0, try pool.insert(69)); - try std.testing.expectEqual(1, pool.first_free_index); - try std.testing.expectEqual(1, try pool.insert(420)); - try std.testing.expectEqual(69, pool.get(0).?.*); - try std.testing.expectEqual(420, pool.get(1).?.*); - try std.testing.expectEqual(69, pool.remove(0).?); - try std.testing.expectEqual(0, pool.first_free_index); - try std.testing.expectEqual(null, pool.get(0)); - try std.testing.expectEqual(null, pool.remove(0)); - try std.testing.expectEqual(0, try pool.insert(36)); - try std.testing.expectEqual(36, pool.get(0).?.*); -} - -pub fn Read(comptime Value: type) type { - return Shared(Value, .{ - .thread_restriction = thread_restriction(Value), - .read_only = true, - }); + pub fn init(state: *const State) Self { + return .{ + .ptr = state, + }; + } + }; } pub fn Receive(comptime Message: type) type { - const TypedChannel = Channel(Message); + const TypedChannel = Channel(Message); - return struct { - channel: *const TypedChannel, + return struct { + channel: *const TypedChannel, - const Self = @This(); + const Self = @This(); - pub const Param = struct { - channel: *const TypedChannel, - }; + pub const bind = TypedChannel.bind; - pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param { - return .{ - .channel = (try context.register_readable_state_access(TypedChannel)) orelse set: { - try context.world.set_state(TypedChannel.init(heap.allocator)); + pub fn init(channel: *const TypedChannel) Self { + return .{ + .channel = channel, + }; + } - try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{ - .label = "swap channel of " ++ @typeName(Message), - }); - - try context.world.on_event(.exit, system_fn(TypedChannel.cleanup), .{ - .label = "clean up channel of " ++ @typeName(Message), - }); - - break: set (try context.register_readable_state_access(TypedChannel)).?; - }, - }; - } - - pub fn init(param: *Param) Self { - return .{ - .channel = param.channel, - }; - } - - pub fn messages(self: Self) []const Message { - return self.channel.messages(); - } - }; + pub fn messages(self: Self) []const Message { + return self.channel.messages(); + } + }; } pub fn Send(comptime Message: type) type { - const TypedChannel = Channel(Message); + const TypedChannel = Channel(Message); - return struct { - channel: *TypedChannel, + return struct { + channel: *TypedChannel, - const Self = @This(); + const Self = @This(); - pub const Param = struct { - channel: *TypedChannel, - }; + pub const bind = TypedChannel.bind; - pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param { - return .{ - .channel = (try context.register_writable_state_access(TypedChannel)) orelse set: { - try context.world.set_state(TypedChannel.init(heap.allocator)); + pub fn init(channel: *TypedChannel) Self { + return .{ + .channel = channel, + }; + } - try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{ - .label = "swap channel of " ++ @typeName(Message), - }); - - try context.world.on_event(.exit, system_fn(TypedChannel.cleanup), .{ - .label = "clean up channel of " ++ @typeName(Message), - }); - - break: set (try context.register_writable_state_access(TypedChannel)).?; - }, - }; - } - - pub fn init(param: *Param) Self { - return .{ - .channel = param.channel, - }; - } - - pub fn push(self: Self, message: Message) std.mem.Allocator.Error!void { - try self.channel.push(message); - } - }; + pub fn push(self: Self, message: Message) std.mem.Allocator.Error!void { + try self.channel.push(message); + } + }; } -pub const ShareInfo = struct { - thread_restriction: ThreadRestriction, - read_only: bool, +pub const Traits = packed struct { + is_thread_unsafe: bool = false, + + pub fn derived(self: Traits, other: Traits) Traits { + return .{ + .is_thread_unsafe = self.is_thread_unsafe or other.is_thread_unsafe, + }; + } }; -pub fn Shared(comptime Value: type, comptime info: ShareInfo) type { - const value_info = @typeInfo(Value); +pub fn Write(comptime State: type) type { + return struct { + ptr: *State, - const Qualified = switch (value_info) { - .Optional => @Type(.{ - .Optional = .{ - .child = .{ - .Pointer = .{ - .is_allowzero = false, - .sentinel = null, - .address_space = .generic, - .is_volatile = false, - .alignment = @alignOf(Value), - .size = .One, - .child = Value, - .is_const = info.read_only, - }, - }, - }, - }), + const Self = @This(); - else => @Type(.{ - .Pointer = .{ - .is_allowzero = false, - .sentinel = null, - .address_space = .generic, - .is_volatile = false, - .alignment = @alignOf(Value), - .size = .One, - .child = Value, - .is_const = info.read_only, - }, - }), - }; - - return struct { - state: Qualified, - - const Self = @This(); - - pub const Param = struct { - state: Qualified, - }; - - pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param { - const thread_restriction_name = switch (info.thread_restriction) { - .main => "main thread-restricted ", - .none => "" - }; - - const state = switch (info.read_only) { - true => (try context.register_readable_state_access(Value)), - false => (try context.register_writable_state_access(Value)), - }; - - return .{ - .state = switch (value_info) { - .Optional => state, - - else => state orelse { - @panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not been set yet", .{ - thread_restriction_name, - if (info.read_only) "read-only" else "read-write", - @typeName(Value), - })); - }, - }, - }; - } - - pub fn init(param: *Param) Self { - return .{ - .state = param.state, - }; - } - - pub const thread_restriction = info.thread_restriction; - }; + pub fn init(state: *State) Self { + return .{ + .ptr = state, + }; + } + }; } -pub fn SlotMap(comptime Value: type) type { - const Index = @Type(.{.Int = .{ - .bits = @bitSizeOf(usize) - 1, - .signedness = .unsigned, - }}); +pub const realtime_app = App.Setup.init(run_realtime_loop); - const Slot = packed struct { - index: Index, - is_occupied: bool, - }; +fn run_realtime_loop(app: *App) !void { + // ext.SDL_SetLogPriorities(ext.SDL_LOG_PRIORITY_VERBOSE); + // ext.SDL_SetLogOutputFunction(sdl_log, null); + const updates_per_frame = 60.0; + const target_frame_time = 1.0 / updates_per_frame; + const time = app.hasState(App.Time).?; - comptime std.debug.assert(@sizeOf(Slot) == @sizeOf(usize)); + const virtual_thread_count = std.Thread.getCpuCount() catch 0; + var tasks = try coral.asio.TaskQueue.init(virtual_thread_count / 2); - return struct { - slots: stack.Sequential(Slot), - entries: stack.Parallel(Entry), - next: Index = 0, + defer { + tasks.deinit(); + } - const Entry = struct { - value: Value, - erased: usize, - }; + try app.run(&tasks, .load); - const Self = @This(); + const ticks_initial = std.time.milliTimestamp(); + var ticks_previous = ticks_initial; + var accumulated_time = @as(f64, 0); - pub fn deinit(self: *Self) void { - self.slots.deinit(); - self.entries.deinit(); - } + while (app.is_running) { + const ticks_current = std.time.milliTimestamp(); + const milliseconds_per_second = 1000.0; - pub fn get(self: Self, index: usize) ?*Value { - if (index >= self.slots.len()) { - return null; - } + accumulated_time += @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; + time.elapsed = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second; + ticks_previous = ticks_current; - const slot = self.slots.values[index]; + try app.run(&tasks, .pre_update); - if (!slot.is_occupied) { - return null; - } + while (accumulated_time >= target_frame_time) : (accumulated_time -= target_frame_time) { + try app.run(&tasks, .update); + } - return &self.entries.values.get(.value)[slot.index]; - } + try app.run(&tasks, .post_update); + try app.run(&tasks, .render); + try app.run(&tasks, .finish); + } - pub fn init(allocator: std.mem.Allocator) Self { - return .{ - .slots = .{.allocator = allocator}, - .entries = .{.allocator = allocator}, - }; - } - - pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize { - const index = self.next; - const slots_len: Index = @intCast(self.slots.len()); - - if (self.next == slots_len) { - try self.slots.push_grow(.{ - .index = slots_len + 1, - .is_occupied = false, - }); - } - - const slot = &self.slots.values[index]; - - std.debug.assert(!slot.is_occupied); - - self.next = slot.index; - - slot.* = .{ - .index = index, - .is_occupied = true, - }; - - try self.entries.push_grow(.{ - .value = value, - .erased = index, - }); - - return index; - } - - pub fn remove(self: *Self, index: usize) ?Value { - if (index >= self.slots.len()) { - return null; - } - - const slot = self.slots.values[index]; - - if (!slot.is_occupied) { - return null; - } - - const value = self.entries.values.get(.value)[slot.index]; - - std.debug.assert(self.entries.values.write(slot.index, .{ - .value = self.entries.get(.value).?.*, - .erased = self.entries.get(.erased).?.*, - })); - - std.debug.assert(self.entries.pop()); - - if (!self.entries.is_empty()) { - self.slots.values[self.entries.values.get(.erased)[slot.index]] = slot; - } - - self.slots.values[index] = .{ - .index = self.next, - .is_occupied = false, - }; - - self.next = @intCast(index); - - return value; - } - - pub fn values(self: Self) []Value { - return self.entries.values.get(.value); - } - }; -} - -pub const SystemInfo = struct { - execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void, - parameters: [max_parameters]*const Parameter = undefined, - parameter_count: u4 = 0, - thread_restriction: ThreadRestriction = .none, - - pub const Parameter = struct { - thread_restriction: ThreadRestriction, - init: *const fn (*anyopaque, *anyopaque) void, - bind: *const fn (std.mem.Allocator, World.BindContext) std.mem.Allocator.Error!*anyopaque, - unbind: *const fn (std.mem.Allocator, *anyopaque, World.UnbindContext) void, - }; - - pub const max_parameters = 16; - - pub fn used_parameters(self: *const SystemInfo) []const *const Parameter { - return self.parameters[0 .. self.parameter_count]; - } -}; - -pub const SystemOrder = struct { - label: []const u8 = "", - run_after: []const *const SystemInfo = &.{}, - run_before: []const *const SystemInfo = &.{}, -}; - -pub const ThreadRestriction = enum { - none, - main, -}; - -pub const TypeID = enum (usize) { _ }; - -pub fn Write(comptime Value: type) type { - return Shared(Value, .{ - .thread_restriction = thread_restriction(Value), - .read_only = false, - }); -} - -pub fn launch_arg(flag: LaunchFlag) ?[]const u8 { - return LaunchFlag.args[@intFromEnum(flag)]; -} - -fn parameter_type(comptime Value: type) *const SystemInfo.Parameter { - const ValueParams = Params(Value); - - if (@sizeOf(Value) == 0) { - @compileError("System parameters must have a non-zero size"); - } - - const parameters = struct { - fn bind(allocator: std.mem.Allocator, context: World.BindContext) std.mem.Allocator.Error!*anyopaque { - const value_name = @typeName(Value); - - if (!@hasDecl(Value, "bind")) { - @compileError( - "a `bind` declaration on " ++ - value_name ++ - " is requied for parameter types with a `Param` declaration"); - } - - const bind_type = @typeInfo(@TypeOf(Value.bind)); - - if (bind_type != .Fn) { - @compileError("`bind` declaration on " ++ value_name ++ " must be a fn"); - } - - if (bind_type.Fn.params.len != 1 or bind_type.Fn.params[0].type.? != World.BindContext) { - @compileError( - "`bind` fn on " ++ - value_name ++ - " must accept " ++ - @typeName(World.BindContext) ++ - " as it's one and only argument"); - } - - const params_node = try allocator.create(ValueParams.Node); - - params_node.* = .{ - .param = switch (bind_type.Fn.return_type.?) { - Value.Param => Value.bind(context), - std.mem.Allocator.Error!Value.Param => try Value.bind(context), - - else => @compileError(std.fmt.comptimePrint("`bind` fn on {s} must return {s} or {s}", .{ - @typeName(Value), - @typeName(Value.Param), - @typeName(std.mem.Allocator.Error!Value.Param) - })), - }, - }; - - if (context.world.get_state(ValueParams)) |value_params| { - if (value_params.has_tail) |tail| { - tail.has_next = params_node; - } - - params_node.has_prev = value_params.has_tail; - value_params.has_tail = params_node; - } else { - try context.world.set_state(ValueParams{ - .has_head = params_node, - .has_tail = params_node, - }); - } - - return @ptrCast(params_node); - } - - fn init(argument: *anyopaque, erased_node: *anyopaque) void { - const value_name = @typeName(Value); - - if (!@hasDecl(Value, "init")) { - @compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types"); - } - - const init_type = @typeInfo(@TypeOf(Value.init)); - - if (init_type != .Fn) { - @compileError("`init` declaration on " ++ value_name ++ " must be a fn"); - } - - if (init_type.Fn.return_type.? != Value) { - @compileError("`init` fn on " ++ value_name ++ " must return a " ++ value_name); - } - - const concrete_argument = @as(*Value, @ptrCast(@alignCast(argument))); - - if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.Param) { - @compileError("`init` fn on " ++ value_name ++ " must accept a " ++ @typeName(*Value.Param)); - } - - concrete_argument.* = Value.init(&@as(*ValueParams.Node, @ptrCast(@alignCast(erased_node))).param); - } - - fn unbind(allocator: std.mem.Allocator, erased_node: *anyopaque, context: World.UnbindContext) void { - if (@hasDecl(Value, "unbind")) { - const node = @as(*ValueParams.Node, @ptrCast(@alignCast(erased_node))); - - if (node.has_prev) |prev| { - prev.has_next = node.has_next; - } - - if (node.has_next) |next| { - next.has_prev = node.has_prev; - } - - if (context.world.get_state(ValueParams)) |params| { - if (node.has_prev == null) { - params.has_head = node.has_next; - } - - if (node.has_next == null) { - params.has_tail = node.has_prev; - } - } - - Value.unbind(&node.param, context); - allocator.destroy(node); - } - } - }; - - return comptime &.{ - .thread_restriction = if (@hasDecl(Value, "thread_restriction")) Value.thread_restriction else .none, - .init = parameters.init, - .bind = parameters.bind, - .unbind = parameters.unbind, - }; -} - -pub fn system_fn(comptime call: anytype) *const SystemInfo { - const Call = @TypeOf(call); - - const system_info = comptime generate: { - switch (@typeInfo(Call)) { - .Fn => |call_fn| { - if (call_fn.params.len > SystemInfo.max_parameters) { - @compileError("number of parameters to `call` cannot be more than 16"); - } - - const systems = struct { - fn run(parameters: []const *const SystemInfo.Parameter, data: *const [SystemInfo.max_parameters]*anyopaque) anyerror!void { - var call_args = @as(std.meta.ArgsTuple(Call), undefined); - - inline for (parameters, &call_args, data[0 .. parameters.len]) |parameter, *call_arg, state| { - parameter.init(call_arg, state); - } - - switch (@typeInfo(call_fn.return_type.?)) { - .Void => @call(.auto, call, call_args), - .ErrorUnion => try @call(.auto, call, call_args), - else => @compileError("number of parameters to `call` must return void or !void"), - } - } - }; - - var parameters = @as([SystemInfo.max_parameters]*const SystemInfo.Parameter, undefined); - var system_thread_restriction = ThreadRestriction.none; - - for (0 .. call_fn.params.len) |index| { - const CallParam = call_fn.params[index].type.?; - const parameter = parameter_type(CallParam); - - if (parameter.thread_restriction != .none) { - if (system_thread_restriction != .none and system_thread_restriction != parameter.thread_restriction) { - @compileError("a system may not have conflicting thread restrictions"); - } - - system_thread_restriction = parameter.thread_restriction; - } - - parameters[index] = parameter; - } - - break: generate &.{ - .parameters = parameters, - .parameter_count = call_fn.params.len, - .execute = systems.run, - .thread_restriction = system_thread_restriction, - }; - }, - - else => @compileError("parameter `call` must be a function"), - } - }; - - return system_info; -} - -pub fn thread_restriction(comptime State: type) ThreadRestriction { - if (@hasDecl(State, "thread_restriction")) { - return State.thread_restriction; - } - - return .none; -} - -pub fn type_id(comptime T: type) TypeID { - const TypeHandle = struct { - comptime { - _ = T; - } - - var byte: u8 = 0; - }; - - return @enumFromInt(@intFromPtr(&TypeHandle.byte)); + try app.run(&tasks, .exit); } diff --git a/src/ona/scalars.zig b/src/ona/scalars.zig deleted file mode 100644 index aa4387d..0000000 --- a/src/ona/scalars.zig +++ /dev/null @@ -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; -} diff --git a/src/ona/slices.zig b/src/ona/slices.zig deleted file mode 100644 index 2542e3a..0000000 --- a/src/ona/slices.zig +++ /dev/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)); - } -} diff --git a/src/ona/stack.zig b/src/ona/stack.zig deleted file mode 100644 index bb4ceb6..0000000 --- a/src/ona/stack.zig +++ /dev/null @@ -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)); - } - }; -} diff --git a/src/ona/utf8.zig b/src/ona/utf8.zig deleted file mode 100644 index 8326e89..0000000 --- a/src/ona/utf8.zig +++ /dev/null @@ -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"; -} diff --git a/src/tests.zig b/src/tests.zig new file mode 100644 index 0000000..e5f05f9 --- /dev/null +++ b/src/tests.zig @@ -0,0 +1,3 @@ +test { + _ = @import("coral/bytes/formatting.zig"); +} diff --git a/testing.spv b/testing.spv new file mode 100644 index 0000000..62956f1 --- /dev/null +++ b/testing.spv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:755f1dfdd33dd69b0d33e1f6ddac926a5df0b52327ecad47539695bae6ade536 +size 2576