From 6ec24c765c9e8b70c66e913fe16b2a25f8d8c02e Mon Sep 17 00:00:00 2001 From: kayomn Date: Wed, 24 Jul 2024 00:25:18 +0100 Subject: [PATCH] Convert project into Zig package and add demos (#55) --- .gitattributes | 1 + .gitignore | 4 +- .vscode/launch.json | 4 +- build.zig | 384 +-- build.zig.zon | 9 +- debug/actor.bmp | 3 - debug/ca.frag | 25 - {debug => demos}/crt.frag | 14 +- demos/crt.frag.spv | 3 + demos/effects.zig | 92 + src/{ona/gfx => coral}/Resources.zig | 46 +- src/coral/colors.zig | 13 + src/coral/coral.zig | 595 ++++- src/{ona/gfx => coral}/rendering.zig | 70 +- src/coral/script.zig | 2218 ----------------- .../gfx => coral}/shaders/2d_default.frag | 0 .../gfx => coral}/shaders/2d_default.vert | 0 src/{ona/gfx => coral}/spirv.zig | 8 +- src/flow/World.zig | 98 - src/flow/flow.zig | 329 --- src/flow/states.zig | 87 - src/input/input.zig | 274 ++ src/main.zig | 124 - src/{flow/system.zig => ona/World.zig} | 290 ++- src/ona/act.zig | 59 - src/{coral => ona}/ascii.zig | 41 +- src/{coral => ona}/asyncio.zig | 0 src/{flow => ona}/dag.zig | 6 +- src/ona/ext.zig | 4 - src/{coral => ona}/files.zig | 14 +- src/ona/gfx.zig | 534 ---- src/ona/gfx/colors.zig | 13 - src/{coral => ona}/hashes.zig | 0 src/{coral => ona}/heap.zig | 0 src/{coral => ona}/io.zig | 22 +- src/{coral => ona}/lina.zig | 25 - src/{coral => ona}/map.zig | 14 +- src/ona/msg.zig | 134 - src/ona/ona.zig | 773 +++++- src/{coral => ona}/scalars.zig | 0 src/{coral => ona}/slices.zig | 8 +- src/{coral => ona}/stack.zig | 28 +- src/{coral => ona}/utf8.zig | 44 +- 43 files changed, 2226 insertions(+), 4184 deletions(-) delete mode 100755 debug/actor.bmp delete mode 100644 debug/ca.frag rename {debug => demos}/crt.frag (83%) create mode 100644 demos/crt.frag.spv create mode 100644 demos/effects.zig rename src/{ona/gfx => coral}/Resources.zig (89%) create mode 100644 src/coral/colors.zig rename src/{ona/gfx => coral}/rendering.zig (82%) delete mode 100644 src/coral/script.zig rename src/{ona/gfx => coral}/shaders/2d_default.frag (100%) rename src/{ona/gfx => coral}/shaders/2d_default.vert (100%) rename src/{ona/gfx => coral}/spirv.zig (97%) delete mode 100644 src/flow/World.zig delete mode 100644 src/flow/flow.zig delete mode 100644 src/flow/states.zig create mode 100644 src/input/input.zig delete mode 100644 src/main.zig rename src/{flow/system.zig => ona/World.zig} (62%) delete mode 100644 src/ona/act.zig rename src/{coral => ona}/ascii.zig (79%) rename src/{coral => ona}/asyncio.zig (100%) rename src/{flow => ona}/dag.zig (96%) delete mode 100644 src/ona/ext.zig rename src/{coral => ona}/files.zig (87%) delete mode 100644 src/ona/gfx.zig delete mode 100644 src/ona/gfx/colors.zig rename src/{coral => ona}/hashes.zig (100%) rename src/{coral => ona}/heap.zig (100%) rename src/{coral => ona}/io.zig (88%) rename src/{coral => ona}/lina.zig (66%) rename src/{coral => ona}/map.zig (96%) delete mode 100644 src/ona/msg.zig rename src/{coral => ona}/scalars.zig (100%) rename src/{coral => ona}/slices.zig (94%) rename src/{coral => ona}/stack.zig (87%) rename src/{coral => ona}/utf8.zig (66%) diff --git a/.gitattributes b/.gitattributes index 95c1536..3a63264 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ *.bmp filter=lfs diff=lfs merge=lfs -text +*.spv filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index f190a33..fdab065 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ # Generated assets .zig-cache /zig-out -*.spv +/src/**/*.spv +/demos/**/*.out +/demos/**/*.exe diff --git a/.vscode/launch.json b/.vscode/launch.json index e0928e0..62345d6 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,8 +5,8 @@ "name": "Runner", "type": "gdb", "request": "launch", - "target": "${workspaceRoot}/zig-out/bin/main", - "cwd": "${workspaceRoot}/debug/", + "target": "${workspaceRoot}/demos/effects.out", + "cwd": "${workspaceRoot}/demos/", "valuesFormatting": "prettyPrinters", "preLaunchTask": "Build All" }, diff --git a/build.zig b/build.zig index c702a40..66823b6 100644 --- a/build.zig +++ b/build.zig @@ -2,44 +2,215 @@ 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, b: *std.Build) !void { + const demos = b.step("demos", "Build demos"); + + var dir = try std.fs.cwd().openDir("demos/", .{ + .iterate = true, + }); + + defer { + dir.close(); + } + + var entries = try dir.walk(b.allocator); + + defer { + entries.deinit(); + } + + while (try entries.next()) |entry| { + if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) { + continue; + } + + const source_path = try sub_path(.{"demos", entry.basename}); + var path_buffer = [_:0]u8{0} ** 255; + + const demo = b.addExecutable(.{ + .name = try std.fmt.bufPrint(&path_buffer, "{s}.out", .{std.fs.path.stem(entry.basename)}), + .root_source_file = b.path(source_path.bytes()), + .target = self.target, + .optimize = self.optimize, + }); + + for (self.imports.items) |import| { + demo.root_module.addImport(import.name, import.module); + } + + demos.dependOn(&b.addInstallArtifact(demo, .{ + .dest_dir = .{ + .override = .{ + .custom = "../demos/", + }, + }, + }).step); + } + } + + pub fn find_tests(self: Project, b: *std.Build) !void { + const tests = b.step("test", "Build and run tests"); + + for (self.imports.items) |import| { + tests.dependOn(&b.addRunArtifact(b.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 full_source_path = try sub_path(.{shaders_path, entry.path}); + const full_binary_path = try sub_path(.{shaders_path, binary_name}); + + const glslang_validator_args = [_][]const u8{ + "glslangValidator", + "-V", + full_source_path.bytes(), + "-o", + full_binary_path.bytes(), + }; + + 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 SubPath = struct { buffer: [max]u8 = [_]u8{0} ** max, unused: u8 = max, - const max = 255; + pub const max = 255; - pub fn utf8(self: *const SubPath) [:0]const u8 { - return self.buffer[0 .. (max - self.unused):0]; + pub fn append(self: *SubPath, component: []const u8) !void { + const used = max - self.unused; + + if (used != 0 and self.buffer[used - 1] != '/') { + if (component.len > self.unused) { + return error.PathTooBig; + } + + @memcpy(self.buffer[used .. (used + component.len)], component); + + self.unused -= @intCast(component.len); + } else { + const required_len = component.len + 1; + + if (required_len > self.unused) { + return error.PathTooBig; + } + + @memcpy(self.buffer[used .. (used + component.len)], component); + + self.buffer[component.len] = '/'; + + self.unused -= @intCast(required_len); + } + } + + pub fn bytes(self: *const SubPath) [:0]const u8 { + return @ptrCast(self.buffer[0 .. max - self.unused]); } }; pub fn build(b: *std.Build) !void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); + var project = Project{ + .imports = ImportList.init(b.allocator), + .target = b.standardTargetOptions(.{}), + .optimize = b.standardOptimizeOption(.{}), + }; const sokol_dependency = b.dependency("sokol", .{ - .target = target, - .optimize = optimize, + .target = project.target, + .optimize = project.optimize, }); - const coral_module = b.createModule(.{ - .root_source_file = b.path("src/coral/coral.zig"), - }); - - const flow_module = b.createModule(.{ - .root_source_file = b.path("src/flow/flow.zig"), - - .imports = &.{ - .{ - .name = "coral", - .module = coral_module, - }, - } - }); - - const ona_module = b.createModule(.{ - .root_source_file = b.path("src/ona/ona.zig"), + const ona_module = try project.add_module(b, "ona", .{}); + const input_module = try project.add_module(b, "input", .{}); + const coral_module = try project.add_module(b, "coral", .{ .imports = &.{ .{ .name = "sokol", @@ -47,20 +218,20 @@ pub fn build(b: *std.Build) !void { }, .{ - .name = "coral", - .module = coral_module, + .name = "ona", + .module = ona_module, }, .{ - .name = "flow", - .module = flow_module, + .name = "input", + .module = input_module, }, }, }); - ona_module.addIncludePath(b.path("ext/")); + coral_module.addIncludePath(b.path("ext/")); - ona_module.linkLibrary(spirv_cross: { + coral_module.linkLibrary(spirv_cross: { const dir = "ext/spirv-cross/"; const sources = [_][]const u8{ @@ -78,8 +249,8 @@ pub fn build(b: *std.Build) !void { const lib = b.addStaticLibrary(.{ .name = "spirvcross", - .target = target, - .optimize = optimize, + .target = project.target, + .optimize = project.optimize, }); switch (lib.rootModuleTarget().abi) { @@ -90,135 +261,62 @@ pub fn build(b: *std.Build) !void { 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"}, + + .flags = &.{ + "-fstrict-aliasing", + "-DSPIRV_CROSS_C_API_GLSL", + "-DSPIRV_CROSS_C_API_HLSL", + "-DSPIRV_CROSS_C_API_MSL", + }, }); } break: spirv_cross lib; }); - b.step("test", "Run unit tests").dependOn(tests: { - const tests = b.addTest(.{ - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); - - break: tests &tests.step; + coral_module.linkSystemLibrary("SDL2", .{ + .needed = true, + .preferred_link_mode = .dynamic, }); - b.installArtifact(add: { - const exe = b.addExecutable(.{ - .name = "main", - .root_source_file = b.path("src/main.zig"), - .target = target, - .optimize = optimize, - }); + coral_module.link_libc = true; - exe.root_module.addImport("ona", ona_module); - exe.root_module.addImport("coral", coral_module); - exe.linkLibC(); - exe.linkSystemLibrary("SDL2"); + try project.find_tests(b); + try project.find_demos(b); +} - const shaders_sub_path = "src/ona/gfx/shaders/"; +fn sub_path(components: anytype) !SubPath { + var path = comptime try std.BoundedArray(u8, SubPath.max).init(0); + const Components = @TypeOf(components); - var shaders_dir = try std.fs.cwd().openDir(shaders_sub_path, .{ - .iterate = true, - }); - - defer shaders_dir.close(); - - var shaders_walker = try shaders_dir.walk(b.allocator); - - defer shaders_walker.deinit(); - - const Shader = struct { - source_sub_path: SubPath = .{}, - binary_sub_path: SubPath = .{}, - }; - - var pending_shaders = std.ArrayList(Shader).init(b.allocator); - - defer pending_shaders.deinit(); - - scan_shaders: while (try shaders_walker.next()) |entry| { - if (entry.kind != .file) { - continue: scan_shaders; + switch (@typeInfo(Components)) { + .Struct => |@"struct"| { + if (!@"struct".is_tuple) { + @compileError("`components` must be a tuple"); } - const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert"); + const last_component_index = components.len - 1; - if (!is_shader_file) { - continue: scan_shaders; - } + inline for (components, 0 .. components.len) |component, i| { + path.appendSlice(component) catch { + return error.PathTooBig; + }; - const shader_name = std.fs.path.stem(entry.path); - - for (pending_shaders.items) |pending_shader| { - if (std.mem.endsWith(u8, pending_shader.source_sub_path.utf8(), shader_name)) { - continue: scan_shaders; + if (i < last_component_index and !std.mem.endsWith(u8, component, "/")) { + path.append('/') catch { + return error.PathTooBig; + }; } } + }, - var shader = Shader{}; - const source_sub_path = try std.fmt.bufPrint(&shader.source_sub_path.buffer, "{s}{s}", .{shaders_sub_path, shader_name}); - const binary_sub_path = try std.fmt.bufPrint(&shader.binary_sub_path.buffer, "{s}.spv", .{source_sub_path}); - - shaders_dir.access(std.fs.path.basename(binary_sub_path), .{.mode = .read_only}) catch { - shader.source_sub_path.unused -= @intCast(source_sub_path.len); - shader.binary_sub_path.unused -= @intCast(binary_sub_path.len); - - try pending_shaders.append(shader); - - continue: scan_shaders; - }; - - if ((try shaders_dir.statFile(entry.basename)).mtime > (try shaders_dir.statFile(std.fs.path.basename(binary_sub_path))).mtime) { - shader.source_sub_path.unused -= @intCast(source_sub_path.len); - shader.binary_sub_path.unused -= @intCast(binary_sub_path.len); - - try pending_shaders.append(shader); - - continue: scan_shaders; - } + else => { + @compileError("`components` cannot be a " ++ @typeName(Components)); } + } - for (pending_shaders.items) |pending_shader| { - var vertex_binary_sub_path = SubPath{}; - var fragment_binary_sub_path = SubPath{}; - const source_sub_path_utf8 = pending_shader.source_sub_path.utf8(); - - vertex_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&vertex_binary_sub_path.buffer, "{s}.vert.spv", .{source_sub_path_utf8})).len); - fragment_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&fragment_binary_sub_path.buffer, "{s}.frag.spv", .{source_sub_path_utf8})).len); - - const vertex_binary_sub_path_utf8 = vertex_binary_sub_path.utf8(); - const fragment_binary_sub_path_utf8 = fragment_binary_sub_path.utf8(); - - exe.step.dependOn(compile_vertex: { - const compile_command = b.addSystemCommand(&.{ - "glslangValidator", - "-V", - vertex_binary_sub_path_utf8[0 .. vertex_binary_sub_path_utf8.len - 4], - "-o", - vertex_binary_sub_path_utf8, - }); - - break: compile_vertex &compile_command.step; - }); - - exe.step.dependOn(compile_fragment: { - const compile_command = b.addSystemCommand(&.{ - "glslangValidator", - "-V", - fragment_binary_sub_path_utf8[0 .. fragment_binary_sub_path_utf8.len - 4], - "-o", - fragment_binary_sub_path_utf8, - }); - - break: compile_fragment &compile_command.step; - }); - } - - break: add exe; - }); + return .{ + .unused = SubPath.max - path.len, + .buffer = path.buffer, + }; } diff --git a/build.zig.zon b/build.zig.zon index a155592..4d103d3 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,6 +1,8 @@ .{ - .name = "Ona", + .name = "ona", .version = "0.0.1", + .minimum_zig_version = "0.13.0", + .paths = .{ "src", "build.zig", @@ -8,10 +10,15 @@ "LICENSE", "README.md", }, + .dependencies = .{ .sokol = .{ .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27", .hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569", }, + + .@"spirv-cross" = . { + .path = "./ext/spirv-cross", + }, }, } diff --git a/debug/actor.bmp b/debug/actor.bmp deleted file mode 100755 index bc9dfad..0000000 --- a/debug/actor.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40ce0efb274a117e5a679e1eeb76d083cd6e12f0006845052e35df9893431fc2 -size 16522 diff --git a/debug/ca.frag b/debug/ca.frag deleted file mode 100644 index 74e9519..0000000 --- a/debug/ca.frag +++ /dev/null @@ -1,25 +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; - -layout (binding = 0) uniform Effect { - float effect_magnitude; -}; - -void main() { - vec4 color1 = texture(sprite, uv) / 3.0; - vec4 color2 = texture(sprite, uv + 0.002 * effect_magnitude) / 3.0; - vec4 color3 = texture(sprite, uv - 0.002 * effect_magnitude) / 3.0; - - color1 *= 2.0; - color2.g = 0.0; - color2.b = 0.0; - color3.r = 0.0; - - texel = color * (color1 + color2 + color3); -} diff --git a/debug/crt.frag b/demos/crt.frag similarity index 83% rename from debug/crt.frag rename to demos/crt.frag index 682f44e..d8d3c8c 100644 --- a/debug/crt.frag +++ b/demos/crt.frag @@ -1,5 +1,7 @@ #version 430 +// Adapted from: https://www.shadertoy.com/view/4sf3Dr + layout (binding = 0) uniform sampler2D sprite; layout (location = 0) in vec4 color; @@ -13,14 +15,12 @@ layout (binding = 0) uniform Effect { float time; }; -vec3 scanline(vec2 coord, vec3 screen) -{ +vec3 scanline(vec2 coord, vec3 screen) { screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02; return screen; } -vec2 crt(vec2 coord, float bend) -{ +vec2 crt(vec2 coord, float bend) { // put in symmetrical coords coord = (coord - 0.5) * 2.0; @@ -36,17 +36,13 @@ vec2 crt(vec2 coord, float bend) return coord; } -void main() -{ +void main() { vec2 crtCoords = crt(uv, 4.8); // Split the color channels texel.rgb = texture(sprite, crtCoords).rgb; texel.a = 1; - // HACK: this bend produces a shitty moire pattern. - // Up the bend for the scanline 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 new file mode 100644 index 0000000..31a5259 --- /dev/null +++ b/demos/crt.frag.spv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:43f845adaa07403eae9fa02124fa96f4cf70a5b1107fb45fefdb6caca586ebd7 +size 3352 diff --git a/demos/effects.zig b/demos/effects.zig new file mode 100644 index 0000000..3d0a2c2 --- /dev/null +++ b/demos/effects.zig @@ -0,0 +1,92 @@ +const coral = @import("coral"); + +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: coral.Texture = .default, + crt_effect: coral.Effect = .default, +}; + +pub const main = ona.start(setup, .{ + .tick_rate = 60, + .execution = .{.thread_share = 0.1}, + .middleware = &.{coral.setup}, +}); + +fn load(display: ona.Write(coral.Display), actors: ona.Write(Effects), assets: ona.Write(coral.Assets)) !void { + display.state.width, display.state.height = .{1280, 720}; + + actors.state.render_texture = try assets.state.load_texture(.{ + .format = .rgba8, + + .access = .{ + .render = .{ + .width = display.state.width, + .height = display.state.height, + }, + }, + }); + + actors.state.crt_effect = try assets.state.load_effect_file(ona.files.bundle, "./crt.frag.spv"); +} + +fn render(commands: coral.Commands, effects: ona.Write(Effects), app: ona.Read(ona.App), display: ona.Write(coral.Display)) !void { + try commands.set_target(.{ + .texture = effects.state.render_texture, + .clear_color = coral.colors.black, + .clear_depth = 0, + .clear_stencil = 0, + }); + + const display_width: f32 = @floatFromInt(display.state.width); + const display_height: f32 = @floatFromInt(display.state.height); + + const display_transform = coral.Transform2D{ + .origin = .{display_width / 2, display_height / 2}, + .xbasis = .{display_width, 0}, + .ybasis = .{0, display_height}, + }; + + try commands.draw_texture(.{ + .texture = .default, + .transform = display_transform, + }); + + try commands.set_effect(.{ + .effect = effects.state.crt_effect, + + .properties = std.mem.asBytes(&CRT{ + .width = display_width, + .height = display_height, + .time = @floatCast(app.state.elapsed_time), + }), + }); + + try commands.set_target(.{ + .texture = .backbuffer, + .clear_color = null, + .clear_depth = null, + .clear_stencil = null, + }); + + try commands.draw_texture(.{ + .texture = effects.state.render_texture, + .transform = display_transform, + }); +} + +fn setup(world: *ona.World, events: ona.App.Events) !void { + try world.set_state(Effects{}); + + try world.on_event(events.load, ona.system_fn(load), .{.label = "load"}); + try world.on_event(events.render, ona.system_fn(render), .{.label = "render actors"}); +} diff --git a/src/ona/gfx/Resources.zig b/src/coral/Resources.zig similarity index 89% rename from src/ona/gfx/Resources.zig rename to src/coral/Resources.zig index b4718de..2c929b7 100644 --- a/src/ona/gfx/Resources.zig +++ b/src/coral/Resources.zig @@ -1,6 +1,6 @@ -const coral = @import("coral"); +const coral = @import("./coral.zig"); -const gfx = @import("../gfx.zig"); +const ona = @import("ona"); const sokol = @import("sokol"); @@ -14,18 +14,18 @@ textures: TexturePool, pub const Effect = struct { shader: sokol.gfx.Shader, pipeline: sokol.gfx.Pipeline, - properties: []coral.io.Byte, + properties: []u8, pub fn deinit(self: *Effect) void { - coral.heap.allocator.free(self.properties); + 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(coral.heap.allocator); + pub fn init(desc: coral.Effect.Desc) spirv.Error!Effect { + var spirv_arena = std.heap.ArenaAllocator.init(ona.heap.allocator); defer { spirv_arena.deinit(); @@ -80,13 +80,13 @@ pub const Effect = struct { break: pipeline_desc pipeline_desc; }); - const properties = try coral.heap.allocator.alloc( - coral.io.Byte, + 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 { - coral.heap.allocator.free(properties); + ona.heap.allocator.free(properties); sokol.gfx.destroyPipeline(pipeline); sokol.gfx.destroyShader(shader); } @@ -239,7 +239,7 @@ pub const Effect = struct { }; }; -const EffectPool = coral.Pool(Effect); +const EffectPool = ona.Pool(Effect); const Self = @This(); @@ -282,7 +282,7 @@ pub const Texture = struct { self.* = undefined; } - pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture { + pub fn init(desc: coral.Texture.Desc) std.mem.Allocator.Error!Texture { const pixel_format = switch (desc.format) { .rgba8 => sokol.gfx.PixelFormat.RGBA8, .bgra8 => sokol.gfx.PixelFormat.BGRA8, @@ -384,9 +384,9 @@ pub const Texture = struct { } }; -const TexturePool = coral.Pool(Texture); +const TexturePool = ona.Pool(Texture); -pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect { +pub fn create_effect(self: *Self, desc: coral.Effect.Desc) !coral.Effect { var effect = try Effect.init(desc); errdefer effect.deinit(); @@ -394,7 +394,7 @@ pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect { return @enumFromInt(try self.effects.insert(effect)); } -pub fn create_texture(self: *Self, desc: gfx.Texture.Desc) !gfx.Texture { +pub fn create_texture(self: *Self, desc: coral.Texture.Desc) !coral.Texture { var texture = try Texture.init(desc); errdefer texture.deinit(); @@ -422,7 +422,7 @@ pub fn deinit(self: *Self) void { self.* = undefined; } -pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool { +pub fn destroy_effect(self: *Self, handle: coral.Effect) bool { switch (handle) { .default => {}, @@ -438,7 +438,7 @@ pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool { return true; } -pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool { +pub fn destroy_texture(self: *Self, handle: coral.Texture) bool { switch (handle) { .default => {}, @@ -454,18 +454,18 @@ pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool { return true; } -pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect { +pub fn get_effect(self: *Self, handle: coral.Effect) ?*Effect { return self.effects.get(@intFromEnum(handle)); } -pub fn get_texture(self: *Self, handle: gfx.Texture) ?*Texture { +pub fn get_texture(self: *Self, handle: coral.Texture) ?*Texture { return self.textures.get(@intFromEnum(handle)); } pub fn init() !Self { var pools = Self{ - .effects = EffectPool.init(coral.heap.allocator), - .textures = TexturePool.init(coral.heap.allocator), + .effects = EffectPool.init(ona.heap.allocator), + .textures = TexturePool.init(ona.heap.allocator), }; errdefer { @@ -478,11 +478,11 @@ pub fn init() !Self { } }; - assert.is_handle(gfx.Effect.default, try pools.create_effect(.{ + assert.is_handle(coral.Effect.default, try pools.create_effect(.{ .fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), })); - assert.is_handle(gfx.Texture.default, try pools.create_texture(.{ + assert.is_handle(coral.Texture.default, try pools.create_texture(.{ .format = .rgba8, .access = .{ @@ -503,7 +503,7 @@ pub fn init() !Self { }, })); - assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{ + assert.is_handle(coral.Texture.backbuffer, try pools.create_texture(.{ .format = .rgba8, .access = .{ diff --git a/src/coral/colors.zig b/src/coral/colors.zig new file mode 100644 index 0000000..ddc6624 --- /dev/null +++ b/src/coral/colors.zig @@ -0,0 +1,13 @@ +const coral = @import("./coral.zig"); + +pub const black = greyscale(0); + +pub const white = greyscale(1); + +pub fn greyscale(v: f32) coral.Color { + return .{v, v, v, 1}; +} + +pub fn rgb(r: f32, g: f32, b: f32) coral.Color { + return .{r, g, b, 1}; +} diff --git a/src/coral/coral.zig b/src/coral/coral.zig index e7ce11c..5b61d1c 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -1,125 +1,524 @@ -pub const ascii = @import("./ascii.zig"); +pub const colors = @import("./colors.zig"); -pub const asyncio = @import("./asyncio.zig"); +const input = @import("input"); -pub const files = @import("./files.zig"); +const ona = @import("ona"); -pub const hashes = @import("./hashes.zig"); +const ext = @cImport({ + @cInclude("SDL2/SDL.h"); +}); -pub const heap = @import("./heap.zig"); - -pub const io = @import("./io.zig"); - -pub const lina = @import("./lina.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"); +const rendering = @import("./rendering.zig"); const std = @import("std"); -pub const utf8 = @import("./utf8.zig"); +pub const Assets = struct { + window: *ext.SDL_Window, + texture_formats: ona.stack.Sequential(TextureFormat), + frame_rendered: std.Thread.ResetEvent = .{}, + pending_work: WorkQueue = .{}, + has_worker_thread: ?std.Thread = null, -pub fn Pool(comptime Value: type) type { - return struct { - entries: stack.Sequential(Entry), - first_free_index: usize = 0, + pub const LoadError = std.mem.Allocator.Error; - const Entry = union (enum) { - free_index: usize, - occupied: Value, + 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, union (enum) { + load_effect: LoadEffectWork, + load_texture: LoadTextureWork, + render_frame: RenderFrameWork, + shutdown, + unload_effect: UnloadEffectWork, + unload_texture: UnloadTextureWork, + + const LoadEffectWork = struct { + desc: Effect.Desc, + loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect), }; - pub const Values = struct { - cursor: usize = 0, - pool: *const Self, + const LoadTextureWork = struct { + desc: Texture.Desc, + loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture), + }; - pub fn next(self: *Values) ?*Value { - while (self.cursor < self.pool.entries.len()) { - defer self.cursor += 1; + const RenderFrameWork = struct { + clear_color: Color, + width: u16, + height: u16, + finished: *std.Thread.ResetEvent, + has_command_params: ?*ona.Params(Commands).Node, + }; - switch (self.pool.entries.values[self.cursor]) { - .free_index => { - continue; - }, + const UnloadEffectWork = struct { + handle: Effect, + }; - .occupied => |*occupied| { - return occupied; - }, - } - } + const UnloadTextureWork = struct { + handle: Texture, + }; + }); - return null; + fn deinit(self: *Assets) void { + self.pending_work.enqueue(.shutdown); + + if (self.has_worker_thread) |worker_thread| { + worker_thread.join(); + } + + self.texture_formats.deinit(); + + self.* = undefined; + } + + fn init() !Assets { + const window = create: { + const position = ext.SDL_WINDOWPOS_CENTERED; + const flags = ext.SDL_WINDOW_OPENGL; + const width = 640; + const height = 480; + + break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse { + return error.Unsupported; + }; + }; + + errdefer { + ext.SDL_DestroyWindow(window); + } + + return .{ + .texture_formats = .{.allocator = ona.heap.allocator}, + .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 fn load_texture_file(self: *Assets, storage: ona.files.Storage, path: []const u8) LoadFileError!Texture { + var arena = std.heap.ArenaAllocator.init(ona.heap.allocator); + + defer { + arena.deinit(); + } + + for (self.texture_formats.values) |format| { + if (!std.mem.endsWith(u8, path, format.extension)) { + continue; } - }; - const Self = @This(); + return self.load_texture(try format.load_file(&arena, storage, path)); + } - pub fn deinit(self: *Self) void { - self.entries.deinit(); + return error.FormatUnsupported; + } + + pub const thread_restriction = .main; +}; + +pub const Color = @Vector(4, f32); + +pub const Commands = struct { + pending: *List, + + const Command = union (enum) { + draw_texture: DrawTextureCommand, + set_effect: SetEffectCommand, + set_target: SetTargetCommand, + }; + + pub const DrawTextureCommand = struct { + texture: Texture, + transform: Transform2D, + }; + + pub const SetEffectCommand = struct { + effect: Effect, + properties: []const u8, + }; + + pub const SetTargetCommand = struct { + texture: Texture, + clear_color: ?Color, + clear_depth: ?f32, + clear_stencil: ?u8, + }; + + 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; } - pub fn get(self: *Self, key: usize) ?*Value { - return switch (self.entries.values[key]) { - .free_index => null, - .occupied => |*occupied| occupied, - }; - } - - pub fn init(allocator: std.mem.Allocator) Self { + fn init(allocator: std.mem.Allocator) List { 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, + .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; + } + }; + + 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) Commands { + return .{ + .pending = param.pending_list(), + }; + } + + pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{.draw_texture = command}); + } + + pub fn set_effect(self: Commands, command: SetEffectCommand) 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 unbind(param: *Param, _: ona.World.UnbindContext) void { + param.deinit(); + } + + pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void { + try self.pending.stack.push_grow(.{.set_target = command}); + } +}; + +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 Rect = struct { + left: f32, + top: f32, + right: f32, + bottom: f32, +}; + +pub const Texture = enum (u32) { + default, + backbuffer, + _, + + 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 Transform2D = extern struct { + xbasis: Vector = .{1, 0}, + ybasis: Vector = .{0, 1}, + origin: Vector = @splat(0), + + const Vector = @Vector(2, f32); +}; + +fn load_bmp_texture(arena: *std.heap.ArenaAllocator, storage: ona.files.Storage, path: []const u8) !Texture.Desc { + const header = try storage.read_little(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 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 storage.read(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 poll(app: ona.Write(ona.App), events: ona.Send(input.Event)) !void { + var event = @as(ext.SDL_Event, undefined); + + while (ext.SDL_PollEvent(&event) != 0) { + switch (event.type) { + ext.SDL_QUIT => app.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)}), + else => {}, + } + } +} + +pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { + if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) { + return error.Unsupported; + } + + const assets = create: { + var assets = try Assets.init(); + + 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, + }); + + const builtin_texture_formats = [_]Assets.TextureFormat{ + .{ + .extension = "bmp", + .load_file = load_bmp_texture, + }, + }; + + for (builtin_texture_formats) |format| { + try assets.texture_formats.push_grow(format); + } + + try world.set_state(Display{}); + try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll coral"}); + try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop coral"}); + try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize coral"}); +} + +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) |command_param| { + assets.pending_work.enqueue(.{ + .render_frame = .{ + .has_command_params = command_param, + .width = display.width, + .height = display.height, + .clear_color = display.clear_color, + .finished = &assets.frame_rendered, + }, + }); + } else { + assets.frame_rendered.set(); + } } diff --git a/src/ona/gfx/rendering.zig b/src/coral/rendering.zig similarity index 82% rename from src/ona/gfx/rendering.zig rename to src/coral/rendering.zig index bba5fa6..2e4182f 100644 --- a/src/ona/gfx/rendering.zig +++ b/src/coral/rendering.zig @@ -1,29 +1,31 @@ const Resources = @import("./Resources.zig"); -const coral = @import("coral"); +const coral = @import("./coral.zig"); -const ext = @import("../ext.zig"); +const ext = @cImport({ + @cInclude("SDL2/SDL.h"); +}); -const gfx = @import("../gfx.zig"); - -const spirv = @import("./spirv.zig"); +const ona = @import("ona"); const sokol = @import("sokol"); +const spirv = @import("./spirv.zig"); + const std = @import("std"); const Frame = struct { - texture_batch_buffers: coral.stack.Sequential(sokol.gfx.Buffer), + 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 = .backbuffer, - current_effect: gfx.Effect = .default, + current_source_texture: coral.Texture = .default, + current_target_texture: coral.Texture = .backbuffer, + current_effect: coral.Effect = .default, const DrawTexture = extern struct { - transform: gfx.Transform2D, + transform: coral.Transform2D, tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), depth: f32 = 0, texture_offset: @Vector(2, f32) = @splat(0), @@ -65,15 +67,15 @@ const Frame = struct { }); return .{ - .texture_batch_buffers = .{.allocator = coral.heap.allocator}, + .texture_batch_buffers = .{.allocator = ona.heap.allocator}, .quad_index_buffer = quad_index_buffer, .quad_vertex_buffer = quad_vertex_buffer, }; } - pub fn draw_texture(self: *Frame, pools: *Resources, command: gfx.Commands.DrawTextureCommand) !void { + pub fn draw_texture(self: *Frame, resources: *Resources, command: coral.Commands.DrawTextureCommand) !void { if (command.texture != self.current_source_texture) { - self.flush(pools); + self.flush(resources); } self.current_source_texture = command.texture; @@ -99,8 +101,8 @@ const Frame = struct { self.drawn_count += 1; } - pub fn finish(self: *Frame, pools: *Resources) void { - self.flush(pools); + pub fn finish(self: *Frame, resources: *Resources) void { + self.flush(resources); self.drawn_count = 0; self.flushed_count = 0; @@ -109,7 +111,7 @@ const Frame = struct { self.current_effect = .default; } - pub fn flush(self: *Frame, pools: *Resources) void { + pub fn flush(self: *Frame, resources: *Resources) void { if (self.flushed_count == self.drawn_count) { return; } @@ -120,7 +122,7 @@ const Frame = struct { bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer; - switch (pools.get_texture(self.current_source_texture).?.access) { + switch (resources.get_texture(self.current_source_texture).?.access) { .render => |render| { bindings.fs.images[0] = render.color_image; bindings.fs.samplers[0] = default_sampler; @@ -136,13 +138,13 @@ const Frame = struct { }, } - const effect = pools.get_effect(self.current_effect).?; + const effect = resources.get_effect(self.current_effect).?; sokol.gfx.applyPipeline(effect.pipeline); - const texture = pools.get_texture(self.current_target_texture).?; + const texture = resources.get_texture(self.current_target_texture).?; - sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{ + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{ .left = 0, .top = 0, .right = @floatFromInt(texture.width), @@ -171,19 +173,19 @@ const Frame = struct { } } - pub fn set_effect(self: *Frame, pools: *Resources, command: gfx.Commands.SetEffectCommand) void { + pub fn set_effect(self: *Frame, resources: *Resources, command: coral.Commands.SetEffectCommand) void { if (command.effect != self.current_effect) { - self.flush(pools); + self.flush(resources); } self.current_effect = command.effect; - if (pools.get_effect(self.current_effect)) |effect| { + if (resources.get_effect(self.current_effect)) |effect| { @memcpy(effect.properties, command.properties); } } - pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void { + pub fn set_target(self: *Frame, resources: *Resources, command: coral.Commands.SetTargetCommand) void { sokol.gfx.endPass(); var pass = sokol.gfx.Pass{ @@ -212,7 +214,7 @@ const Frame = struct { pass.action.depth = .{.load_action = .LOAD}; } - pass.attachments = switch (pools.get_texture(self.current_target_texture).?.access) { + pass.attachments = switch (resources.get_texture(self.current_target_texture).?.access) { .static => @panic("Cannot render to static textures"), .empty => @panic("Cannot render to empty textures"), .render => |render| render.attachments, @@ -224,6 +226,10 @@ const Frame = struct { } }; +fn Matrix(comptime n: usize, comptime Element: type) type { + return [n]@Vector(n, Element); +} + var default_sampler: sokol.gfx.Sampler = undefined; const vertex_indices = .{ @@ -231,7 +237,19 @@ const vertex_indices = .{ .instance = 1, }; -pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void { +fn orthographic_projection(near: f32, far: f32, viewport: coral.Rect) Matrix(4, f32) { + 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: *coral.Assets.WorkQueue, window: *ext.SDL_Window) !void { const context = configure_and_create: { var result = @as(c_int, 0); diff --git a/src/coral/script.zig b/src/coral/script.zig deleted file mode 100644 index 99af8ec..0000000 --- a/src/coral/script.zig +++ /dev/null @@ -1,2218 +0,0 @@ -const Chunk = @import("./script/Chunk.zig"); - -const Table = @import("./script/Table.zig"); - -const debug = @import("./debug.zig"); - -const file = @import("./file.zig"); - -const hashes = @import("./hashes.zig"); - -const io = @import("./io.zig"); - -const map = @import("./map.zig"); - -const scalars = @import("./scalars.zig"); - -const stack = @import("./stack.zig"); - -const std = @import("std"); - -const tokens = @import("./script/tokens.zig"); - -const tree = @import("./script/tree.zig"); - -const utf8 = @import("./utf8.zig"); - -/// -/// Archetypes of errors that may occur in the virtual machine during run-time. -/// -pub const Error = std.mem.Allocator.Error || io.Error || error { - IllegalState, - TypeMismatch, - BadOperation, - BadSyntax, -}; - -/// -/// Fixed-length integer number type. -/// -pub const Fixed = i32; - -/// -/// Floating point real number type. -/// -pub const Float = f64; - -/// -/// Supertype for all numeric types. -/// -pub const Numeric = union (enum) { - fixed: Fixed, - float: Float, - vector2: Vector2, - vector3: Vector3, -}; - -/// -/// Runtime-polymorphic data type for representing all data types supported by the virtual machine. -/// -pub const Object = opaque { - const Internal = struct { - ref_count: u16, - - payload: union (enum) { - false, - true, - float: Float, - fixed: Fixed, - symbol: [*:0]const u8, - vector2: Vector2, - vector3: Vector3, - syscall: *const Syscall, - boxed: ?*Object, - - string: struct { - ptr: [*]u8, - len: Fixed, - - const Self = @This(); - - fn unpack(self: Self) []u8 { - std.debug.assert(self.len >= 0); - - return self.ptr[0 .. @intCast(self.len)]; - } - }, - - dynamic: struct { - ptr: [*]io.Byte, - len: u32, - - const Self = @This(); - - fn typeinfo(self: Self) *const Typeinfo { - return @as(**const Typeinfo, @ptrCast(@alignCast(self.ptr))).*; - } - - fn unpack(self: Self) []io.Byte { - return self.ptr[0 .. (@sizeOf(usize) + self.len)]; - } - - fn userdata(self: Self) []io.Byte { - const unpacked = self.unpack(); - const address_size = @sizeOf(usize); - - std.debug.assert(unpacked.len >= address_size); - - return unpacked[address_size ..]; - } - }, - }, - - fn acquire(self: *Internal) *Object { - self.ref_count += 1; - - return @ptrCast(self); - } - }; - - fn allocate(allocator: std.mem.Allocator, data: Internal) std.mem.Allocator.Error!*Object { - const copy = try allocator.create(Internal); - - errdefer allocator.destroy(copy); - - copy.* = data; - - return @ptrCast(copy); - } - - fn internal(self: *const Object) *Internal { - return @constCast(@ptrCast(@alignCast(self))); - } - - /// - /// Returns `true` if the value of `self` is equivalent to `other`, otherwise `false`. - /// - pub fn equals(self: *Object, other: *Object) bool { - return switch (self.internal().payload) { - .false => other.internal().payload == .false, - .true => other.internal().payload == .true, - - .fixed => |lhs_fixed| switch (other.internal().payload) { - .fixed => |rhs_fixed| rhs_fixed == lhs_fixed, - .float => |rhs_float| rhs_float == @as(Float, @floatFromInt(lhs_fixed)), - else => false, - }, - - .float => |lhs_float| switch (other.internal().payload) { - .float => |rhs_float| rhs_float == lhs_float, - .fixed => |rhs_fixed| @as(Float, @floatFromInt(rhs_fixed)) == lhs_float, - else => false, - }, - - .symbol => |lhs_symbol| switch (other.internal().payload) { - .symbol => |rhs_symbol| lhs_symbol == rhs_symbol, - else => false, - }, - - .boxed => |boxed| if (boxed) |object| self.equals(object) else false, - - .vector2 => |lhs_vector2| switch (other.internal().payload) { - .vector2 => |rhs_vector2| lhs_vector2[0] == rhs_vector2[0] and lhs_vector2[1] == rhs_vector2[1], - else => false, - }, - - .vector3 => |lhs_vector3| switch (other.internal().payload) { - .vector3 => |rhs_vector3| lhs_vector3[0] == rhs_vector3[0] and lhs_vector3[1] == rhs_vector3[1] and lhs_vector3[2] == rhs_vector3[2], - else => false, - }, - - .syscall => |lhs_syscall| switch (other.internal().payload) { - .syscall => |rhs_syscall| lhs_syscall == rhs_syscall, - else => false, - }, - - .string => |lhs_string| switch (other.internal().payload) { - .string => |rhs_string| io.are_equal(lhs_string.unpack(), rhs_string.unpack()), - else => false, - }, - - .dynamic => |lhs_dynamic| switch (other.internal().payload) { - .dynamic => |rhs_dynamic| - lhs_dynamic.typeinfo() == rhs_dynamic.typeinfo() and - lhs_dynamic.userdata().ptr == rhs_dynamic.userdata().ptr, - - else => false, - }, - }; - } - - /// - /// Computes a hash for `self` based on the held type and value. - /// - pub fn hash(self: *Object) usize { - return switch (self.internal().payload) { - .false => 1237, - .true => 1231, - .float => |float| @bitCast(float), - .fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))), - .symbol => |symbol| @intFromPtr(symbol), - .vector2 => |vector| @bitCast(vector), - .vector3 => |vector| hashes.jenkins(@typeInfo(usize).Int, io.bytes_of(&vector)), - .syscall => |syscall| @intFromPtr(syscall), - .boxed => |boxed| @intFromPtr(boxed), - .string => |string| hashes.djb2(@typeInfo(usize).Int, string.unpack()), - .dynamic => |dynamic| @intFromPtr(dynamic.typeinfo()) ^ @intFromPtr(dynamic.userdata().ptr), - }; - } - - /// - /// Returns the name of the type held in `self`. - /// - pub fn typename(self: *Object) []const u8 { - return switch (self.internal().payload) { - .false => "false", - .true => "true", - .float => "float", - .fixed => "fixed", - .symbol => "symbol", - .vector2 => "vector2", - .vector3 => "vector3", - .syscall => "syscall", - .boxed => "boxed", - .string => "string", - .dynamic => "dynamic", - }; - } - - /// - /// Checks if `self` is a dynamic object with type information matching `typeinfo`, returning the userdata buffer if - /// it is or `null` if it is not. - /// - pub fn is_dynamic(self: *Object, comptime Type: type) ?*Type { - return switch (self.internal().payload) { - .dynamic => |dynamic| @as(?*Type, if (dynamic.typeinfo() == &Type.typeinfo) @ptrCast(@alignCast(dynamic.userdata())) else null), - else => null, - }; - } - - /// - /// Checks if `self` is a false object (i.e. any object that evaluates to false), returning `true` if it is or - /// `false` if it is not. - /// - pub fn is_false(self: *Object) bool { - return !self.is_true(); - } - - /// - /// Checks if `self` is a fixed number object, returning the value if it is or `null` if it is not. - /// - pub fn is_fixed(self: *Object) ?Fixed { - return switch (self.internal().payload) { - .fixed => |fixed| fixed, - else => null, - }; - } - - /// - /// Checks if `self` is a numeric object (i.e. any object that behaves like a number), returning the supertype for - /// further inspection if it is or `null` if it is not. - /// - pub fn is_numeric(self: *Object) ?Numeric { - return switch (self.internal().payload) { - .fixed => |fixed| .{.fixed = fixed}, - .float => |float| .{.float = float}, - .vector2 => |vector2| .{.vector2 = vector2}, - .vector3 => |vector3| .{.vector3 = vector3}, - else => null, - }; - } - - /// - /// Checks if `self` is a string text object, returning the value if it is or `null` if it is not. - /// - pub fn is_string(self: *Object) ?[]const u8 { - return switch (self.internal().payload) { - .string => |string| get: { - std.debug.assert(string.len > -1); - - break: get string.ptr[0 .. @intCast(string.len)]; - }, - - .symbol => |symbol| io.slice_sentineled(@as(u8, 0), symbol), - else => null, - }; - } - - /// - /// Checks if `self` is a symbo text object, returning the value if it is or `null` if it is not. - /// - pub fn is_symbol(self: *Object) ?[*:0]const u8 { - return switch (self.internal().payload) { - .symbol => |symbol| symbol, - else => null, - }; - } - - /// - /// Checks if `self` is a true object (i.e. any object that evaluates to true), returning `true` if it is and - /// `false` if it is not. - /// - pub fn is_true(self: *Object) bool { - return switch (self.internal().payload) { - .false => false, - .true => true, - .float => |float| float != 0, - .fixed => |fixed| fixed != 0, - .symbol => true, - .vector2 => |vector| vector[0] != 0 or vector[1] != 0, - .vector3 => |vector| vector[0] != 0 or vector[1] != 0 or vector[2] != 0, - .syscall => true, - .boxed => |boxed| boxed != null, - .string => |string| string.len != 0, - .dynamic => true, - }; - } - - /// - /// Checks if `self` is a 2-component vector number object, returning the value if it is or `null` if it is not. - /// - pub fn is_vector2(self: *Object) ?Vector2 { - return switch (self.internal().payload) { - .vector2 => |vector2| vector2, - else => null, - }; - } - - /// - /// Checks if `self` is a 3-component vector number object, returning the value if it is or `null` if it is not. - /// - pub fn is_vector3(self: *Object) ?Vector3 { - return switch (self.internal().payload) { - .vector3 => |vector3| vector3, - else => null, - }; - } -}; - -/// -/// Runtime environment virtual machine state. -/// -/// The environment operates on a mostly stack-based architecture, with nearly all functions "returning" computations by -/// writing them to the local values stack within. Functions may be chained together to perform sequential mutations on -/// data and either retrieved or discarded once the computation is done via [Runtime.pop] or [Runtime.discard] -/// respectively. -/// -/// *Note* that improperly cleaning up data pushed to the stack can result in accidental stack overflows, however also -/// note that all locals are always cleaned when exiting their call frame. -/// -pub const Runtime = struct { - allocator: std.mem.Allocator, - options: Options, - interned_symbols: map.StringTable([:0]u8), - locals: LocalList, - frames: FrameList, - - const FrameList = stack.Sequential(struct { - callable: *Object, - locals_top: usize, - arg_count: u8, - - const Self = @This(); - - fn args_of(self: Self, env: *Runtime) []?*Object { - return env.locals.values[self.locals_top .. (self.locals_top + self.arg_count)]; - } - - fn locals_of(self: Self, env: *Runtime) []?*Object { - return env.locals.values[self.locals_top ..]; - } - }); - - const LocalList = stack.Sequential(?*Object); - - /// - /// Optional settings for a [Runtime]. - /// - pub const Options = struct { - file_system: file.System = .data, - }; - - /// - /// Attempts to push the argument located at `arg_index` to the top of `self`, pushing `null` instead if the given - /// argument does not exist or was provided as a nil value. - /// - /// Arguments are indexed according to the order they are passed, with `0` referring to the first argument position. - /// - /// A [Error] is returned if `self` is out of memory or the virtual machine is not inside a managed call - /// frame. - /// - /// `self` is returned for function chaining. - /// - pub fn arg_get(self: *Runtime, arg_index: Local) Error!*Runtime { - const frame = self.frames.peek() orelse { - return self.raise(error.IllegalState, "cannot get args outside of a call frame", .{}); - }; - - const args = frame.args_of(self); - - if (arg_index < args.len) { - return self.push(args[arg_index]); - } - - try self.locals.push(null); - - return self; - } - - /// - /// Attempts to pop the top-most value of `self` and call it with `local_arg_count` as the number of locals prior to - /// it in `self` that are intended to be arguments to it. Once the callable returns, the locals marked as arguments - /// are popped as well. - /// - /// A `local_arg_count` of `0` will call the function with no arguments and pop nothing other than the callable from - /// `self` during invocation. - /// - /// A [Error] is returned if `self` is out of memory, the top-most local is nil or not callable, or the - /// callable raises a runtime error during invocation. - /// - /// `self` is returned for function chaining. - /// - pub fn call(self: *Runtime, local_arg_count: Local) Error!*Runtime { - const callable = try self.expect_object(self.pop()); - - defer self.release(callable); - - const result = get_result: { - try self.frames.push(.{ - .locals_top = self.locals.values.len - local_arg_count, - .callable = callable.internal().acquire(), - .arg_count = local_arg_count, - }); - - defer { - const popped_frame = self.frames.pop().?; - - self.release(popped_frame.callable); - std.debug.assert(popped_frame.locals_top <= self.locals.values.len); - - var to_discard = self.locals.values.len - popped_frame.locals_top; - - while (to_discard != 0) : (to_discard -= 1) { - self.discard(); - } - } - - const frame = &self.frames.values[self.frames.values.len - 1]; - - break: get_result try switch (frame.callable.internal().payload) { - .syscall => |syscall| syscall(self), - - .dynamic => |dynamic| dynamic.typeinfo().call(.{ - .userdata = dynamic.userdata(), - .env = self, - }), - - else => self.raise(error.TypeMismatch, "{typename} is not callable", .{ - .typename = frame.callable.typename(), - }), - }; - }; - - errdefer { - if (result) |object| { - self.release(object); - } - } - - try self.locals.push(result); - - return self; - } - - pub fn count(self: *Runtime, countable: *Object) Error!Fixed { - return switch (countable.internal().payload) { - .dynamic => |dynamic| dynamic.typeinfo().count(.{ - .userdata = dynamic.userdata(), - .env = self, - }), - - else => self.raise(error.TypeMismatch, "{typename} is not countable", .{ - .typename = countable.typename(), - }), - }; - } - - /// - /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the - /// local is greater than `rhs_comparable`, otherwise pushing a false value. - /// - /// A [Error] is returned if `self` is out of memory, the top-most local is nil or not comparable, or - /// `rhs_comparable` is not comparable. - /// - /// `self` is returned for function chaining. - /// - pub fn compare_greater(self: *Runtime, rhs_comparable: *Object) Error!*Runtime { - const lhs_comparable = try self.expect_object(self.pop()); - - defer self.release(lhs_comparable); - - return switch (lhs_comparable.internal().payload) { - .fixed => |lhs_fixed| switch (rhs_comparable.internal().payload) { - .fixed => |rhs_fixed| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) > @as(Float, @floatFromInt(rhs_fixed))), - .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) > rhs_float), - - else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.typename(), - }), - }, - - .float => |lhs_float| switch (rhs_comparable.internal().payload) { - .float => |rhs_float| self.new_boolean(lhs_float > rhs_float), - .fixed => |rhs_fixed| self.new_boolean(lhs_float > @as(Float, @floatFromInt(rhs_fixed))), - - else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.typename(), - }), - }, - - else => self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.typename(), - }), - }; - } - - /// - /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the - /// local is greater than or equal to `rhs_comparable`, otherwise pushing a false value. - /// - /// A [Error] is returned if `self` is out of memory, the top-most local is nil or not comparable, or - /// `rhs_comparable` is not comparable. - /// - /// `self` is returned for function chaining. - /// - pub fn compare_greater_equals(self: *Runtime, rhs_comparable: *Object) Error!*Runtime { - const lhs_comparable = try self.expect_object(self.pop()); - - defer self.release(lhs_comparable); - - return switch (lhs_comparable.internal().payload) { - .fixed => |lhs_fixed| switch (rhs_comparable.internal().payload) { - .fixed => |rhs_fixed| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) >= @as(Float, @floatFromInt(rhs_fixed))), - .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) >= rhs_float), - - else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.typename(), - }), - }, - - .float => |lhs_float| switch (rhs_comparable.internal().payload) { - .float => |rhs_float| self.new_boolean(lhs_float >= rhs_float), - .fixed => |rhs_fixed| self.new_boolean(lhs_float >= @as(Float, @floatFromInt(rhs_fixed))), - - else => self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.typename(), - }), - }, - - else => self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.typename(), - }), - }; - } - - /// - /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the - /// local is less than `rhs_comparable`, otherwise pushing a false value. - /// - /// A [Error] is returned if `self` is out of memory, the top-most local is nil or not comparable, or - /// `rhs_comparable` is not comparable. - /// - /// `self` is returned for function chaining. - /// - pub fn compare_less(self: *Runtime, rhs_comparable: *Object) Error!*Runtime { - const lhs_comparable = try self.expect_object(self.pop()); - - defer self.release(lhs_comparable); - - return switch (lhs_comparable.internal().payload) { - .fixed => |lhs_fixed| switch (rhs_comparable.internal().payload) { - .fixed => |rhs_fixed| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) < @as(Float, @floatFromInt(rhs_fixed))), - .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) < rhs_float), - - else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.typename(), - }), - }, - - .float => |lhs_float| switch (rhs_comparable.internal().payload) { - .float => |rhs_float| self.new_boolean(lhs_float < rhs_float), - .fixed => |rhs_fixed| self.new_boolean(lhs_float < @as(Float, @floatFromInt(rhs_fixed))), - - else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.typename(), - }), - }, - - else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.typename(), - }), - }; - } - - /// - /// Attempts to pop and compare the top-most local in `self` with `rhs_comparable`, pushing a true value if the - /// local is less than or equal to `rhs_comparable`, otherwise pushing a false value. - /// - /// A [Error] is returned if `self` is out of memory, the top-most local is nil or not comparable, or - /// `rhs_comparable` is not comparable. - /// - /// `self` is returned for function chaining. - /// - pub fn compare_less_equals(self: *Runtime, rhs_comparable: *Object) Error!*Runtime { - const lhs_comparable = try self.expect_object(self.pop()); - - defer self.release(lhs_comparable); - - return switch (lhs_comparable.internal().payload) { - .fixed => |lhs_fixed| switch (rhs_comparable.internal().payload) { - .fixed => |rhs_fixed| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) <= @as(Float, @floatFromInt(rhs_fixed))), - .float => |rhs_float| self.new_boolean(@as(Float, @floatFromInt(lhs_fixed)) <= rhs_float), - - else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.typename(), - }), - }, - - .float => |lhs_float| switch (rhs_comparable.internal().payload) { - .float => |rhs_float| self.new_boolean(lhs_float <= rhs_float), - .fixed => |rhs_fixed| self.new_boolean(lhs_float <= @as(Float, @floatFromInt(rhs_fixed))), - - else => return self.raise(error.TypeMismatch, "right-hand {typename} is not comparable", .{ - .typename = rhs_comparable.typename(), - }), - }, - - else => return self.raise(error.TypeMismatch, "left-hand {typename} is not comparable", .{ - .typename = lhs_comparable.typename(), - }), - }; - } - - /// - /// Attempts to pop `local_concat_count` number of locals from the stack and concatenate them together as one long - /// string object, pushing the result to the top of `self`. - /// - /// A `local_concat_count` of `0` will push an empty string object and pop nothing from `self`. - /// - /// A [Error] is returned if `self` is out of memory or one of the concatenated objects raises an error from - /// within it's to_string implementation. - /// - /// `self` is returned for function chaining. - /// - pub fn concat(self: *Runtime, local_concat_count: Local) Error!*Runtime { - if (local_concat_count == 0) { - return self.new_string(""); - } - - const concated_buffer = build_buffer: { - var concat_buffer = stack.Sequential(u8){.allocator = self.allocator}; - - errdefer concat_buffer.deinit(); - - const concat_values = self.locals.values[(self.locals.values.len - local_concat_count) .. self.locals.values.len]; - - for (concat_values) |concat_value| { - const string = (try (try self.push(concat_value)).to_string()).pop().?; - - defer self.release(string); - - try concat_buffer.push_all(string.is_string().?); - } - - { - var concated_value_count = concat_values.len; - - while (concated_value_count != 0) : (concated_value_count -= 1) { - self.discard(); - } - } - - break: build_buffer try concat_buffer.to_allocation(concat_buffer.values.len, 0); - }; - - errdefer self.allocator.free(concated_buffer); - - const string = try Object.allocate(self.allocator, .{ - .ref_count = 1, - - .payload = .{ - .string = .{ - .ptr = concated_buffer.ptr, - .len = @intCast(concated_buffer.len), - }, - }, - }); - - errdefer self.release(string); - - try self.locals.push(string); - - return self; - } - - /// - /// Deinitializes `self`, freeing all allocated virtual machine resources. - /// - pub fn deinit(self: *Runtime) void { - while (self.locals.pop()) |local| { - if (local.*) |ref| { - self.release(ref); - } - } - - { - var entries = self.interned_symbols.entries(); - - while (entries.next()) |entry| { - self.allocator.free(entry.value); - } - } - - self.interned_symbols.deinit(); - self.locals.deinit(); - self.frames.deinit(); - } - - /// - /// Pops the top-most value of `self` and releases it. - /// - pub fn discard(self: *Runtime) void { - if (self.pop()) |popped| { - self.release(popped); - } - } - - pub fn equals_nil(self: *Runtime) Error!*Runtime { - if (self.pop()) |lhs_object| { - defer self.release(lhs_object); - - return self.new_boolean(false); - } - - return self.new_boolean(true); - } - - pub fn equals_object(self: *Runtime, rhs_object: *Object) Error!*Runtime { - if (self.pop()) |lhs_object| { - defer self.release(lhs_object); - - return self.new_boolean(lhs_object.equals(rhs_object)); - } - - return self.new_boolean(false); - } - - pub fn expect_dynamic(self: *Runtime, value: *Object, comptime Type: type) Error!*Type { - return value.is_dynamic(Type) orelse self.raise(error.TypeMismatch, "expected dynamic object, not {typename}", .{ - .typename = value.typename(), - }); - } - - pub fn expect_float(self: *Runtime, value: *Object) Error!Float { - return switch (value.internal().payload) { - .fixed => |fixed| @floatFromInt(fixed), - .float => |float| float, - - else => self.raise(error.TypeMismatch, "expected float type, not {typename}", .{ - .typename = value.typename(), - }), - }; - } - - pub fn expect_fixed(self: *Runtime, value: *Object) Error!Fixed { - return value.is_fixed() orelse self.raise(error.TypeMismatch, "expected fixed type, not {typename}", .{ - .typename = value.typename(), - }); - } - - pub fn expect_numeric(self: *Runtime, value: *Object) Error!Numeric { - return value.is_numeric() orelse self.raise(error.TypeMismatch, "expected numeric type, not {typename}", .{ - .typename = value.typename(), - }); - } - - pub fn expect_object(self: *Runtime, value: ?*Object) Error!*Object { - return value orelse self.raise(error.TypeMismatch, "expected object type, not nil", .{}); - } - - pub fn expect_string(self: *Runtime, value: *Object) Error![]const u8 { - return value.is_string() orelse self.raise(error.TypeMismatch, "expected string type, not {typename}", .{ - .typename = value.typename(), - }); - } - - pub fn expect_symbol(self: *Runtime, value: *Object) Error![*:0]const u8 { - return value.is_symbol() orelse self.raise(error.TypeMismatch, "expected symbol type, not {typename}", .{ - .typename = value.typename(), - }); - } - - /// - /// Attempts to pop the top-most value in `self` and add `rhs_fixed` to it, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_fixed` is not addable with the top-most local. - /// - /// `self` is returned for function chaining. - /// - pub fn fixed_add(self: *Runtime, rhs_fixed: Fixed) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| if (scalars.add(lhs_fixed, rhs_fixed)) |result| - self.new_fixed(result) - else - self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))), - - .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 + @as(Vector2, @splat(@floatFromInt(rhs_fixed)))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 + @as(Vector3, @splat(@floatFromInt(rhs_fixed)))), - - else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ - .typename = addable.typename(), - }), - }; - } - - /// - /// Attempts to pop the top-most value in `self` and divide it with `rhs_fixed` to it, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_fixed` is not divisible with the top-most local. - /// - /// `self` is returned for function chaining. - /// - pub fn fixed_divide(self: *Runtime, rhs_fixed: Fixed) Error!*Runtime { - if (rhs_fixed == 0) { - return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), - .float => |lhs_float| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 / @as(Vector2, @splat(@floatFromInt(rhs_fixed)))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 / @as(Vector3, @splat(@floatFromInt(rhs_fixed)))), - - else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ - .typename = addable.typename(), - }), - }; - } - - /// - /// Attempts to pop the top-most value in `self` and multiply it with `rhs_fixed` to it, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_fixed` is not multiplicable with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn fixed_multiply(self: *Runtime, rhs_fixed: Fixed) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| if (scalars.mul(lhs_fixed, rhs_fixed)) |result| - self.new_fixed(result) - else - self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed))), - - .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 * @as(Vector2, @splat(@floatFromInt(rhs_fixed)))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 * @as(Vector3, @splat(@floatFromInt(rhs_fixed)))), - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.typename(), - }), - }; - } - - /// - /// Attempts to pop the top-most value in `self` and subtract it with `rhs_fixed`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_fixed` is not subtractable with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn fixed_subtract(self: *Runtime, rhs_fixed: Fixed) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| if (scalars.sub(lhs_fixed, rhs_fixed)) |result| - self.new_fixed(result) - else - self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed))), - - .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 - @as(Vector2, @splat(@floatFromInt(rhs_fixed)))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 - @as(Vector3, @splat(@floatFromInt(rhs_fixed)))), - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.typename(), - }), - }; - } - - /// - /// Attempts to pop the top-most value in `self` and add it with `rhs_float`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_float` is not addable with the top-most local. - /// - /// `self` is returned for function chaining. - /// - pub fn float_add(self: *Runtime, rhs_float: Float) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), - .float => |lhs_float| self.new_float(lhs_float + lhs_float), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 + @as(Vector2, @splat(@floatCast(rhs_float)))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 + @as(Vector3, @splat(@floatCast(rhs_float)))), - - else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ - .typename = addable.typename(), - }), - }; - } - - /// - /// Attempts to pop the top-most value in `self` and divide it with `rhs_float` to it, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_float` is not divisible with the top-most local. - /// - /// `self` is returned for function chaining. - /// - pub fn float_divide(self: *Runtime, rhs_float: Float) Error!*Runtime { - if (rhs_float == 0) { - return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), - .float => |lhs_float| self.new_float(lhs_float / lhs_float), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 / @as(Vector2, @splat(@floatCast(rhs_float)))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 / @as(Vector3, @splat(@floatCast(rhs_float)))), - - else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ - .typename = addable.typename(), - }), - }; - } - - /// - /// Attempts to pop the top-most value in `self` and multiply it with `rhs_float`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_float` is not multiplicable with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn float_multiply(self: *Runtime, rhs_float: Float) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), - .float => |lhs_float| self.new_float(lhs_float * lhs_float), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 * @as(Vector2, @splat(@floatCast(rhs_float)))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 * @as(Vector3, @splat(@floatCast(rhs_float)))), - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.typename(), - }), - }; - } - - /// - /// Attempts to pop the top-most value in `self` and subtract it with `rhs_float`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_float` is not subtractable with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn float_subtract(self: *Runtime, rhs_float: Float) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return switch (addable.internal().payload) { - .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), - .float => |lhs_float| self.new_float(lhs_float - lhs_float), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 - @as(Vector2, @splat(@floatCast(rhs_float)))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 - @as(Vector3, @splat(@floatCast(rhs_float)))), - - else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ - .typename = addable.typename(), - }), - }; - } - - /// - /// TODO: doc comment. - /// - /// A [Error] is returned if `self` is out of memory, the top-most local is nil or not indexable, or the - /// object-specific indexing operation raised an error. - /// - /// `self` is returned for function chaining. - /// - pub fn index_get(self: *Runtime, indexable: *Object) Error!*Runtime { - const vector_max = 3; - const index = try self.expect_object(self.pop()); - - defer self.release(index); - - switch (indexable.internal().payload) { - .vector2 => |vector2| { - const swizzle_symbol = try self.expect_symbol(index); - var swizzle_buffer = [_]f32{0} ** vector_max; - var swizzle_count = @as(usize, 0); - - while (true) : (swizzle_count += 1) { - if (swizzle_count > swizzle_buffer.len) { - try self.locals.push(null); - - return self; - } - - swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) { - 'x' => vector2[0], - 'y' => vector2[1], - - 0 => return switch (swizzle_count) { - 1 => self.new_float(swizzle_buffer[0]), - 2 => self.new_vector2(.{swizzle_buffer[0], swizzle_buffer[1]}), - 3 => self.new_vector3(swizzle_buffer), - else => unreachable, - }, - - else => return self.raise(error.BadOperation, "no such vector2 index or swizzle: `{index}`", .{ - .index = io.slice_sentineled(@as(u8, 0), swizzle_symbol), - }), - }; - } - }, - - .vector3 => |vector3| { - const swizzle_symbol = try self.expect_symbol(index); - var swizzle_buffer = [_]f32{0} ** vector_max; - var swizzle_count = @as(usize, 0); - - while (true) : (swizzle_count += 1) { - if (swizzle_count > swizzle_buffer.len) { - try self.locals.push(null); - - return self; - } - - swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) { - 'x' => vector3[0], - 'y' => vector3[1], - 'z' => vector3[2], - - 0 => return switch (swizzle_count) { - 1 => self.new_float(swizzle_buffer[0]), - 2 => self.new_vector2(.{swizzle_buffer[0], swizzle_buffer[1]}), - 3 => self.new_vector3(swizzle_buffer), - else => unreachable, - }, - - else => return self.raise(error.BadOperation, "no such vector3 index or swizzle: `{index}`", .{ - .index = io.slice_sentineled(@as(u8, 0), swizzle_symbol), - }), - }; - } - }, - - .dynamic => |dynamic| { - return self.push(get_value: { - _ = try self.push(index); - - const value = try dynamic.typeinfo().get(.{ - .userdata = dynamic.userdata(), - .env = self, - }); - - self.release(try self.expect_object(self.pop())); - - break: get_value value; - }); - }, - - else => return self.raise(error.TypeMismatch, "{typename} is not get-indexable", .{ - .typename = indexable.typename(), - }), - } - } - - /// - /// TODO: doc comment. - /// - /// A [Error] is returned if `self` is out of memory, the top-most local is nil or not indexable, or the - /// object-specific indexing operation raised an error. - /// - pub fn index_set(self: *Runtime, indexable: *Object, value: ?*Object) Error!void { - switch (indexable.internal().payload) { - .dynamic => |dynamic| { - _ = try self.push(value); - - try dynamic.typeinfo().set(.{ - .userdata = dynamic.userdata(), - .env = self, - }); - - if (self.pop()) |object| { - self.release(object); - } - - self.release(try self.expect_object(self.pop())); - }, - - else => return self.raise(error.TypeMismatch, "{typename} is not set-indexable", .{ - .typename = indexable.typename(), - }), - } - } - - /// - /// Attempts to import a script from the import [file.Access] (specified during [Runtime.init]) from `file_system` - /// and `file_path`. - /// - /// TODO: comment externals. - /// - /// If the loaded script is a plain text file, it will be read, parsed, and compiled into an optimized format before - /// being executed. - /// - /// If the loaded script is already compiled, it is immediately executed. - /// - /// After completing execution, the return value is pushed to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory, no script at `script_url` could be found by the runtime, the - /// script is a source file and contains invalid syntax, or the imported script raised an error during execution. - /// - /// Any syntax errors are reported using [print_error] prior to raising a runtime error. - /// - /// `self` is returned for function chaining. - /// - pub fn import(self: *Runtime, script_url: file.URL, externals: []const Chunk.External) Error!*Runtime { - { - const callable = new_chunk: { - var script_file = script_url.open() catch { - return self.raise(error.BadOperation, "failed to open `{name}`", .{.name = script_url.path.data()}); - }; - - defer script_file.close(); - - const script_text = io.read_alloc(script_file.reader(), self.allocator) catch { - return self.raise(error.BadOperation, "failed to read `{name}`", .{.name = script_url.path.data()}); - }; - - defer self.allocator.free(script_text); - - var root = try tree.Root.init(self.allocator); - - defer root.deinit(); - - { - var stream = tokens.Stream{.source = script_text}; - - root.parse(&stream) catch |parse_error| { - for (root.error_messages.values) |error_message| { - std.log.err("{s}", .{error_message}); - } - - return self.raise(parse_error, "failed to parse `{name}`", .{.name = script_url.path.data()}); - }; - } - - var chunk = try Chunk.init(self, script_url.path.data(), &root.environment, externals); - - errdefer chunk.deinit(self); - - break: new_chunk (try self.new_dynamic(chunk)).pop().?; - }; - - errdefer self.release(callable); - - try self.locals.push(callable); - } - - return self.call(0); - } - - /// - /// Attempts to initialize a new virtual machine instance with `allocator` as the allocation strategy, `frames_max` - /// as the maximum call depth, and `options` as the optional values to pass to the VM state. - /// - /// This function will fail if `allocator` cannot allocate the memory required to create the runtime environment. - /// - pub fn init(allocator: std.mem.Allocator, frames_max: u32, options: Options) std.mem.Allocator.Error!Runtime { - var frames = FrameList{.allocator = allocator}; - - errdefer frames.deinit(); - - try frames.grow(frames_max); - - var locals = LocalList{.allocator = allocator}; - - errdefer locals.deinit(); - - try locals.grow(local_max * frames_max); - - return .{ - .interned_symbols = .{ - .allocator = allocator, - .traits = .{}, - }, - - .locals = locals, - .frames = frames, - .allocator = allocator, - .options = options, - }; - } - - const Local = u8; - - const local_max = std.math.maxInt(Local); - - /// - /// Attempts to push the local located at `index` to the top of `self`. - /// - /// Locals are indexed according to the order they are declared, with `0` referring to the first declared arg, let, - /// or var in the current frame. - /// - /// A [Error] is returned if `self` is out of memory, `index` does not refer to a valid local, or the - /// virtual-machine is not in a managed call frame. - /// - /// `self` is returned for function chaining. - /// - pub fn local_get(self: *Runtime, index: Local) Error!*Runtime { - const frame = self.frames.peek() orelse { - return self.raise(error.IllegalState, "cannot get locals outside of a call frame", .{}); - }; - - const locals = frame.locals_of(self); - - if (index >= locals.len) { - return self.raise(error.IllegalState, "invalid local get", .{}); - } - - return self.push(locals[index]); - } - - /// - /// Attempts to set the local located at `index` to `value`. - /// - /// Locals are indexed according to the order they are declared, with `0` referring to the first declared arg, let, - /// or var in the current frame. - /// - /// A [Error] if`index` does not refer to a valid local, or the virtual-machine is not in a managed call - /// frame. - /// - /// `self` is returned for function chaining. - /// - pub fn local_set(self: *Runtime, index: Local, value: ?*Object) Error!void { - const frame = self.frames.peek() orelse { - return self.raise(error.IllegalState, "cannot get locals outside of a call frame", .{}); - }; - - const locals = frame.locals_of(self); - - if (index >= locals.len) { - return self.raise(error.IllegalState, "invalid local get", .{}); - } - - const local = &locals[index]; - - if (local.*) |previous_local| { - self.release(previous_local); - } - - local.* = if (value) |object| object.internal().acquire() else null; - } - - /// - /// Attempts to push a copy of the top-most local in `self`. - /// - /// A [Error] is returned if `self` is out of memory, the virtual-machine is not in a managed call frame, or - /// there are no locals in the frame. - /// - /// `self` is returned for function chaining. - /// - pub fn local_top(self: *Runtime) Error!*Runtime { - const frame = self.frames.peek() orelse { - return self.raise(error.IllegalState, "cannot get locals outside of a call frame", .{}); - }; - - const locals = frame.locals_of(self); - - if (locals.len != 0) { - return self.raise(error.IllegalState, "invalid local top", .{}); - } - - return self.push(locals[locals.len - 1]); - } - - /// - /// Pops the top-most local from `self`, negates it, and pushes the result. - /// - /// A [Error] is returned if `self` is out of memory or the top-most local is nil or not negatable. - /// - /// `self` is returned for function chaining. - /// - pub fn neg(self: *Runtime) Error!*Runtime { - const negatable = try self.expect_object(self.pop()); - - defer self.release(negatable); - - return switch (negatable.internal().payload) { - .fixed => |fixed| self.new_fixed(-fixed), - .float => |float| self.new_float(-float), - - else => self.raise(error.TypeMismatch, "{typename} is not scalar negatable", .{ - .typename = negatable.typename(), - }), - }; - } - - /// - /// Attempts to create a new true or false object according to `value`, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_boolean(self: *Runtime, value: bool) Error!*Runtime { - const boolean = try Object.allocate(self.allocator, .{ - .ref_count = 1, - .payload = if (value) .true else .false, - }); - - errdefer self.release(boolean); - - try self.locals.push(boolean); - - return self; - } - - /// - /// Attempts to create a new boxed object holding a reference to `value`, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_boxed(self: *Runtime, value: ?*Object) Error!*Runtime { - const boxed = try Object.allocate(self.allocator, .{ - .ref_count = 1, - .payload = .{.boxed = if (value) |object| object.internal().acquire() else null}, - }); - - errdefer self.release(boxed); - - try self.locals.push(boxed); - - return self; - } - - /// - /// Attempts to create a new dynamic object from `userdata` and `typeinfo`, pushing it to the top of `self`. - /// - /// *Note* the size of the type specified in `userdata` must match the size described in `typeinfo` exactly. - /// - /// A [RuntimeError] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_dynamic(self: *Runtime, userdata: anytype) Error!*Runtime { - const Userdata = @TypeOf(userdata); - const userdata_size = @sizeOf(Userdata); - - if (userdata_size > std.math.maxInt(u32)) { - @compileError(@typeName(Userdata) ++ " is too big to be a dynamic object"); - } - - if (!@hasDecl(Userdata, "typeinfo") or @TypeOf(Userdata.typeinfo) != Typeinfo) { - @compileError(@typeName(Userdata) ++ " must contain a `typeinfo` declaration of type `Typeinfo`"); - } - - const dynamic = new: { - const dynamic = try self.allocator.alloc(io.Byte, @sizeOf(usize) + userdata_size); - - errdefer self.allocator.free(dynamic); - - @memcpy(dynamic, io.bytes_of(&&Userdata.typeinfo)); - @memcpy(dynamic[@sizeOf(usize) ..], io.bytes_of(&userdata)); - - break: new try Object.allocate(self.allocator, .{ - .ref_count = 1, - - .payload = .{ - .dynamic = .{ - .ptr = dynamic.ptr, - .len = userdata_size, - }, - }, - }); - }; - - errdefer self.release(dynamic); - - try self.locals.push(dynamic); - - return self; - } - - /// - /// Attempts to create a new fixed-size number object from `value`, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_fixed(self: *Runtime, value: Fixed) Error!*Runtime { - const syscall = try Object.allocate(self.allocator, .{ - .ref_count = 1, - .payload = .{.fixed = value}, - }); - - errdefer self.release(syscall); - - try self.locals.push(syscall); - - return self; - } - - /// - /// Attempts to create a new floating point number object from `value`, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_float(self: *Runtime, value: Float) Error!*Runtime { - const syscall = try Object.allocate(self.allocator, .{ - .ref_count = 1, - .payload = .{.float = value}, - }); - - errdefer self.release(syscall); - - try self.locals.push(syscall); - - return self; - } - - /// - /// Attempts to create a new byte string object from `value`, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_string(self: *Runtime, value: []const u8) Error!*Runtime { - if (value.len > std.math.maxInt(Fixed)) { - return error.OutOfMemory; - } - - const string = new: { - const string = try self.allocator.dupe(u8, value); - - errdefer self.allocator.free(string); - - break: new try Object.allocate(self.allocator, .{ - .payload = .{ - .string = .{ - .ptr = string.ptr, - .len = @intCast(string.len), - }, - }, - - .ref_count = 1, - }); - }; - - errdefer self.release(string); - - try self.locals.push(string); - - return self; - } - - /// - /// Attempts to create a new system-native function object from `value`, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_syscall(self: *Runtime, value: *const Syscall) Error!*Runtime { - const syscall = try Object.allocate(self.allocator, .{ - .ref_count = 1, - .payload = .{.syscall = value}, - }); - - errdefer self.release(syscall); - - try self.locals.push(syscall); - - return self; - } - - /// - /// Attempts to create a new empty table object, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_table(self: *Runtime) Error!*Runtime { - var table = Table.init(self); - - errdefer table.deinit(self); - - return self.new_dynamic(table); - } - - /// - /// Attempts to create a new 2-component vector object from `value`, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_vector2(self: *Runtime, value: Vector2) Error!*Runtime { - const vector2 = try Object.allocate(self.allocator, .{ - .ref_count = 1, - .payload = .{.vector2 = value}, - }); - - errdefer self.release(vector2); - - try self.locals.push(vector2); - - return self; - } - - /// - /// Attempts to create a new 3-component vector object from `value`, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_vector3(self: *Runtime, value: Vector3) Error!*Runtime { - const vector3 = try Object.allocate(self.allocator, .{ - .ref_count = 1, - .payload = .{.vector3 = value}, - }); - - errdefer self.release(vector3); - - try self.locals.push(vector3); - - return self; - } - - /// - /// Attempts to create a new symbol object from `value`, pushing it to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn new_symbol(self: *Runtime, value: []const u8) Error!*Runtime { - const symbol = try Object.allocate(self.allocator, .{ - .payload = .{ - .symbol = self.interned_symbols.lookup(value) orelse create: { - const symbol_string = try self.allocator.dupeZ(u8, value); - - errdefer self.allocator.free(symbol_string); - - std.debug.assert(try self.interned_symbols.insert(symbol_string[0 .. value.len], symbol_string)); - - break: create symbol_string; - }, - }, - - .ref_count = 1, - }); - - errdefer self.release(symbol); - - try self.locals.push(symbol); - - return self; - } - - /// - /// Pops the top-most value of `self`, returning the value. - /// - /// *Note* any returned non-null pointer must be released via [Runtime.release] or else the resources belonging - /// to the objects will never be freed by the runtime. - /// - pub fn pop(self: *Runtime) ?*Object { - return self.locals.pop().?.*; - } - - /// - /// Attempts to push `value` to the top of `self`. - /// - /// A [Error] is returned if `self` is out of memory. - /// - /// `self` is returned for function chaining. - /// - pub fn push(self: *Runtime, value: ?*Object) Error!*Runtime { - if (value) |object| { - const acquired = object.internal().acquire(); - - errdefer self.release(acquired); - - try self.locals.push(acquired); - } else { - try self.locals.push(null); - } - - return self; - } - - /// - /// Creates stack-unwinding runtime error from `error_value` with `format` and `args` as the formatted error message - /// presented with the type of error raised and a full stack-trace. - /// - /// The `error_value` passed is returned so that the error may be raised and returned in a single line. - /// - pub fn raise(self: *Runtime, error_value: Error, comptime format: []const u8, args: anytype) Error { - const formatted_message = utf8.alloc_formatted(self.allocator, format, args) catch |alloc_error| { - return alloc_error; - }; - - defer self.allocator.free(formatted_message); - - std.log.err("{s}", .{formatted_message}); - - if (!self.frames.is_empty()) { - std.log.err("stack trace:", .{}); - - var remaining_frames = self.frames.values.len; - - while (remaining_frames != 0) { - remaining_frames -= 1; - - const callable = self.frames.values[remaining_frames].callable; - const name = (try (try self.push(callable)).to_string()).pop().?; - - defer self.release(name); - - if (callable.is_dynamic(Chunk)) |chunk| { - const line = chunk.lines.values[chunk.cursor]; - - const chunk_name = try utf8.alloc_formatted(self.allocator, "{name}@{line_number}", .{ - .name = get_name: { - const string = name.is_string(); - - std.debug.assert(string != null); - - break: get_name string.?; - }, - - .line_number = line.number, - }); - - defer self.allocator.free(chunk_name); - - std.log.err("{s}", .{chunk_name}); - } else { - std.log.err("{s}", .{name.is_string().?}); - } - } - } - - return error_value; - } - - /// - /// Releases `value` from `self`, decrementing the reference count (if applicable), and releasing all resources - /// belonging to the object once the reference count reaches zero. - /// - pub fn release(self: *Runtime, value: *Object) void { - std.debug.assert(value.internal().ref_count != 0); - - value.internal().ref_count -= 1; - - if (value.internal().ref_count == 0) { - switch (value.internal().payload) { - .false, .true, .float, .fixed, .symbol, .vector2, .vector3, .syscall => {}, - - .boxed => |*boxed| { - if (boxed.*) |boxed_value| { - self.release(boxed_value); - } - }, - - .string => |string| { - std.debug.assert(string.len >= 0); - self.allocator.free(string.ptr[0 .. @intCast(string.len)]); - }, - - .dynamic => |dynamic| { - if (dynamic.typeinfo().destruct) |destruct| { - destruct(.{ - .userdata = dynamic.userdata(), - .env = self, - }); - } - - self.allocator.free(dynamic.unpack()); - }, - } - - self.allocator.destroy(value.internal()); - } - } - - /// - /// Attempts to pop the top-most value of `self`, convert it to a string object representation, and push the result. - /// - /// A [Error] is returned if `self` is out of memory or the to_string implementation of the top-most value of - /// `self` raised an error. - /// - /// `self` is returned for function chaining. - /// - pub fn to_string(self: *Runtime) Error!*Runtime { - const decimal_format = utf8.DecimalFormat.default; - const value = try self.expect_object(self.pop()); - - defer self.release(value); - - return switch (value.internal().payload) { - .false => self.new_string("false"), - .true => self.new_string("true"), - - .fixed => |fixed| convert: { - var string = io.FixedBuffer(32, @as(u8, 0)){}; - - debug.assert_ok(decimal_format.print(string.writer(), fixed)); - - break: convert self.new_string(string.values()); - }, - - .float => |float| convert: { - var string = io.FixedBuffer(32, @as(u8, 0)){}; - - debug.assert_ok(decimal_format.print(string.writer(), float)); - - break: convert self.new_string(string.values()); - }, - - .boxed => |boxed| (try self.push(try self.expect_object(boxed))).to_string(), - .symbol => |symbol| self.new_string(io.slice_sentineled(@as(u8, 0), symbol)), - - .string => acquire: { - try self.locals.push(value.internal().acquire()); - - break: acquire self; - }, - - .vector2 => |vector2| convert: { - var string = io.FixedBuffer(64, @as(u8, 0)){}; - const x, const y = vector2; - - debug.assert_ok(utf8.print_formatted(string.writer(), "vec2({x}, {y})", .{ - .x = x, - .y = y, - })); - - break: convert self.new_string(string.values()); - }, - - .vector3 => |vector3| convert: { - var string = io.FixedBuffer(96, @as(u8, 0)){}; - const x, const y, const z = vector3; - - debug.assert_ok(utf8.print_formatted(string.writer(), "vec3({x}, {y}, {z})", .{ - .x = x, - .y = y, - .z = z, - })); - - break: convert self.new_string(string.values()); - }, - - .syscall => self.new_string("syscall"), - - .dynamic => |dynamic| dynamic_to_string: { - const string = try dynamic.typeinfo().to_string(.{ - .userdata = dynamic.userdata(), - .env = self, - }); - - errdefer self.release(string); - - try self.locals.push(string); - - break: dynamic_to_string self; - }, - }; - } - - /// - /// Attempts to pop the top-most value in `self` and add it with `rhs_vector2`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_vector2` is not addable with the top-most local. - /// - /// `self` is returned for function chaining. - /// - pub fn vector2_add(self: *Runtime, rhs_vector2: Vector2) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2 + rhs_vector2) - else - self.raise(error.TypeMismatch, "vector2 types are not addable with {typename}", .{ - .typename = addable.typename(), - }); - } - - /// - /// Attempts to pop the top-most value in `self` and divide it with `rhs_vector2`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_vector2` is not divisible with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn vector2_divide(self: *Runtime, rhs_vector2: Vector2) Error!*Runtime { - if (rhs_vector2[0] == 0 or rhs_vector2[1] == 0) { - return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2 / rhs_vector2) - else - self.raise(error.TypeMismatch, "vector2 types are not divisible with {typename}", .{ - .typename = addable.typename(), - }); - } - - /// - /// Attempts to pop the top-most value in `self` and multiply it with `rhs_vector2`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_vector2` is not multiplicable with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn vector2_multiply(self: *Runtime, rhs_vector2: Vector2) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2 * rhs_vector2) - else - self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ - .typename = addable.typename(), - }); - } - - /// - /// Attempts to pop the top-most value in `self` and subtract it with `rhs_vector2`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_vector2` is not subtractable with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn vector2_subtract(self: *Runtime, rhs_vector2: Vector2) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2 - rhs_vector2) - else - self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ - .typename = addable.typename(), - }); - } - - /// - /// Attempts to pop the top-most value in `self` and add it with `rhs_vector3`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_vector3` is not addable with the top-most local. - /// - /// `self` is returned for function chaining. - /// - pub fn vector3_add(self: *Runtime, rhs_vector3: Vector3) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3 + rhs_vector3) - else - self.raise(error.TypeMismatch, "vector3 types are not addable with {typename}", .{ - .typename = addable.typename(), - }); - } - - /// - /// Attempts to pop the top-most value in `self` and divide it with `rhs_vector3`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_vector3` is not divisible with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn vector3_divide(self: *Runtime, rhs_vector3: Vector3) Error!*Runtime { - if (rhs_vector3[0] == 0 or rhs_vector3[1] == 0 or rhs_vector3[2] == 0) { - return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); - } - - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3 / rhs_vector3) - else - self.raise(error.TypeMismatch, "vector3 types are not divisible with {typename}", .{ - .typename = addable.typename(), - }); - } - - /// - /// Attempts to pop the top-most value in `self` and multiply it with `rhs_vector3`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_vector3` is not multiplicable with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn vector3_multiply(self: *Runtime, rhs_vector3: Vector3) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3 * rhs_vector3) - else - self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ - .typename = addable.typename(), - }); - } - - /// - /// Attempts to pop the top-most value in `self` and subtract it with `rhs_vector3`, pushing the result. - /// - /// A [Error] is returned if `self` is out of memory or `rhs_vector3` is not subtractable with the top-most - /// local. - /// - /// `self` is returned for function chaining. - /// - pub fn vector3_subtract(self: *Runtime, rhs_vector3: Vector3) Error!*Runtime { - const addable = try self.expect_object(self.pop()); - - defer self.release(addable); - - return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3 - rhs_vector3) - else - self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ - .typename = addable.typename(), - }); - } - - /// - /// Attempts to pop the top-most value in `self`, pushing the unboxed value. - /// - /// A [Error] is returned if `self` is out of memory or the top-most value is nil or not unboxable. - /// - /// `self` is returned for function chaining. - /// - pub fn boxed_get(self: *Runtime) Error!*Runtime { - const unboxable = try self.expect_object(self.pop()); - - defer self.release(unboxable); - - return switch (unboxable.internal().payload) { - .boxed => |boxed| self.push(boxed), - - else => self.raise(error.TypeMismatch, "{typename} is not unboxable", .{ - .typename = unboxable.typename(), - }), - }; - } - - /// - /// Attempts to pop the top-most value in `self`, unboxing it, and replacing the contents with `value`. - /// - /// A [Error] is returned if the top-most value is nil or not unboxable. - /// - pub fn boxed_set(self: *Runtime, value: ?*Object) Error!void { - const unboxable = try self.expect_object(self.pop()); - - defer self.release(unboxable); - - switch (unboxable.internal().payload) { - .boxed => |*unboxed_value| { - if (unboxed_value.*) |unboxed_object| { - self.release(unboxed_object); - } - - unboxed_value.* = if (value) |object| object.internal().acquire() else null; - }, - - else => return self.raise(error.TypeMismatch, "{typename} is not unboxable", .{ - .typename = unboxable.typename(), - }), - } - } -}; - -/// -/// Bridge function type between the virtual machine and native code. -/// -pub const Syscall = fn (env: *Runtime) Error!?*Object; - -/// -/// Type information for dynamically created object types -/// -pub const Typeinfo = struct { - name: []const u8, - destruct: ?*const fn (context: DestructContext) void = null, - to_string: *const fn (context: ToStringContext) Error!*Object = default_to_string, - count: *const fn (context: CountContext) Error!Fixed = default_count, - call: *const fn (context: CallContext) Error!?*Object = default_call, - get: *const fn (context: GetContext) Error!?*Object = default_get, - set: *const fn (context: SetContext) Error!void = default_set, - - /// - /// Context used for calling objects. - /// - pub const CallContext = struct { - env: *Runtime, - userdata: []io.Byte, - }; - - /// - /// - /// - pub const CountContext = struct { - env: *Runtime, - userdata: []io.Byte, - }; - - /// - /// Context used with objects that have special destruction semantics - /// - pub const DestructContext = struct { - env: *Runtime, - userdata: []io.Byte, - }; - - /// - /// Context used for getting indices of objects. - /// - pub const GetContext = struct { - env: *Runtime, - userdata: []io.Byte, - - /// - /// Pushes the index object of the get operation to the top of the stack. - /// - /// *Note* the index is guaranteed to be a non-`null` value. - /// - /// A [Error] is returned if the virtual machine is out of memory. - /// - /// The [Runtime] is returned for function chaining. - /// - pub fn push_index(self: *const GetContext) Error!*Runtime { - std.debug.assert(self.env.locals.values.len > 0); - - return self.env.push(self.env.locals.values[self.env.locals.values.len - 1]); - } - }; - - /// - /// Context used for setting indices of objects to new values. - /// - pub const SetContext = struct { - env: *Runtime, - userdata: []io.Byte, - - /// - /// Pushes the index object of the set operation to the top of the stack. - /// - /// *Note* the index is guaranteed to be a non-`null` value. - /// - /// A [Error] is returned if the virtual machine is out of memory. - /// - /// The [Runtime] is returned for function chaining. - /// - pub fn push_index(self: *const SetContext) Error!*Runtime { - std.debug.assert(self.env.locals.values.len > 0); - - return self.env.push(self.env.locals.values[self.env.locals.values.len - 2]); - } - - /// - /// Pushes the value of the set operation to the top of the stack. - /// - /// A [Error] is returned if the virtual machine is out of memory. - /// - /// The [Runtime] is returned for function chaining. - /// - pub fn push_value(self: *const SetContext) Error!*Runtime { - std.debug.assert(self.env.locals.values.len > 1); - - return self.env.push(self.env.locals.values[self.env.locals.values.len - 1]); - } - }; - - /// - /// Context used for converting objects to string representations. - /// - pub const ToStringContext = struct { - env: *Runtime, - userdata: []io.Byte, - }; - - fn default_call(context: CallContext) Error!?*Object { - return context.env.raise(error.BadOperation, "this dynamic object is not callable", .{}); - } - - fn default_count(context: CountContext) Error!Fixed { - return context.env.raise(error.BadOperation, "this dynamic object is not countable", .{}); - } - - fn default_get(context: GetContext) Error!?*Object { - return context.env.raise(error.BadOperation, "this dynamic object is not get-indexable", .{}); - } - - fn default_set(context: SetContext) Error!void { - return context.env.raise(error.BadOperation, "this dynamic object is not set-indexable", .{}); - } - - fn default_to_string(context: ToStringContext) Error!*Object { - return (try context.env.new_string("{}")).pop().?; - } -}; - -/// -/// 2-component vector type. -/// -pub const Vector2 = @Vector(2, f32); - -/// -/// 3-component vector type. -/// -pub const Vector3 = @Vector(3, f32); - -/// -/// Higher-level wrapper for [Runtime.index_get] that makes it easier to index [Fixed] keys of objects. -/// -pub fn get_at(env: *Runtime, indexable: *Object, index: Fixed) Error!?*Object { - const index_number = (try env.new_fixed(index)).pop(); - - defer env.discard(index_number); - - return (try (try env.push(indexable)).index_get(index_number)).pop(); -} - -/// -/// Higher-level wrapper for [Runtime.index_get] that makes it easier to index field keys of objects. -/// -pub fn get_field(env: *Runtime, indexable: *Object, field: []const u8) Error!?*Object { - const field_symbol = (try env.new_symbol(field)).pop().?; - - defer env.release(field_symbol); - - return (try (try env.push(indexable)).index_get(field_symbol)).pop(); -} - -/// -/// Higher-level wrapper for [Runtime.index_get] that makes it easier to index string keys of objects. -/// -pub fn get_key(env: *Runtime, indexable: *Object, key: []const u8) Error!?*Object { - const key_string = (try env.new_string(key)).pop(); - - defer env.discard(key_string); - - return (try (try env.push(indexable)).index_get(key_string)).pop(); -} diff --git a/src/ona/gfx/shaders/2d_default.frag b/src/coral/shaders/2d_default.frag similarity index 100% rename from src/ona/gfx/shaders/2d_default.frag rename to src/coral/shaders/2d_default.frag diff --git a/src/ona/gfx/shaders/2d_default.vert b/src/coral/shaders/2d_default.vert similarity index 100% rename from src/ona/gfx/shaders/2d_default.vert rename to src/coral/shaders/2d_default.vert diff --git a/src/ona/gfx/spirv.zig b/src/coral/spirv.zig similarity index 97% rename from src/ona/gfx/spirv.zig rename to src/coral/spirv.zig index 65f0186..09254d3 100644 --- a/src/ona/gfx/spirv.zig +++ b/src/coral/spirv.zig @@ -1,10 +1,10 @@ -const coral = @import("coral"); +const coral = @import("./coral.zig"); const ext = @cImport({ @cInclude("spirv-cross/spirv_cross_c.h"); }); -const ona = @import("../ona.zig"); +const ona = @import("ona"); const std = @import("std"); @@ -202,7 +202,7 @@ pub const Stage = struct { } uniform.* = .{ - .name = try coral.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{ + .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)), }), @@ -357,7 +357,7 @@ fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Tar var binding: u32 = 0; for (combined_image_samplers) |combined_image_sampler| { - const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{ + 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)), }); diff --git a/src/flow/World.zig b/src/flow/World.zig deleted file mode 100644 index 9041cc9..0000000 --- a/src/flow/World.zig +++ /dev/null @@ -1,98 +0,0 @@ -const builtin = @import("builtin"); - -const flow = @import("./flow.zig"); - -const coral = @import("coral"); - -const states = @import("./states.zig"); - -const std = @import("std"); - -const system = @import("./system.zig"); - -thread_pool: ?*std.Thread.Pool = null, -thread_restricted_resources: [std.enums.values(states.ThreadRestriction).len]states.Table, -event_systems: coral.stack.Sequential(system.Schedule), - -pub const Event = enum (usize) { _ }; - -const Self = @This(); - -pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event { - var systems = try system.Schedule.init(label); - - errdefer systems.deinit(self); - - const index = self.event_systems.len(); - - try self.event_systems.push_grow(systems); - - return @enumFromInt(index); -} - -pub fn deinit(self: *Self) void { - for (self.event_systems.values) |*schedule| { - schedule.deinit(self); - } - - for (&self.thread_restricted_resources) |*resources| { - resources.deinit(); - } - - if (self.thread_pool) |thread_pool| { - thread_pool.deinit(); - coral.heap.allocator.destroy(thread_pool); - } - - self.event_systems.deinit(); - - self.* = undefined; -} - -pub fn get_params(self: Self, comptime Value: type) flow.Params(Value) { - const params = self.get_state(flow.Params(Value)) orelse { - return .{}; - }; - - return params.*; -} - -pub fn get_state(self: Self, comptime Value: type) ?*Value { - return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(states.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(states.thread_restriction(@TypeOf(value)))].set_get(value); -} - -pub fn init(thread_count: u32) std.Thread.SpawnError!Self { - var world = Self{ - .thread_restricted_resources = .{states.Table.init(), states.Table.init()}, - .event_systems = .{.allocator = coral.heap.allocator}, - }; - - if (thread_count != 0 and !builtin.single_threaded) { - const thread_pool = try coral.heap.allocator.create(std.Thread.Pool); - - try thread_pool.init(.{ - .allocator = coral.heap.allocator, - .n_jobs = thread_count, - }); - - world.thread_pool = thread_pool; - } - - return world; -} - -pub fn on_event(self: *Self, event: Event, action: *const system.Info, order: system.Order) std.mem.Allocator.Error!void { - try self.event_systems.values[@intFromEnum(event)].then(self, action, order); -} - -pub fn run_event(self: *Self, event: Event) anyerror!void { - try self.event_systems.values[@intFromEnum(event)].run(self); -} - -pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void { - try self.thread_restricted_resources[@intFromEnum(states.thread_restriction(@TypeOf(value)))].set(value); -} diff --git a/src/flow/flow.zig b/src/flow/flow.zig deleted file mode 100644 index c9fd36d..0000000 --- a/src/flow/flow.zig +++ /dev/null @@ -1,329 +0,0 @@ -const std = @import("std"); - -pub const states = @import("./states.zig"); - -pub const system = @import("./system.zig"); - -pub const World = @import("./World.zig"); - -pub const Exclusive = struct { - world: *World, - - pub const Param = struct { - world: *World, - }; - - pub fn bind(context: system.BindContext) std.mem.Allocator.Error!Param { - return .{ - .world = context.world, - }; - } - - pub fn init(param: *Param) Exclusive { - return .{ - .world = param.world, - }; - } - - pub const thread_restriction = .main; -}; - -pub fn Params(comptime Value: type) type { - if (!@hasDecl(Value, "Param")) { - @compileError("System parameters must have a Params type declaration"); - } - - 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 Read(comptime Value: type) type { - return Shared(Value, .{ - .thread_restriction = states.thread_restriction(Value), - .read_only = true, - }); -} - -pub const ShareInfo = struct { - thread_restriction: states.ThreadRestriction, - read_only: bool, -}; - -pub fn Shared(comptime Value: type, comptime info: ShareInfo) type { - const value_info = @typeInfo(Value); - - 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, - }, - }, - }, - }), - - 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 { - res: Qualified, - - const Self = @This(); - - pub const Param = struct { - res: Qualified, - }; - - pub fn bind(context: system.BindContext) std.mem.Allocator.Error!Param { - const thread_restriction_name = switch (info.thread_restriction) { - .main => "main thread-restricted ", - .none => "" - }; - - const res = switch (info.read_only) { - true => (try context.register_readable_state_access(Value)), - false => (try context.register_writable_state_access(Value)), - }; - - return .{ - .res = switch (value_info) { - .Optional => res, - - else => res orelse { - @panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{ - thread_restriction_name, - if (info.read_only) "read-only" else "read-write", - @typeName(Value), - })); - }, - }, - }; - } - - pub fn init(param: *Param) Self { - return .{ - .res = param.res, - }; - } - }; -} - -pub fn Write(comptime Value: type) type { - return Shared(Value, .{ - .thread_restriction = states.thread_restriction(Value), - .read_only = false, - }); -} - -fn parameter_type(comptime Value: type) *const system.Info.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: system.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.? != system.BindContext) { - @compileError( - "`bind` fn on " ++ - value_name ++ - " must accept " ++ - @typeName(system.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: system.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 system.Info { - const Call = @TypeOf(call); - - const system_info = comptime generate: { - switch (@typeInfo(Call)) { - .Fn => |call_fn| { - if (call_fn.params.len > system.max_parameters) { - @compileError("number of parameters to `call` cannot be more than 16"); - } - - const systems = struct { - fn run(parameters: []const *const system.Info.Parameter, data: *const [system.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([system.max_parameters]*const system.Info.Parameter, undefined); - var thread_restriction = states.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 (thread_restriction != .none and thread_restriction != parameter.thread_restriction) { - @compileError("a system may not have conflicting thread restrictions"); - } - - thread_restriction = parameter.thread_restriction; - } - - parameters[index] = parameter; - } - - break: generate &.{ - .parameters = parameters, - .parameter_count = call_fn.params.len, - .execute = systems.run, - .thread_restriction = thread_restriction, - }; - }, - - else => @compileError("parameter `call` must be a function"), - } - }; - - return system_info; -} diff --git a/src/flow/states.zig b/src/flow/states.zig deleted file mode 100644 index d5874d5..0000000 --- a/src/flow/states.zig +++ /dev/null @@ -1,87 +0,0 @@ -const coral = @import("coral"); - -const std = @import("std"); - -pub const Table = struct { - arena: std.heap.ArenaAllocator, - table: coral.map.Hashed(TypeID, Entry, coral.map.enum_traits(TypeID)), - - const Entry = struct { - ptr: *anyopaque, - }; - - pub fn deinit(self: *Table) void { - self.table.deinit(); - self.arena.deinit(); - - self.* = undefined; - } - - pub fn get(self: Table, comptime Resource: type) ?*Resource { - if (self.table.get(type_id(Resource))) |entry| { - return @ptrCast(@alignCast(entry.ptr)); - } - - return null; - } - - pub fn init() Table { - return .{ - .arena = std.heap.ArenaAllocator.init(coral.heap.allocator), - .table = .{.allocator = coral.heap.allocator}, - }; - } - - pub fn set_get(self: *Table, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) { - try self.set(value); - - return self.get(@TypeOf(value)).?; - } - - pub fn set(self: *Table, value: anytype) std.mem.Allocator.Error!void { - const Value = @TypeOf(value); - const value_id = 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 ThreadRestriction = enum { - none, - main, -}; - -pub const TypeID = enum (usize) { _ }; - -pub fn type_id(comptime T: type) TypeID { - const TypeHandle = struct { - comptime { - _ = T; - } - - var byte: u8 = 0; - }; - - return @enumFromInt(@intFromPtr(&TypeHandle.byte)); -} - -pub fn thread_restriction(comptime State: type) ThreadRestriction { - if (@hasDecl(State, "thread_restriction")) { - return State.thread_restriction; - } - - return .none; -} diff --git a/src/input/input.zig b/src/input/input.zig new file mode 100644 index 0000000..648210b --- /dev/null +++ b/src/input/input.zig @@ -0,0 +1,274 @@ +const ona = @import("ona"); + +const std = @import("std"); + +pub const Axis = struct { + keys: ?[2]Event.Key = null, +}; + +pub const Event = union (enum) { + key_up: Key, + key_down: Key, +}; + +pub const Key = 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 { + keys_pressed: ScancodeSet = ScancodeSet.initEmpty(), + keys_held: ScancodeSet = ScancodeSet.initEmpty(), + + const ScancodeSet = std.bit_set.StaticBitSet(512); + + pub fn axis_strength(self: Mapping, axis: Axis) f32 { + if (axis.keys) |keys| { + const key_down, const key_up = keys; + const is_key_down_held = self.keys_held.isSet(@intFromEnum(key_down)); + const is_key_up_held = self.keys_held.isSet(@intFromEnum(key_up)); + + if (is_key_down_held or is_key_up_held) { + return + @as(f32, @floatFromInt(@intFromBool(is_key_up_held))) - + @as(f32, @floatFromInt(@intFromBool(is_key_down_held))); + } + } + + return 0; + } +}; + +test "mapping values" { + const axis = Axis{ + .keys = .{.minus, .equal}, + }; + + { + var mapping = Mapping{}; + + try std.testing.expectEqual(mapping.axis_strength(axis), 0); + } + + { + var mapping = Mapping{}; + + mapping.keys_held.set(@intFromEnum(Key.equal)); + + try std.testing.expectEqual(mapping.axis_strength(axis), 1); + } + + { + var mapping = Mapping{}; + + mapping.keys_held.set(@intFromEnum(Key.minus)); + + try std.testing.expectEqual(mapping.axis_strength(axis), -1); + } + + { + var mapping = Mapping{}; + + mapping.keys_held.set(@intFromEnum(Key.minus)); + mapping.keys_held.set(@intFromEnum(Key.equal)); + + try std.testing.expectEqual(mapping.axis_strength(axis), 0); + } +} + +pub fn setup(world: *ona.World, events: ona.App.Events) std.mem.Allocator.Error!void { + try world.set_state(Mapping{}); + + try world.on_event(events.pre_update, ona.system_fn(update), .{ + .label = "update actions", + }); +} + +pub fn update(inputs: ona.msg.Receive(ona.gfx.Input), mapping: ona.Write(Mapping)) void { + mapping.state.keys_pressed = Mapping.ScancodeSet.initEmpty(); + + for (inputs.messages()) |message| { + switch (message) { + .key_down => |key| { + mapping.state.keys_pressed.set(key.scancode()); + mapping.state.keys_held.set(key.scancode()); + }, + + .key_up => |key| { + mapping.state.keys_held.unset(key.scancode()); + }, + } + } +} diff --git a/src/main.zig b/src/main.zig deleted file mode 100644 index a446030..0000000 --- a/src/main.zig +++ /dev/null @@ -1,124 +0,0 @@ -const coral = @import("coral"); - -const std = @import("std"); - -const ona = @import("ona"); - -const ChromaticAberration = extern struct { - effect_magnitude: f32, - padding: [12]u8 = undefined, -}; - -const CRT = extern struct { - width: f32, - height: f32, - time: f32, - padding: [4]u8 = undefined, -}; - -const Actors = struct { - instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator}, - body_texture: ona.gfx.Texture = .default, - render_texture: ona.gfx.Texture = .default, - ca_effect: ona.gfx.Effect = .default, - crt_effect: ona.gfx.Effect = .default, - staging_texture: ona.gfx.Texture = .default, -}; - -const Player = struct { - move_x: ona.act.Axis = .{.keys = .{.a, .d}}, - move_y: ona.act.Axis = .{.keys = .{.w, .s}}, -}; - -pub const main = ona.start(setup, .{ - .tick_rate = 60, - .execution = .{.thread_share = 0.1}, -}); - -fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void { - config.res.width, config.res.height = .{1280, 720}; - actors.res.body_texture = try assets.res.load_texture_file(coral.files.bundle, "actor.bmp"); - - actors.res.render_texture = try assets.res.load_texture(.{ - .format = .rgba8, - - .access = .{ - .render = .{ - .width = config.res.width, - .height = config.res.height, - }, - }, - }); - - actors.res.ca_effect = try assets.res.load_effect_file(coral.files.bundle, "./ca.frag.spv"); - actors.res.crt_effect = try assets.res.load_effect_file(coral.files.bundle, "./crt.frag.spv"); - - try actors.res.instances.push_grow(.{0, 0}); -} - -fn exit(actors: ona.Write(Actors)) void { - actors.res.instances.deinit(); -} - -fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), app: ona.Read(ona.App)) !void { - try commands.set_target(.{ - .texture = actors.res.render_texture, - .clear_color = ona.gfx.colors.black, - .clear_depth = 0, - .clear_stencil = 0, - }); - - try commands.draw_texture(.{ - .texture = .default, - - .transform = .{ - .origin = .{1280 / 2, 720 / 2}, - .xbasis = .{1280, 0}, - .ybasis = .{0, 720}, - }, - }); - - try commands.set_effect(.{ - .effect = actors.res.crt_effect, - - .properties = std.mem.asBytes(&CRT{ - .width = 1280, - .height = 720, - .time = @floatCast(app.res.elapsed_time), - }), - }); - - try commands.set_target(.{ - .texture = .backbuffer, - .clear_color = null, - .clear_depth = null, - .clear_stencil = null, - }); - - try commands.draw_texture(.{ - .texture = actors.res.render_texture, - - .transform = .{ - .origin = .{1280 / 2, 720 / 2}, - .xbasis = .{1280, 0}, - .ybasis = .{0, 720}, - }, - }); -} - -fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void { - actors.res.instances.values[0] += .{ - mapping.res.axis_strength(player.res.move_x) * 10, - mapping.res.axis_strength(player.res.move_y) * 10, - }; -} - -fn setup(world: *ona.World, events: ona.App.Events) !void { - try world.set_state(Actors{}); - try world.set_state(Player{}); - - try world.on_event(events.load, ona.system_fn(load), .{.label = "load"}); - try world.on_event(events.update, ona.system_fn(update), .{.label = "update"}); - try world.on_event(events.exit, ona.system_fn(exit), .{.label = "exit"}); - try world.on_event(events.render, ona.system_fn(render), .{.label = "render actors"}); -} diff --git a/src/flow/system.zig b/src/ona/World.zig similarity index 62% rename from src/flow/system.zig rename to src/ona/World.zig index ba93cf8..208eb3b 100644 --- a/src/flow/system.zig +++ b/src/ona/World.zig @@ -1,21 +1,19 @@ -const coral = @import("coral"); +const builtin = @import("builtin"); -const dag = @import("./dag.zig"); - -const states = @import("./states.zig"); +const ona = @import("./ona.zig"); const std = @import("std"); -const World = @import("./World.zig"); +thread_pool: ?*std.Thread.Pool = null, +thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable, +event_systems: ona.stack.Sequential(Schedule), pub const BindContext = struct { - node: dag.Node, + node: ona.dag.Node, systems: *Schedule, - world: *World, + world: *Self, - pub const ResourceAccess = std.meta.Tag(Schedule.ResourceAccess); - - pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: states.TypeID) bool { + pub fn accesses_state(self: BindContext, access: std.meta.Tag(StateAccess), id: ona.TypeID) bool { const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses; for (resource_accesses.values) |resource_access| { @@ -42,23 +40,23 @@ pub const BindContext = struct { return null; }; - const id = states.type_id(Resource); + const id = ona.type_id(Resource); - if (!self.accesses_resource(.read_write, id)) { + if (!self.accesses_state(.read_write, id)) { try self.systems.graph.get_ptr(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 = coral.heap.allocator, + .allocator = ona.heap.allocator, })); break: insert self.systems.read_write_resource_id_nodes.get(id).?; }; }; - if (std.mem.indexOfScalar(dag.Node, read_write_resource_nodes.values, self.node) == null) { + if (std.mem.indexOfScalar(ona.dag.Node, read_write_resource_nodes.values, self.node) == null) { try read_write_resource_nodes.push_grow(self.node); } @@ -70,23 +68,23 @@ pub const BindContext = struct { return null; }; - const id = states.type_id(Resource); + const id = ona.type_id(Resource); - if (!self.accesses_resource(.read_only, id)) { + if (!self.accesses_state(.read_only, id)) { try self.systems.graph.get_ptr(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 = coral.heap.allocator, + .allocator = ona.heap.allocator, })); break: insert self.systems.read_only_resource_id_nodes.get(id).?; }; }; - if (std.mem.indexOfScalar(dag.Node, read_only_resource_nodes.values, self.node) == null) { + if (std.mem.indexOfScalar(ona.dag.Node, read_only_resource_nodes.values, self.node) == null) { try read_only_resource_nodes.push_grow(self.node); } @@ -94,70 +92,48 @@ pub const BindContext = struct { } }; -pub const Info = 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: states.ThreadRestriction = .none, +const Dependency = struct { + kind: Kind, + id: usize, - pub const Parameter = struct { - thread_restriction: states.ThreadRestriction, - init: *const fn (*anyopaque, *anyopaque) void, - bind: *const fn (std.mem.Allocator, BindContext) std.mem.Allocator.Error!*anyopaque, - unbind: *const fn (std.mem.Allocator, *anyopaque, UnbindContext) void, + const Kind = enum { + after, + before, }; - - pub fn used_parameters(self: *const Info) []const *const Parameter { - return self.parameters[0 .. self.parameter_count]; - } }; -pub const Order = struct { - label: []const u8 = "", - run_after: []const *const Info = &.{}, - run_before: []const *const Info = &.{}, +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), +}); + +pub const Event = enum (usize) { _ }; + +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, }; -pub const Schedule = struct { +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: coral.map.Hashed(usize, NodeBundle, coral.map.usize_traits), + system_id_nodes: ona.map.Hashed(usize, NodeBundle, ona.map.usize_traits), read_write_resource_id_nodes: ResourceNodeBundle, read_only_resource_id_nodes: ResourceNodeBundle, parallel_work_bundles: ParallelNodeBundles, blocking_work: NodeBundle, - const Dependency = struct { - kind: Kind, - id: usize, - - const Kind = enum { - after, - before, - }; - }; - - const Graph = dag.Graph(struct { - info: *const Info, - label: [:0]u8, - dependencies: []Dependency, - parameter_states: [max_parameters]*anyopaque = [_]*anyopaque{undefined} ** max_parameters, - resource_accesses: coral.stack.Sequential(ResourceAccess), - }); - - const NodeBundle = coral.stack.Sequential(dag.Node); - - const ParallelNodeBundles = coral.stack.Sequential(NodeBundle); - - const ResourceAccess = union (enum) { - read_only: states.TypeID, - read_write: states.TypeID, - }; - - const ResourceNodeBundle = coral.map.Hashed(states.TypeID, NodeBundle, coral.map.enum_traits(states.TypeID)); - - pub fn deinit(self: *Schedule, world: *World) void { + pub fn deinit(self: *Schedule, world: *Self) void { { var nodes = self.system_id_nodes.entries(); @@ -194,8 +170,8 @@ pub const Schedule = struct { } system.resource_accesses.deinit(); - coral.heap.allocator.free(system.dependencies); - coral.heap.allocator.free(system.label); + ona.heap.allocator.free(system.dependencies); + ona.heap.allocator.free(system.label); } for (self.parallel_work_bundles.values) |*bundle| { @@ -211,7 +187,7 @@ pub const Schedule = struct { self.arena.deinit(); } - pub fn run(self: *Schedule, world: *World) anyerror!void { + pub fn run(self: *Schedule, world: *Self) anyerror!void { if (self.is_invalidated()) { const work = struct { fn regenerate_graph(schedule: *Schedule) !void { @@ -276,7 +252,7 @@ pub const Schedule = struct { } } - fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: dag.Node) !void { + 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| { @@ -300,7 +276,7 @@ pub const Schedule = struct { continue; } - try schedule.parallel_work_bundles.push_grow(.{.allocator = coral.heap.allocator}); + try schedule.parallel_work_bundles.push_grow(.{.allocator = ona.heap.allocator}); const bundle = schedule.parallel_work_bundles.get_ptr().?; @@ -381,21 +357,21 @@ pub const Schedule = struct { } pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule { - var arena = std.heap.ArenaAllocator.init(coral.heap.allocator); + var arena = std.heap.ArenaAllocator.init(ona.heap.allocator); errdefer arena.deinit(); const duped_label = try arena.allocator().dupeZ(u8, label); return .{ - .graph = Graph.init(coral.heap.allocator), + .graph = Graph.init(ona.heap.allocator), .label = duped_label, .arena = arena, - .system_id_nodes = .{.allocator = coral.heap.allocator}, - .read_write_resource_id_nodes = .{.allocator = coral.heap.allocator}, - .read_only_resource_id_nodes = .{.allocator = coral.heap.allocator}, - .parallel_work_bundles = .{.allocator = coral.heap.allocator}, - .blocking_work = .{.allocator = coral.heap.allocator}, + .system_id_nodes = .{.allocator = ona.heap.allocator}, + .read_write_resource_id_nodes = .{.allocator = ona.heap.allocator}, + .read_only_resource_id_nodes = .{.allocator = ona.heap.allocator}, + .parallel_work_bundles = .{.allocator = ona.heap.allocator}, + .blocking_work = .{.allocator = ona.heap.allocator}, }; } @@ -413,7 +389,7 @@ pub const Schedule = struct { return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty(); } - pub fn then(self: *Schedule, world: *World, info: *const Info, order: Order) std.mem.Allocator.Error!void { + pub fn then(self: *Schedule, world: *Self, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void { const nodes = lazily_create: { const system_id = @intFromPtr(info); @@ -428,7 +404,7 @@ pub const Schedule = struct { const dependencies = init: { const total_run_orders = order.run_after.len + order.run_before.len; - const dependencies = try coral.heap.allocator.alloc(Dependency, total_run_orders); + const dependencies = try ona.heap.allocator.alloc(Dependency, total_run_orders); var dependencies_written = @as(usize, 0); for (order.run_after) |after_system| { @@ -452,17 +428,17 @@ pub const Schedule = struct { break: init dependencies; }; - errdefer coral.heap.allocator.free(dependencies); + errdefer ona.heap.allocator.free(dependencies); - const label = try coral.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label); + const label = try ona.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label); - errdefer coral.heap.allocator.free(label); + errdefer ona.heap.allocator.free(label); const node = try self.graph.append(.{ .info = info, .label = label, .dependencies = dependencies, - .resource_accesses = .{.allocator = coral.heap.allocator}, + .resource_accesses = .{.allocator = ona.heap.allocator}, }); const system = self.graph.get_ptr(node).?; @@ -491,8 +467,144 @@ pub const Schedule = struct { } }; -pub const UnbindContext = struct { - world: *World, +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 max_parameters = 16; +pub const UnbindContext = struct { + world: *Self, +}; + +pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event { + var systems = try Schedule.init(label); + + errdefer systems.deinit(self); + + const index = self.event_systems.len(); + + try self.event_systems.push_grow(systems); + + return @enumFromInt(index); +} + +pub fn deinit(self: *Self) void { + for (self.event_systems.values) |*schedule| { + schedule.deinit(self); + } + + 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_systems.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_systems = .{.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, event: Event, action: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void { + try self.event_systems.values[@intFromEnum(event)].then(self, action, order); +} + +pub fn run_event(self: *Self, event: Event) anyerror!void { + try self.event_systems.values[@intFromEnum(event)].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/act.zig b/src/ona/act.zig deleted file mode 100644 index 41573c3..0000000 --- a/src/ona/act.zig +++ /dev/null @@ -1,59 +0,0 @@ -const coral = @import("coral"); - -const flow = @import("flow"); - -const ona = @import("./ona.zig"); - -const std = @import("std"); - -pub const Axis = struct { - keys: ?[2]ona.gfx.Input.Key = null, -}; - -pub const Mapping = struct { - keys_pressed: ScancodeSet = ScancodeSet.initEmpty(), - keys_held: ScancodeSet = ScancodeSet.initEmpty(), - - const ScancodeSet = std.bit_set.StaticBitSet(512); - - pub fn axis_strength(self: Mapping, axis: Axis) f32 { - if (axis.keys) |keys| { - const key_down, const key_up = keys; - const is_key_down_held = self.keys_held.isSet(key_down.scancode()); - const is_key_up_held = self.keys_held.isSet(key_up.scancode()); - - if (is_key_down_held or is_key_up_held) { - return - @as(f32, @floatFromInt(@intFromBool(is_key_up_held))) - - @as(f32, @floatFromInt(@intFromBool(is_key_down_held))); - } - } - - return 0; - } -}; - -pub fn setup(world: *flow.World, events: ona.App.Events) std.mem.Allocator.Error!void { - try world.set_state(Mapping{}); - - try world.on_event(events.pre_update, flow.system_fn(update), .{ - .label = "update act", - }); -} - -pub fn update(inputs: ona.msg.Receive(ona.gfx.Input), mapping: flow.Write(Mapping)) void { - mapping.res.keys_pressed = Mapping.ScancodeSet.initEmpty(); - - for (inputs.messages()) |message| { - switch (message) { - .key_down => |key| { - mapping.res.keys_pressed.set(key.scancode()); - mapping.res.keys_held.set(key.scancode()); - }, - - .key_up => |key| { - mapping.res.keys_held.unset(key.scancode()); - }, - } - } -} diff --git a/src/coral/ascii.zig b/src/ona/ascii.zig similarity index 79% rename from src/coral/ascii.zig rename to src/ona/ascii.zig index 11830ee..dbf220c 100644 --- a/src/coral/ascii.zig +++ b/src/ona/ascii.zig @@ -1,19 +1,10 @@ -const coral = @import("./coral.zig"); - -const io = @import("./io.zig"); - -const scalars = @import("./scalars.zig"); +const ona = @import("./ona.zig"); const std = @import("std"); pub const DecimalFormat = struct { - delimiter: []const coral.io.Byte, - positive_prefix: enum {none, plus, space}, - - pub const default = DecimalFormat{ - .delimiter = "", - .positive_prefix = .none, - }; + 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) { @@ -35,14 +26,14 @@ pub const DecimalFormat = struct { switch (code) { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { - const offset_code = scalars.sub(code, '0') orelse return null; + const offset_code = ona.scalars.sub(code, '0') orelse return null; - result = scalars.mul(result, radix) orelse return null; - result = scalars.add(result, offset_code) 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 !coral.are_equal(self.delimiter, utf8[index ..])) { + if (self.delimiter.len == 0 or !std.mem.eql(u8, self.delimiter, utf8[index ..])) { return null; } }, @@ -115,9 +106,9 @@ pub const DecimalFormat = struct { } } - pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.Error!void { + pub fn format(self: DecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void { if (value == 0) { - return io.write_all(writer, switch (self.positive_prefix) { + return ona.io.write_all(writer, switch (self.positive_prefix) { .none => "0", .plus => "+0", .space => " 0", @@ -151,12 +142,12 @@ pub const DecimalFormat = struct { } } - return io.write_all(writer, buffer[buffer_start ..]); + return ona.io.write_all(writer, buffer[buffer_start ..]); }, .Float => |float| { if (value < 0) { - try io.write_all(writer, "-"); + try ona.io.write_all(writer, "-"); } const Float = @TypeOf(value); @@ -164,7 +155,7 @@ pub const DecimalFormat = struct { const integer = @as(Int, @intFromFloat(value)); try self.format(writer, integer); - try io.write_all(writer, "."); + try ona.io.write_all(writer, "."); try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100))); }, @@ -173,6 +164,12 @@ pub const DecimalFormat = struct { } }; +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, @@ -184,7 +181,7 @@ pub const HexadecimalFormat = struct { .casing = .lower, }; - pub fn format(self: HexadecimalFormat, writer: io.Writer, value: anytype) io.Error!void { + pub fn format(self: HexadecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void { // TODO: Implement. _ = self; _ = writer; diff --git a/src/coral/asyncio.zig b/src/ona/asyncio.zig similarity index 100% rename from src/coral/asyncio.zig rename to src/ona/asyncio.zig diff --git a/src/flow/dag.zig b/src/ona/dag.zig similarity index 96% rename from src/flow/dag.zig rename to src/ona/dag.zig index 931a49f..f1ed7f7 100644 --- a/src/flow/dag.zig +++ b/src/ona/dag.zig @@ -1,4 +1,4 @@ -const coral = @import("coral"); +const ona = @import("./ona.zig"); const std = @import("std"); @@ -7,9 +7,9 @@ pub fn Graph(comptime Payload: type) type { node_count: usize = 0, table: NodeTables, - const NodeTables = coral.stack.Parallel(struct { + const NodeTables = ona.stack.Parallel(struct { payload: Payload, - edges: coral.stack.Sequential(Node), + edges: ona.stack.Sequential(Node), is_occupied: bool = true, is_visited: bool = false, }); diff --git a/src/ona/ext.zig b/src/ona/ext.zig deleted file mode 100644 index 1be2ca3..0000000 --- a/src/ona/ext.zig +++ /dev/null @@ -1,4 +0,0 @@ - -pub usingnamespace @cImport({ - @cInclude("SDL2/SDL.h"); -}); diff --git a/src/coral/files.zig b/src/ona/files.zig similarity index 87% rename from src/coral/files.zig rename to src/ona/files.zig index 8756f3e..ba96484 100644 --- a/src/coral/files.zig +++ b/src/ona/files.zig @@ -28,14 +28,14 @@ pub const Storage = struct { pub const VTable = struct { stat: *const fn (*anyopaque, []const u8) AccessError!Stat, - read: *const fn (*anyopaque, []const u8, usize, []io.Byte) AccessError!usize, + read: *const fn (*anyopaque, []const u8, usize, []u8) AccessError!usize, }; - pub fn read(self: Storage, path: []const u8, output: []io.Byte, offset: u64) 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: []io.Byte, options: ReadAllOptions) ReadAllError![]const io.Byte { + 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) { @@ -45,8 +45,8 @@ pub const Storage = struct { 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)![]io.Byte { - const buffer = try allocator.alloc(io.Byte, @min((try self.stat(path)).size, options.limit)); + 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); @@ -69,7 +69,7 @@ pub const Storage = struct { } pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type { - var buffer = @as([@sizeOf(Type)]io.Byte, undefined); + var buffer = @as([@sizeOf(Type)]u8, undefined); if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) { return null; @@ -105,7 +105,7 @@ pub const bundle = init: { return buffer; } - fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) AccessError!usize { + 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, diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig deleted file mode 100644 index ab467b2..0000000 --- a/src/ona/gfx.zig +++ /dev/null @@ -1,534 +0,0 @@ -pub const colors = @import("./gfx/colors.zig"); - -const coral = @import("coral"); - -const ext = @import("./ext.zig"); - -const flow = @import("flow"); - -const ona = @import("./ona.zig"); - -const rendering = @import("./gfx/rendering.zig"); - -const std = @import("std"); - -pub const Assets = struct { - window: *ext.SDL_Window, - texture_formats: coral.stack.Sequential(TextureFormat), - 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 || coral.files.ReadAllError || error { - FormatUnsupported, - }; - - pub const TextureFormat = struct { - extension: []const u8, - load_file: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) LoadFileError!Texture.Desc, - }; - - pub const WorkQueue = coral.asyncio.BlockingQueue(1024, union (enum) { - load_effect: LoadEffectWork, - load_texture: LoadTextureWork, - render_frame: RenderFrameWork, - shutdown, - unload_effect: UnloadEffectWork, - unload_texture: UnloadTextureWork, - - const LoadEffectWork = struct { - desc: Effect.Desc, - loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Effect), - }; - - const LoadTextureWork = struct { - desc: Texture.Desc, - loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Texture), - }; - - const RenderFrameWork = struct { - clear_color: Color, - width: u16, - height: u16, - finished: *std.Thread.ResetEvent, - has_command_params: ?*flow.Params(Commands).Node, - }; - - const UnloadEffectWork = struct { - handle: Effect, - }; - - const UnloadTextureWork = struct { - handle: Texture, - }; - }); - - fn deinit(self: *Assets) void { - self.pending_work.enqueue(.shutdown); - - if (self.has_worker_thread) |worker_thread| { - worker_thread.join(); - } - - self.texture_formats.deinit(); - - self.* = undefined; - } - - fn init() !Assets { - const window = create: { - const position = ext.SDL_WINDOWPOS_CENTERED; - const flags = ext.SDL_WINDOW_OPENGL; - const width = 640; - const height = 480; - - break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse { - return error.Unsupported; - }; - }; - - errdefer { - ext.SDL_DestroyWindow(window); - } - - return .{ - .texture_formats = .{.allocator = coral.heap.allocator}, - .window = window, - }; - } - - pub fn load_effect_file(self: *Assets, storage: coral.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 coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32)); - - defer { - coral.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 = coral.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 = coral.asyncio.Future(std.mem.Allocator.Error!Texture){}; - - self.pending_work.enqueue(.{ - .load_texture = .{ - .desc = desc, - .loaded = &loaded, - }, - }); - - return loaded.get().*; - } - - pub fn load_texture_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Texture { - var arena = std.heap.ArenaAllocator.init(coral.heap.allocator); - - defer { - arena.deinit(); - } - - for (self.texture_formats.values) |format| { - if (!std.mem.endsWith(u8, path, format.extension)) { - continue; - } - - return self.load_texture(try format.load_file(&arena, storage, path)); - } - - return error.FormatUnsupported; - } - - pub const thread_restriction = .main; -}; - -pub const Color = @Vector(4, f32); - -pub const Commands = struct { - pending: *List, - - const Command = union (enum) { - draw_texture: DrawTextureCommand, - set_effect: SetEffectCommand, - set_target: SetTargetCommand, - }; - - pub const DrawTextureCommand = struct { - texture: Texture, - transform: Transform2D, - }; - - pub const SetEffectCommand = struct { - effect: Effect, - properties: []const coral.io.Byte, - }; - - pub const SetTargetCommand = struct { - texture: Texture, - clear_color: ?Color, - clear_depth: ?f32, - clear_stencil: ?u8, - }; - - pub const List = struct { - arena: std.heap.ArenaAllocator, - stack: coral.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; - } - }; - - pub fn bind(_: flow.system.BindContext) std.mem.Allocator.Error!Param { - return .{ - .swap_lists = .{ - List.init(coral.heap.allocator), - List.init(coral.heap.allocator), - }, - }; - } - - pub fn init(param: *Param) Commands { - return .{ - .pending = param.pending_list(), - }; - } - - pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void { - try self.pending.stack.push_grow(.{.draw_texture = command}); - } - - pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void { - try self.pending.stack.push_grow(.{ - .set_effect = .{ - .properties = try self.pending.arena.allocator().dupe(coral.io.Byte, command.properties), - .effect = command.effect, - }, - }); - } - - pub fn unbind(param: *Param, _: flow.system.UnbindContext) void { - param.deinit(); - } - - pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void { - try self.pending.stack.push_grow(.{.set_target = command}); - } -}; - -pub const Config = struct { - width: u16 = 1280, - height: u16 = 720, - clear_color: Color = colors.black, -}; - -pub const Input = union (enum) { - key_up: Key, - key_down: Key, - - pub const Key = enum (u32) { - a = ext.SDL_SCANCODE_A, - d = ext.SDL_SCANCODE_D, - s = ext.SDL_SCANCODE_S, - w = ext.SDL_SCANCODE_W, - - pub fn scancode(key: Key) u32 { - return @intFromEnum(key); - } - }; -}; - -pub const Effect = enum (u32) { - default, - _, - - pub const Desc = struct { - fragment_spirv_ops: []const u32, - }; -}; - -pub const Texture = enum (u32) { - default, - backbuffer, - _, - - 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 coral.io.Byte, - }; - - 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 Transform2D = extern struct { - xbasis: Vector = .{1, 0}, - ybasis: Vector = .{0, 1}, - origin: Vector = @splat(0), - - const Vector = @Vector(2, f32); -}; - -fn load_bmp_texture(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !Texture.Desc { - const header = try storage.read_little(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 arena.allocator().alloc(coral.io.Byte, header.image_size); - const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte; - 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 = coral.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 storage.read(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 poll(app: flow.Write(ona.App), inputs: ona.msg.Send(Input)) !void { - var event = @as(ext.SDL_Event, undefined); - - while (ext.SDL_PollEvent(&event) != 0) { - switch (event.type) { - ext.SDL_QUIT => app.res.quit(), - ext.SDL_KEYUP => try inputs.push(.{.key_up = @enumFromInt(event.key.keysym.scancode)}), - ext.SDL_KEYDOWN => try inputs.push(.{.key_down = @enumFromInt(event.key.keysym.scancode)}), - else => {}, - } - } -} - -pub fn setup(world: *flow.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { - if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) { - return error.Unsupported; - } - - const assets = create: { - var assets = try Assets.init(); - - 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, - }); - - const builtin_texture_formats = [_]Assets.TextureFormat{ - .{ - .extension = "bmp", - .load_file = load_bmp_texture, - }, - }; - - for (builtin_texture_formats) |format| { - try assets.texture_formats.push_grow(format); - } - - try world.set_state(Config{}); - try world.on_event(events.pre_update, flow.system_fn(poll), .{.label = "poll gfx"}); - try world.on_event(events.exit, flow.system_fn(stop), .{.label = "stop gfx"}); - try world.on_event(events.finish, flow.system_fn(synchronize), .{.label = "synchronize gfx"}); -} - -pub fn stop(assets: flow.Write(Assets)) void { - assets.res.deinit(); -} - -pub fn synchronize(exclusive: flow.Exclusive) !void { - const assets = exclusive.world.get_state(Assets).?; - const config = exclusive.world.get_state(Config).?; - - 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 (config.width != display_width or config.height != display_height) { - ext.SDL_SetWindowSize(assets.window, config.width, config.height); - } - - if (exclusive.world.get_params(Commands).has_head) |command_param| { - assets.pending_work.enqueue(.{ - .render_frame = .{ - .has_command_params = command_param, - .width = config.width, - .height = config.height, - .clear_color = config.clear_color, - .finished = &assets.frame_rendered, - }, - }); - } else { - assets.frame_rendered.set(); - } -} diff --git a/src/ona/gfx/colors.zig b/src/ona/gfx/colors.zig deleted file mode 100644 index e7ee635..0000000 --- a/src/ona/gfx/colors.zig +++ /dev/null @@ -1,13 +0,0 @@ -const gfx = @import("../gfx.zig"); - -pub const black = greyscale(0); - -pub const white = greyscale(1); - -pub fn greyscale(v: f32) gfx.Color { - return .{v, v, v, 1}; -} - -pub fn rgb(r: f32, g: f32, b: f32) gfx.Color { - return .{r, g, b, 1}; -} diff --git a/src/coral/hashes.zig b/src/ona/hashes.zig similarity index 100% rename from src/coral/hashes.zig rename to src/ona/hashes.zig diff --git a/src/coral/heap.zig b/src/ona/heap.zig similarity index 100% rename from src/coral/heap.zig rename to src/ona/heap.zig diff --git a/src/coral/io.zig b/src/ona/io.zig similarity index 88% rename from src/coral/io.zig rename to src/ona/io.zig index e220a55..2ba3154 100644 --- a/src/coral/io.zig +++ b/src/ona/io.zig @@ -1,13 +1,9 @@ const builtin = @import("builtin"); -const coral = @import("./coral.zig"); - -const slices = @import("./slices.zig"); +const ona = @import("./ona.zig"); const std = @import("std"); -pub const Byte = u8; - pub const Error = error { UnavailableResource, }; @@ -111,7 +107,7 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type pub const NullWritable = struct { written: usize = 0, - pub fn write(self: *NullWritable, buffer: []const Byte) Error!usize { + pub fn write(self: *NullWritable, buffer: []const u8) Error!usize { self.written += buffer.len; return buffer.len; @@ -122,12 +118,12 @@ pub const NullWritable = struct { } }; -pub const Reader = Generator(Error!usize, &.{[]Byte}); +pub const Reader = Generator(Error!usize, &.{[]u8}); -pub const Writer = Generator(Error!usize, &.{[]const Byte}); +pub const Writer = Generator(Error!usize, &.{[]const u8}); -pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte { - const buffer = coral.Stack(coral.Byte){.allocator = allocator}; +pub fn alloc_read(input: ona.io.Reader, allocator: std.mem.Allocator) []u8 { + const buffer = ona.stack.Sequential(u8){.allocator = allocator}; errdefer buffer.deinit(); @@ -139,7 +135,7 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral. pub const bits_per_byte = 8; pub fn skip_n(input: Reader, distance: u64) Error!void { - var buffer = @as([512]coral.Byte, undefined); + var buffer = @as([512]u8, undefined); var remaining = distance; while (remaining != 0) { @@ -154,7 +150,7 @@ pub fn skip_n(input: Reader, distance: u64) Error!void { } pub fn stream_all(input: Reader, output: Writer) Error!usize { - var buffer = @as([512]coral.Byte, undefined); + var buffer = @as([512]u8, undefined); var copied = @as(usize, 0); while (true) { @@ -173,7 +169,7 @@ pub fn stream_all(input: Reader, output: Writer) Error!usize { } pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize { - var buffer = @as([512]coral.Byte, undefined); + var buffer = @as([512]u8, undefined); var remaining = limit; while (true) { diff --git a/src/coral/lina.zig b/src/ona/lina.zig similarity index 66% rename from src/coral/lina.zig rename to src/ona/lina.zig index 0748791..f9dfc87 100644 --- a/src/coral/lina.zig +++ b/src/ona/lina.zig @@ -1,18 +1,5 @@ const std = @import("std"); -pub fn Matrix(comptime n: usize, comptime Element: type) type { - return [n]@Vector(n, Element); -} - -pub const ProjectionMatrix = Matrix(4, f32); - -pub const Rect = struct { - left: f32, - top: f32, - right: f32, - bottom: f32, -}; - pub fn cross(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child { const multipled = v1 * v2; const vector_info = @typeInfo(@TypeOf(v1)).Vector; @@ -62,15 +49,3 @@ pub fn normal(v: anytype) @TypeOf(v) { return v; } -pub fn orthographic_projection(near: f32, far: f32, viewport: Rect) Matrix(4, f32) { - 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}, - }; -} - diff --git a/src/coral/map.zig b/src/ona/map.zig similarity index 96% rename from src/coral/map.zig rename to src/ona/map.zig index eb7de4d..9193d4f 100644 --- a/src/coral/map.zig +++ b/src/ona/map.zig @@ -1,8 +1,4 @@ -const coral = @import("./coral.zig"); - -const hashes = @import("./hashes.zig"); - -const io = @import("./io.zig"); +const ona = @import("./ona.zig"); const std = @import("std"); @@ -250,13 +246,17 @@ pub fn enum_traits(comptime Enum: type) Traits(Enum) { 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 hashes.djb2(@typeInfo(usize).Int, value); + return ona.hashes.djb2(@typeInfo(usize).Int, value); } }; break: init Traits([]const u8){ - .are_equal = coral.io.are_equal, + .are_equal = strings.are_equal, .hash = strings.hash, }; }; diff --git a/src/ona/msg.zig b/src/ona/msg.zig deleted file mode 100644 index 00f3e20..0000000 --- a/src/ona/msg.zig +++ /dev/null @@ -1,134 +0,0 @@ -const coral = @import("coral"); - -const flow = @import("flow"); - -const ona = @import("./ona.zig"); - -const std = @import("std"); - -fn Channel(comptime Message: type) type { - return struct { - buffers: [2]coral.stack.Sequential(Message), - swap_index: u1 = 0, - ticks: u1 = 0, - - const Self = @This(); - - fn cleanup(channel: flow.Write(Self)) void { - channel.res.deinit(); - } - - pub fn deinit(self: *Self) void { - for (&self.buffers) |*buffer| { - buffer.deinit(); - } - - self.* = undefined; - } - - fn swap(channel: flow.Write(Self)) void { - channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0; - - if (channel.res.ticks == 0) { - channel.res.swap_index ^= 1; - - channel.res.buffers[channel.res.swap_index].clear(); - } - } - - 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); - } - }; -} - -pub fn Receive(comptime Message: type) type { - const TypedChannel = Channel(Message); - - return struct { - channel: *const TypedChannel, - - const Self = @This(); - - pub const Param = struct { - channel: *const TypedChannel, - }; - - pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param { - return .{ - .channel = (try context.register_readable_state_access(TypedChannel)) orelse set: { - try context.world.set_state(TypedChannel.init(coral.heap.allocator)); - - 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 Send(comptime Message: type) type { - const TypedChannel = Channel(Message); - - return struct { - channel: *TypedChannel, - - const Self = @This(); - - pub const Param = struct { - channel: *TypedChannel, - }; - - pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param { - return .{ - .channel = (try context.register_writable_state_access(TypedChannel)) orelse set: { - try context.world.set_state(TypedChannel.init(coral.heap.allocator)); - - const app = context.world.get_state(ona.App).?; - - try context.world.on_event(app.events.post_update, flow.system_fn(TypedChannel.swap), .{ - .label = "swap channel of " ++ @typeName(Message), - }); - - try context.world.on_event(app.events.exit, flow.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); - } - }; -} diff --git a/src/ona/ona.zig b/src/ona/ona.zig index 63ab56d..2dab086 100644 --- a/src/ona/ona.zig +++ b/src/ona/ona.zig @@ -1,17 +1,33 @@ -pub const act = @import("./act.zig"); +pub const ascii = @import("./ascii.zig"); -const coral = @import("coral"); +pub const asyncio = @import("./asyncio.zig"); -const ext = @import("./ext.zig"); +pub const dag = @import("./dag.zig"); -const flow = @import("flow"); +pub const hashes = @import("./hashes.zig"); -pub const gfx = @import("./gfx.zig"); +pub const heap = @import("./heap.zig"); -pub const msg = @import("./msg.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"); 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 { events: *const Events, target_frame_time: f64, @@ -19,13 +35,13 @@ pub const App = struct { is_running: bool, pub const Events = struct { - load: flow.World.Event, - pre_update: flow.World.Event, - update: flow.World.Event, - post_update: flow.World.Event, - render: flow.World.Event, - finish: flow.World.Event, - exit: flow.World.Event, + load: World.Event, + pre_update: World.Event, + update: World.Event, + post_update: World.Event, + render: World.Event, + finish: World.Event, + exit: World.Event, }; pub fn quit(self: *App) void { @@ -33,18 +49,151 @@ pub const App = struct { } }; -pub const Flag = enum { +fn Channel(comptime Message: type) type { + return struct { + buffers: [2]stack.Sequential(Message), + swap_index: u1 = 0, + ticks: u1 = 0, + + const Self = @This(); + + fn cleanup(channel: Write(Self)) void { + channel.state.deinit(); + } + + pub fn deinit(self: *Self) void { + for (&self.buffers) |*buffer| { + buffer.deinit(); + } + + self.* = undefined; + } + + fn swap(channel: Write(Self)) void { + channel.state.ticks = scalars.add(channel.state.ticks, 1) orelse 0; + + if (channel.state.ticks == 0) { + channel.state.swap_index ^= 1; + + channel.state.buffers[channel.state.swap_index].clear(); + } + } + + 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); + } + }; +} + +pub fn Exclusive(comptime values: []const type) type { + comptime var qualifieds: [values.len]type = undefined; + + 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, + }, + }, + }, + }), + + 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 yet been set", .{ + @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 launch_args = [_]?[]const u8{null} ** std.enums.values(Flag).len; + var args = [_]?[]const u8{null} ** std.enums.values(LaunchFlag).len; - const values = std.enums.values(Flag); + const values = std.enums.values(LaunchFlag); }; pub const Options = struct { tick_rate: u64, execution: Execution, - middlewares: []const *const Setup = default_middlewares, + middleware: []const *const Setup, pub const Execution = union (enum) { single_threaded, @@ -52,29 +201,498 @@ pub const Options = struct { }; }; -pub const Read = flow.Read; +pub fn Params(comptime Value: type) type { + if (!@hasDecl(Value, "Param")) { + @compileError("System parameters must have a Params type declaration"); + } -pub const Setup = fn (*flow.World, App.Events) anyerror!void; + return struct { + has_head: ?*Node = null, + has_tail: ?*Node = null, -pub const World = flow.World; - -pub const Write = flow.Write; - -pub const default_middlewares = &.{ - gfx.setup, - act.setup, -}; - -pub fn launch_arg(flag: Flag) ?[]const u8 { - return Flag.launch_args[@intFromEnum(flag)]; + pub const Node = struct { + param: Value.Param, + has_prev: ?*Node = null, + has_next: ?*Node = null, + }; + }; } -pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void { +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 Receive(comptime Message: type) type { + const TypedChannel = Channel(Message); + + return struct { + channel: *const TypedChannel, + + const Self = @This(); + + pub const Param = struct { + channel: *const TypedChannel, + }; + + 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)); + + 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 Send(comptime Message: type) type { + const TypedChannel = Channel(Message); + + return struct { + channel: *TypedChannel, + + const Self = @This(); + + pub const Param = struct { + channel: *TypedChannel, + }; + + 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)); + + const app = context.world.get_state(App) orelse { + @panic("Send system parameters depend on a " ++ @typeName(App) ++ " state to work"); + }; + + try context.world.on_event(app.events.post_update, system_fn(TypedChannel.swap), .{ + .label = "swap channel of " ++ @typeName(Message), + }); + + try context.world.on_event(app.events.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 const Setup = fn (*World, App.Events) anyerror!void; + +pub const ShareInfo = struct { + thread_restriction: ThreadRestriction, + read_only: bool, +}; + +pub fn Shared(comptime Value: type, comptime info: ShareInfo) type { + const value_info = @typeInfo(Value); + + 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, + }, + }, + }, + }), + + 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 yet been set", .{ + 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 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 start(comptime setup: Setup, comptime options: Options) fn () anyerror!void { const Start = struct { fn main() anyerror!void { defer { - coral.heap.trace_leaks(); - ext.SDL_Quit(); + heap.trace_leaks(); } parse_args: for (std.os.argv[1 ..]) |arg| { @@ -82,14 +700,14 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void { const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len; const arg_name = arg_span[0 .. arg_split_index]; - for (Flag.values) |value| { + for (LaunchFlag.values) |value| { const name = @tagName(value); if (!std.mem.eql(u8, arg_name, name)) { continue; } - Flag.launch_args[@intFromEnum(value)] = + LaunchFlag.args[@intFromEnum(value)] = if (arg_split_index == arg_span.len) name else @@ -100,7 +718,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void { } var world = try switch (options.execution) { - .single_threaded => flow.World.init(0), + .single_threaded => World.init(0), .thread_share => |thread_share| init: { const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| { @@ -111,7 +729,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void { }); }, 0, std.math.maxInt(u32)))); - break: init flow.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0); + break: init World.init(scalars.fractional(cpu_count, thread_share) orelse 0); }, }; @@ -134,7 +752,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void { .is_running = true, }); - for (options.middlewares) |setup_middleware| { + inline for (options.middleware) |setup_middleware| { try setup_middleware(&world, events); } @@ -172,4 +790,81 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void { return Start.main; } -pub const system_fn = flow.system_fn; +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 type_id(comptime T: type) TypeID { + const TypeHandle = struct { + comptime { + _ = T; + } + + var byte: u8 = 0; + }; + + return @enumFromInt(@intFromPtr(&TypeHandle.byte)); +} + +pub fn thread_restriction(comptime State: type) ThreadRestriction { + if (@hasDecl(State, "thread_restriction")) { + return State.thread_restriction; + } + + return .none; +} diff --git a/src/coral/scalars.zig b/src/ona/scalars.zig similarity index 100% rename from src/coral/scalars.zig rename to src/ona/scalars.zig diff --git a/src/coral/slices.zig b/src/ona/slices.zig similarity index 94% rename from src/coral/slices.zig rename to src/ona/slices.zig index 2a4f80a..f21adee 100644 --- a/src/coral/slices.zig +++ b/src/ona/slices.zig @@ -1,5 +1,3 @@ -const io = @import("./io.zig"); - const std = @import("std"); pub fn ElementPtr(comptime Slice: type) type { @@ -25,7 +23,7 @@ pub fn Parallel(comptime Type: type) type { return struct { len: usize = 0, - ptrs: [fields.len][*]align (alignment) io.Byte = undefined, + ptrs: [fields.len][*]align (alignment) u8 = undefined, pub fn Element(comptime field: Field) type { return fields[@intFromEnum(field)].type; @@ -118,7 +116,7 @@ pub fn get_ptr(slice: anytype, index: usize) ?ElementPtr(@TypeOf(slice)) { 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) io.Byte, undefined); + var buffers = @as([std.enums.values(Slices.Field).len][]align (alignment) u8, undefined); var buffers_allocated = @as(usize, 0); var allocated = Slices{.len = n}; @@ -131,7 +129,7 @@ pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: u const fields = @typeInfo(Element).Struct.fields; inline for (0 .. fields.len) |i| { - buffers[i] = try allocator.alignedAlloc(io.Byte, alignment, @sizeOf(fields[i].type) * n); + buffers[i] = try allocator.alignedAlloc(u8, alignment, @sizeOf(fields[i].type) * n); buffers_allocated += 1; allocated.ptrs[i] = buffers[i].ptr; } diff --git a/src/coral/stack.zig b/src/ona/stack.zig similarity index 87% rename from src/coral/stack.zig rename to src/ona/stack.zig index cb70bd9..45237e0 100644 --- a/src/coral/stack.zig +++ b/src/ona/stack.zig @@ -1,14 +1,10 @@ -const io = @import("./io.zig"); - -const scalars = @import("./scalars.zig"); - -const slices = @import("./slices.zig"); +const ona = @import("./ona.zig"); const std = @import("std"); pub fn Sequential(comptime Value: type) type { return struct { - allocator: std.mem.Allocator, + allocator: std.mem.Allocator = ona.heap.allocator, values: []Value = &.{}, cap: usize = 0, @@ -82,7 +78,7 @@ pub fn Sequential(comptime Value: type) type { } pub fn pop_many(self: *Self, n: usize) bool { - const new_length = scalars.sub(self.values.len, n) orelse { + const new_length = ona.scalars.sub(self.values.len, n) orelse { return false; }; @@ -190,12 +186,12 @@ pub fn Sequential(comptime Value: type) type { } pub const writer = switch (Value) { - io.Byte => struct { - fn writer(self: *Self) io.Writer { - return io.Writer.bind(Self, self, write); + u8 => struct { + fn writer(self: *Self) ona.io.Writer { + return ona.io.Writer.bind(Self, self, write); } - fn write(self: *Self, buffer: []const io.Byte) io.Error!usize { + fn write(self: *Self, buffer: []const u8) ona.io.Error!usize { self.push_all(buffer) catch return error.UnavailableResource; return buffer.len; @@ -208,7 +204,7 @@ pub fn Sequential(comptime Value: type) type { } pub fn Parallel(comptime Value: type) type { - const Slices = slices.Parallel(Value); + const Slices = ona.slices.Parallel(Value); const alignment = @alignOf(Value); return struct { @@ -229,7 +225,7 @@ pub fn Parallel(comptime Value: type) type { capacity_slice.len = self.cap; - slices.parallel_free(Value, self.allocator, capacity_slice); + ona.slices.parallel_free(Value, self.allocator, capacity_slice); self.* = undefined; } @@ -244,11 +240,11 @@ pub fn Parallel(comptime Value: type) type { pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void { const grown_capacity = self.cap + additional; - const buffer = try slices.parallel_alloc(Value, self.allocator, grown_capacity); + const buffer = try ona.slices.parallel_alloc(Value, self.allocator, grown_capacity); if (self.cap != 0) { - slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values); - slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?); + ona.slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values); + ona.slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?); } self.cap = grown_capacity; diff --git a/src/coral/utf8.zig b/src/ona/utf8.zig similarity index 66% rename from src/coral/utf8.zig rename to src/ona/utf8.zig index 44000e7..8326e89 100644 --- a/src/coral/utf8.zig +++ b/src/ona/utf8.zig @@ -1,8 +1,4 @@ -const ascii = @import("./ascii.zig"); - -const coral = @import("./coral.zig"); - -const io = @import("./io.zig"); +const ona = @import("./ona.zig"); const std = @import("std"); @@ -12,26 +8,26 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8 errdefer allocator.free(buffer); - // TODO: This is dumb. + // 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 = io.NullWritable{}; + var count = ona.io.NullWritable{}; write_formatted(count.writer(), format, args) catch unreachable; return count.written; } -pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]const u8 { +pub fn print_formatted(buffer: [:0]u8, comptime format: []const u8, args: anytype) ona.io.Error![:0]const u8 { const Seekable = struct { - buffer: []coral.io.Byte, + buffer: []u8, cursor: usize, const Self = @This(); - fn write(self: *Self, input: []const coral.io.Byte) io.Error!usize { + 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; @@ -54,7 +50,7 @@ pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, a .cursor = 0, }; - try write_formatted(coral.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args); + try write_formatted(ona.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args); if (buffer.len < len) { buffer[len] = 0; @@ -63,7 +59,7 @@ pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, a return buffer[0 .. len:0]; } -pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.Error!void { +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; @@ -80,7 +76,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any switch (format[tail]) { '{' => { - try io.print(writer, format[head .. (tail - 1)]); + try ona.io.print(writer, format[head .. (tail - 1)]); tail += 1; head = tail; @@ -91,7 +87,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any @compileError("all format specifiers must be named when using a named struct"); } - try io.print(writer, args[arg_index]); + try ona.io.print(writer, args[arg_index]); arg_index += 1; tail += 1; @@ -103,7 +99,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any @compileError("format specifiers cannot be named when using a tuple struct"); } - try io.write_all(writer, format[head .. (tail - 1)]); + try ona.io.write_all(writer, format[head .. (tail - 1)]); head = tail; tail += 1; @@ -129,25 +125,27 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any } } - try io.write_all(writer, format[head .. ]); + try ona.io.write_all(writer, format[head .. ]); }, else => @compileError("`arguments` must be a struct type"), } } -noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void { +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 => ascii.DecimalFormat.default.format(writer, value), - .Float => ascii.DecimalFormat.default.format(writer, value), - .Enum => io.print(writer, @tagName(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 => ascii.HexadecimalFormat.default.format(writer, @intFromPtr(value)), - .One => if (pointer.child == []const u8) io.write_all(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), - .Slice => if (pointer.child == u8) io.write_all(writer, value) else @compileError(unformattableMessage(Value)), + .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)),