diff --git a/.drone.yml b/.drone.yml index 4a37890..0bbe9c2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -1,8 +1,25 @@ kind: pipeline +type: docker name: continuous integration +environment: + ZIG_VERSION: 0.13.0 + steps: -- name: build & test - image: chainguard/zig:latest - commands: - - zig build test + - name: submodules + image: ubuntu:latest + commands: + - apt-get update + - apt-get install -y git git-lfs + - git submodule update --init --recursive + + - name: build & test + image: ubuntu:latest + commands: + - apt-get update + - apt-get install -y openssl xz-utils wget libsdl2-dev glslang-tools spirv-tools + - wget https://ziglang.org/download/$${ZIG_VERSION}/zig-linux-x86_64-$${ZIG_VERSION}.tar.xz + - tar -xf zig-linux-x86_64-$${ZIG_VERSION}.tar.xz + - export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION} + - zig build + - zig build test diff --git a/.gitattributes b/.gitattributes index 663ee24..95c1536 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1 @@ -tools/sokol-shdc filter=lfs diff=lfs merge=lfs -text -tools/sokol-shdc.exe filter=lfs diff=lfs merge=lfs -text *.bmp filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore index 705556a..f190a33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ # Generated assets -/.zig-cache +.zig-cache /zig-out -*.glsl.zig +*.spv diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0e455b8 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "ext/spirv-cross"] + path = ext/spirv-cross + url = https://github.com/KhronosGroup/SPIRV-Cross/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 89d52ec..e0928e0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,7 +7,7 @@ "request": "launch", "target": "${workspaceRoot}/zig-out/bin/main", "cwd": "${workspaceRoot}/debug/", - "valuesFormatting": "parseText", + "valuesFormatting": "prettyPrinters", "preLaunchTask": "Build All" }, ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 682ea0f..995d05f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,17 @@ "editor.detectIndentation": false, "editor.insertSpaces": false, "editor.rulers": [120], - } + }, + + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/CVS": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "**/.zig-cache": true, + "zig-out": true, + ".drone.yml": true, + }, } diff --git a/build.zig b/build.zig index 234db97..c702a40 100644 --- a/build.zig +++ b/build.zig @@ -2,136 +2,223 @@ const builtin = @import("builtin"); const std = @import("std"); +const SubPath = struct { + buffer: [max]u8 = [_]u8{0} ** max, + unused: u8 = max, + + const max = 255; + + pub fn utf8(self: *const SubPath) [:0]const u8 { + return self.buffer[0 .. (max - self.unused):0]; + } +}; + pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const sokol_dependency = b.dependency("sokol", .{ + .target = target, + .optimize = optimize, + }); + const coral_module = b.createModule(.{ .root_source_file = b.path("src/coral/coral.zig"), }); - const ona_module = add: { - const sokol_dependency = b.dependency("sokol", .{ + 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"), + + .imports = &.{ + .{ + .name = "sokol", + .module = sokol_dependency.module("sokol"), + }, + + .{ + .name = "coral", + .module = coral_module, + }, + + .{ + .name = "flow", + .module = flow_module, + }, + }, + }); + + ona_module.addIncludePath(b.path("ext/")); + + ona_module.linkLibrary(spirv_cross: { + const dir = "ext/spirv-cross/"; + + const sources = [_][]const u8{ + "spirv_cross.cpp", + "spirv_parser.cpp", + "spirv_cross_parsed_ir.cpp", + "spirv_cfg.cpp", + "spirv_glsl.cpp", + "spirv_msl.cpp", + "spirv_hlsl.cpp", + "spirv_reflect.cpp", + "spirv_cross_util.cpp", + "spirv_cross_c.cpp", + }; + + const lib = b.addStaticLibrary(.{ + .name = "spirvcross", .target = target, .optimize = optimize, }); - const module = b.addModule("ona", .{ - .root_source_file = b.path("src/ona/ona.zig"), + switch (lib.rootModuleTarget().abi) { + .msvc => lib.linkLibC(), + else => lib.linkLibCpp(), + } - .imports = &.{ - .{ - .name = "sokol", - .module = sokol_dependency.module("sokol"), - }, + 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"}, + }); + } - .{ - .name = "coral", - .module = coral_module, - }, - }, - }); + break: spirv_cross lib; + }); - break: add module; - }; - - b.step("test", "Run unit tests").dependOn(create: { + 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: create &tests.step; + break: tests &tests.step; }); b.installArtifact(add: { - const compile_step = b.addExecutable(.{ + const exe = b.addExecutable(.{ .name = "main", .root_source_file = b.path("src/main.zig"), .target = target, .optimize = optimize, }); - compile_step.root_module.addImport("ona", ona_module); - compile_step.root_module.addImport("coral", coral_module); - compile_step.linkLibC(); - compile_step.linkSystemLibrary("SDL2"); + exe.root_module.addImport("ona", ona_module); + exe.root_module.addImport("coral", coral_module); + exe.linkLibC(); + exe.linkSystemLibrary("SDL2"); - try depend_on_shaders(b, target, "src/ona/gfx/shaders/", &compile_step.step); + const shaders_sub_path = "src/ona/gfx/shaders/"; - break: add compile_step; - }); -} + var shaders_dir = try std.fs.cwd().openDir(shaders_sub_path, .{ + .iterate = true, + }); -fn depend_on_shaders( - b: *std.Build, - target: std.Build.ResolvedTarget, - shader_dir_path: []const u8, - step: *std.Build.Step, -) !void { - var dir = try std.fs.cwd().openDir(shader_dir_path, .{ .iterate = true }); + defer shaders_dir.close(); - defer dir.close(); + var shaders_walker = try shaders_dir.walk(b.allocator); - var walker = try dir.walk(b.allocator); + defer shaders_walker.deinit(); - defer walker.deinit(); - - const shdc_path = switch (builtin.os.tag) { - .windows => "./tools/sokol-shdc.exe", - .linux => "./tools/sokol-shdc", - else => @compileError("cannot compile sokol shaders on this platform"), - }; - - const path_buffer_max = 255; - var input_path_buffer = [_]u8{undefined} ** path_buffer_max; - var output_path_buffer = [_]u8{undefined} ** path_buffer_max; - - const glsl = if (target.result.isDarwin()) "glsl410" else "glsl430"; - const slang = glsl ++ ":metal_macos:hlsl5:glsl300es:wgsl"; - - while (try walker.next()) |entry| { - if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".glsl")) { - continue; - } - - const input_path = try std.fmt.bufPrint(&input_path_buffer, "{s}{s}", .{shader_dir_path, entry.path}); - const output_path = try std.fmt.bufPrint(&output_path_buffer, "{s}.zig", .{input_path}); - const output = std.fs.path.basename(output_path); - - dir.access(output, .{.mode = .read_only}) catch { - const cmd = b.addSystemCommand(&.{ - shdc_path, - "-i", - input_path, - "-o", - output_path, - "-l", - slang, - "-f", - "sokol_zig", - }); - - step.dependOn(&cmd.step); - - continue; + const Shader = struct { + source_sub_path: SubPath = .{}, + binary_sub_path: SubPath = .{}, }; - if ((try dir.statFile(entry.path)).mtime > (try dir.statFile(output)).mtime) { - const cmd = b.addSystemCommand(&.{ - shdc_path, - "-i", - input_path, - "-o", - output_path, - "-l", - slang, - "-f", - "sokol_zig", + 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; + } + + const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert"); + + if (!is_shader_file) { + continue: scan_shaders; + } + + 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; + } + } + + 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; + } + } + + 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; }); - step.dependOn(&cmd.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; + }); } diff --git a/build.zig.zon b/build.zig.zon index 3fcc20d..a155592 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -1,17 +1,17 @@ .{ - .name = "Ona", - .version = "0.0.1", - .paths = .{ - "src", - "build.zig", - "build.zig.zon", + .name = "Ona", + .version = "0.0.1", + .paths = .{ + "src", + "build.zig", + "build.zig.zon", "LICENSE", "README.md", - }, - .dependencies = .{ - .sokol = .{ - .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27", + }, + .dependencies = .{ + .sokol = .{ + .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27", .hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569", - }, - }, + }, + }, } diff --git a/debug/ca.frag b/debug/ca.frag new file mode 100644 index 0000000..74e9519 --- /dev/null +++ b/debug/ca.frag @@ -0,0 +1,25 @@ +#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/debug/crt.frag new file mode 100644 index 0000000..682f44e --- /dev/null +++ b/debug/crt.frag @@ -0,0 +1,52 @@ +#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 screen_width; + float screen_height; + float time; +}; + +vec3 scanline(vec2 coord, vec3 screen) +{ + screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02; + return screen; +} + +vec2 crt(vec2 coord, float bend) +{ + // put in symmetrical coords + coord = (coord - 0.5) * 2.0; + + coord *= 1.0; + + // deform coords + coord.x *= 1.0 + pow((abs(coord.y) / bend), 2.0); + coord.y *= 1.0 + pow((abs(coord.x) / bend), 2.0); + + // transform back to 0.0 - 1.0 space + coord = (coord / 2.0) + 0.5; + + return coord; +} + +void main() +{ + vec2 crtCoords = crt(uv, 4.8); + + // Split the color channels + texel.rgb = texture(sprite, crtCoords).rgb; + texel.a = 1; + + // 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/debug/test.bmp b/debug/test.bmp deleted file mode 100644 index 2d7f6e7..0000000 --- a/debug/test.bmp +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:e31f120e1c23ea9c949441bca87e504c1e8f2e90b6dff9bd1cd559ddfd4dd225 -size 16522 diff --git a/ext/spirv-cross b/ext/spirv-cross new file mode 160000 index 0000000..6fd1f75 --- /dev/null +++ b/ext/spirv-cross @@ -0,0 +1 @@ +Subproject commit 6fd1f75636b1c424b809ad8a84804654cf5ae48b diff --git a/readme.md b/readme.md index 7e8848a..bf956f6 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,7 @@ Ona is also the Catalan word for "wave". Ona currently depends the following third-party tools to build it: * Platform support for SDL2 at version 2.0.20 or above. + * SPIR-V shader compilation system utilities, namely `glslangValidator`. * Zig compiler toolchain. As the project evolves, dependencies on libraries external to the project codebase will be minimized or removed outright to meet the goals of the project as closely as possible. diff --git a/src/coral/ascii.zig b/src/coral/ascii.zig index 2d4e26a..11830ee 100644 --- a/src/coral/ascii.zig +++ b/src/coral/ascii.zig @@ -7,7 +7,7 @@ const scalars = @import("./scalars.zig"); const std = @import("std"); pub const DecimalFormat = struct { - delimiter: []const coral.Byte, + delimiter: []const coral.io.Byte, positive_prefix: enum {none, plus, space}, pub const default = DecimalFormat{ @@ -115,9 +115,9 @@ pub const DecimalFormat = struct { } } - pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) io.PrintError!void { + pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.Error!void { if (value == 0) { - return io.print(writer, switch (self.positive_prefix) { + return io.write_all(writer, switch (self.positive_prefix) { .none => "0", .plus => "+0", .space => " 0", @@ -151,21 +151,21 @@ pub const DecimalFormat = struct { } } - return io.print(writer, buffer[buffer_start ..]); + return io.write_all(writer, buffer[buffer_start ..]); }, .Float => |float| { if (value < 0) { - try io.print(writer, "-"); + try io.write_all(writer, "-"); } const Float = @TypeOf(value); const Int = std.meta.Int(.unsigned, float.bits); const integer = @as(Int, @intFromFloat(value)); - try self.print(writer, integer); - try io.print(writer, "."); - try self.print(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100))); + try self.format(writer, integer); + try io.write_all(writer, "."); + try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100))); }, else => @compileError("`" ++ @typeName(Value) ++ "` cannot be formatted to a decimal string"), @@ -178,13 +178,13 @@ pub const HexadecimalFormat = struct { positive_prefix: enum {none, plus, space} = .none, casing: enum {lower, upper} = .lower, - const default = HexadecimalFormat{ + pub const default = HexadecimalFormat{ .delimiter = "", .positive_prefix = .none, .casing = .lower, }; - pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) ?usize { + pub fn format(self: HexadecimalFormat, writer: io.Writer, value: anytype) io.Error!void { // TODO: Implement. _ = self; _ = writer; diff --git a/src/coral/asyncio.zig b/src/coral/asyncio.zig new file mode 100644 index 0000000..485beab --- /dev/null +++ b/src/coral/asyncio.zig @@ -0,0 +1,76 @@ +const std = @import("std"); + +pub fn BlockingQueue(comptime max: usize, comptime T: type) type { + return struct { + buffer: [max]T = undefined, + head: usize = 0, + tail: usize = 0, + mutex: std.Thread.Mutex = .{}, + not_empty: std.Thread.Condition = .{}, + not_full: std.Thread.Condition = .{}, + + const Self = @This(); + + pub fn enqueue(self: *Self, item: T) void { + self.mutex.lock(); + + defer self.mutex.unlock(); + + while (((self.tail + 1) % max) == self.head) { + self.not_full.wait(&self.mutex); + } + + self.buffer[self.tail] = item; + self.tail = (self.tail + 1) % max; + + self.not_empty.signal(); + } + + pub fn dequeue(self: *Self) T { + self.mutex.lock(); + + defer self.mutex.unlock(); + + while (self.head == self.tail) { + self.not_empty.wait(&self.mutex); + } + + const item = self.buffer[self.head]; + + self.buffer[self.head] = undefined; + self.head = (self.head + 1) % max; + + self.not_full.signal(); + + return item; + } + }; +} + +pub fn Future(comptime T: type) type { + return struct { + value: ?T = null, + resolved: std.Thread.ResetEvent = .{}, + + const Self = @This(); + + pub fn get(self: *Self) *T { + // TODO: Make async. + self.resolved.wait(); + + return &self.value.?; + } + + pub fn resolve(self: *Self, value: T) bool { + if (self.resolved.isSet()) { + return false; + } + + self.value = value; + + self.resolved.set(); + + return true; + } + }; +} diff --git a/src/coral/coral.zig b/src/coral/coral.zig index d760db7..e7ce11c 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -1,6 +1,6 @@ pub const ascii = @import("./ascii.zig"); -pub const dag = @import("./dag.zig"); +pub const asyncio = @import("./asyncio.zig"); pub const files = @import("./files.zig"); @@ -10,302 +10,116 @@ 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 slots = @import("./slots.zig"); - pub const stack = @import("./stack.zig"); -pub const system = @import("./system.zig"); - -pub const utf8 = @import("./utf8.zig"); - -pub const vectors = @import("./vectors.zig"); - -pub const World = @import("./World.zig"); - const std = @import("std"); -pub const ResourceOptions = struct { - thread_restriction: World.ThreadRestriction, - read_only: bool = false, -}; - -pub fn Read(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .none, - .read_only = true, - }); -} - -pub fn ReadBlocking(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .main, - .read_only = true, - }); -} - -pub fn Resource(comptime Value: type, comptime options: ResourceOptions) 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 = options.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 = options.read_only, - }, - }), - }; +pub const utf8 = @import("./utf8.zig"); +pub fn Pool(comptime Value: type) type { return struct { - res: Qualified, + 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 const State = struct { - res: Qualified, - }; + pub fn deinit(self: *Self) void { + self.entries.deinit(); - pub fn bind(context: system.BindContext) std.mem.Allocator.Error!State { - const thread_restriction_name = switch (thread_restriction) { - .main => "main thread-restricted ", - .none => "" - }; + self.* = undefined; + } - const res = switch (options.read_only) { - true => (try context.register_read_only_resource_access(thread_restriction, Value)), - false => (try context.register_read_write_resource_access(thread_restriction, 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 (options.read_only) "read-only" else "read-write", - @typeName(Value), - })); - }, - }, + pub fn get(self: *Self, key: usize) ?*Value { + return switch (self.entries.values[key]) { + .free_index => null, + .occupied => |*occupied| occupied, }; } - pub fn init(state: *State) Self { + pub fn init(allocator: std.mem.Allocator) Self { return .{ - .res = state.res, + .entries = .{.allocator = allocator}, }; } - pub const thread_restriction = options.thread_restriction; - }; -} + pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize { + const entries_count = self.entries.len(); -pub fn Write(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .none, - }); -} + if (self.first_free_index == entries_count) { + try self.entries.push_grow(.{.occupied = value}); -pub fn WriteBlocking(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .main, - }); -} + self.first_free_index += 1; -fn parameter_type(comptime Value: type) *const system.Info.Parameter { - const has_state = @hasDecl(Value, "State"); + return entries_count; + } - if (@sizeOf(Value) == 0) { - @compileError("System parameters must have a non-zero size"); - } + const insersion_index = self.first_free_index; - const parameters = struct { - fn bind(allocator: std.mem.Allocator, context: system.BindContext) std.mem.Allocator.Error!?*anyopaque { - if (has_state) { - const value_name = @typeName(Value); + self.first_free_index = self.entries.values[self.first_free_index].free_index; + self.entries.values[insersion_index] = .{.occupied = value}; - if (!@hasDecl(Value, "bind")) { - @compileError( - "a `bind` declaration on " ++ - value_name ++ - " is requied for parameter types with a `State` declaration"); - } + return insersion_index; + } - 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 state = try allocator.create(Value.State); - - state.* = switch (bind_type.Fn.return_type.?) { - Value.State => Value.bind(context), - std.mem.Allocator.Error!Value.State => try Value.bind(context), - else => @compileError( - "`bind` fn on " ++ - @typeName(Value) ++ - " must return " ++ - @typeName(Value.State) ++ - " or " ++ - @typeName(std.mem.Allocator.Error!Value.State)), - }; - - return @ptrCast(state); - } else { + pub fn remove(self: *Self, key: usize) ?Value { + if (key >= self.entries.len()) { return null; } - } - fn init(argument: *anyopaque, state: ?*anyopaque) void { - const value_name = @typeName(Value); + switch (self.entries.values[key]) { + .free_index => { + return null; + }, - if (!@hasDecl(Value, "init")) { - @compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types"); - } + .occupied => |occupied| { + self.entries.values[key] = .{.free_index = self.first_free_index}; + self.first_free_index = key; - 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 (has_state) { - if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.State) { - @compileError("`init` fn on stateful " ++ value_name ++ " must accept a " ++ @typeName(*Value.State)); - } - - concrete_argument.* = Value.init(@ptrCast(@alignCast(state.?))); - } else { - if (init_type.Fn.params.len != 0) { - @compileError("`init` fn on statelss " ++ value_name ++ " cannot use parameters"); - } - - concrete_argument.* = Value.init(); + return occupied; + }, } } - fn unbind(allocator: std.mem.Allocator, state: ?*anyopaque) void { - if (@hasDecl(Value, "unbind")) { - if (has_state) { - const typed_state = @as(*Value.State, @ptrCast(@alignCast(state.?))); - - Value.unbind(typed_state); - allocator.destroy(typed_state); - } else { - Value.unbind(); - } - } + pub fn values(self: *const Self) Values { + return .{ + .pool = self, + }; } }; - - 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, states: *const [system.max_parameters]?*anyopaque) anyerror!void { - var call_args = @as(std.meta.ArgsTuple(Call), undefined); - - inline for (parameters, &call_args, states[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 = World.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/coral/files.zig b/src/coral/files.zig index 273202f..8756f3e 100644 --- a/src/coral/files.zig +++ b/src/coral/files.zig @@ -4,11 +4,20 @@ const io = @import("./io.zig"); const std = @import("std"); -pub const Error = error { +pub const AccessError = error { FileNotFound, FileInaccessible, }; +pub const ReadAllError = AccessError || error { + ReadIncomplete, +}; + +pub const ReadAllOptions = struct { + offset: u64 = 0, + limit: u64 = std.math.maxInt(u64), +}; + pub const Stat = struct { size: u64, }; @@ -18,15 +27,37 @@ pub const Storage = struct { vtable: *const VTable, pub const VTable = struct { - stat: *const fn (*anyopaque, []const u8) Error!Stat, - read: *const fn (*anyopaque, []const u8, usize, []io.Byte) Error!usize, + stat: *const fn (*anyopaque, []const u8) AccessError!Stat, + read: *const fn (*anyopaque, []const u8, usize, []io.Byte) AccessError!usize, }; - pub fn read_bytes(self: Storage, path: []const u8, offset: usize, output: []io.Byte) Error!usize { + pub fn read(self: Storage, path: []const u8, output: []io.Byte, offset: u64) AccessError!usize { return self.vtable.read(self.userdata, path, offset, output); } - pub fn read_foreign(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type { + pub fn read_all(self: Storage, path: []const u8, output: []io.Byte, options: ReadAllOptions) ReadAllError![]const io.Byte { + const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output); + + if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) { + return error.ReadIncomplete; + } + + return output[0 .. bytes_read]; + } + + pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]io.Byte { + const buffer = try allocator.alloc(io.Byte, @min((try self.stat(path)).size, options.limit)); + + errdefer allocator.free(buffer); + + if (try self.vtable.read(self.userdata, path, options.offset, buffer) != buffer.len) { + return error.ReadIncomplete; + } + + return buffer; + } + + pub fn read_foreign(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type { const decoded = (try self.read_native(path, offset, Type)) orelse { return null; }; @@ -37,7 +68,7 @@ pub const Storage = struct { }; } - pub fn read_native(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type { + pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type { var buffer = @as([@sizeOf(Type)]io.Byte, undefined); if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) { @@ -47,6 +78,10 @@ pub const Storage = struct { return @as(*align(1) const Type, @ptrCast(&buffer)).*; } + pub fn stat(self: Storage, path: []const u8) AccessError!Stat { + return self.vtable.stat(self.userdata, path); + } + pub const read_little = switch (native_endian) { .little => read_native, .big => read_foreign, @@ -60,7 +95,7 @@ pub const Storage = struct { pub const bundle = init: { const Bundle = struct { - fn full_path(path: []const u8) Error![4095:0]u8 { + fn full_path(path: []const u8) AccessError![4095:0]u8 { var buffer = [_:0]u8{0} ** 4095; _ = std.fs.cwd().realpath(path, &buffer) catch { @@ -70,7 +105,7 @@ pub const bundle = init: { return buffer; } - fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) Error!usize { + fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) 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, @@ -89,7 +124,7 @@ pub const bundle = init: { return file.read(output) catch error.FileInaccessible; } - fn stat(_: *anyopaque, path: []const u8) Error!Stat { + fn stat(_: *anyopaque, path: []const u8) AccessError!Stat { const file_stat = get: { var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| { return switch (open_error) { diff --git a/src/coral/io.zig b/src/coral/io.zig index 383825e..e220a55 100644 --- a/src/coral/io.zig +++ b/src/coral/io.zig @@ -108,13 +108,23 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type }; } -pub const PrintError = Error || error { - IncompleteWrite, +pub const NullWritable = struct { + written: usize = 0, + + pub fn write(self: *NullWritable, buffer: []const Byte) Error!usize { + self.written += buffer.len; + + return buffer.len; + } + + pub fn writer(self: *NullWritable) Writer { + return Writer.bind(NullWritable, self, write); + } }; -pub const Reader = Generator(Error!usize, &.{[]coral.Byte}); +pub const Reader = Generator(Error!usize, &.{[]Byte}); -pub const Writer = Generator(Error!usize, &.{[]const coral.Byte}); +pub const Writer = Generator(Error!usize, &.{[]const Byte}); pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte { const buffer = coral.Stack(coral.Byte){.allocator = allocator}; @@ -128,22 +138,6 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral. pub const bits_per_byte = 8; -pub fn bytes_of(value: anytype) []const Byte { - const pointer_info = @typeInfo(@TypeOf(value)).Pointer; - - return switch (pointer_info.size) { - .One => @as([*]const Byte, @ptrCast(value))[0 .. @sizeOf(pointer_info.child)], - .Slice => @as([*]const Byte, @ptrCast(value.ptr))[0 .. @sizeOf(pointer_info.child) * value.len], - else => @compileError("`value` must be single-element pointer or slice type"), - }; -} - -pub fn print(writer: Writer, utf8: []const u8) PrintError!void { - if (try writer.yield(.{utf8}) != utf8.len) { - return error.IncompleteWrite; - } -} - pub fn skip_n(input: Reader, distance: u64) Error!void { var buffer = @as([512]coral.Byte, undefined); var remaining = distance; @@ -159,16 +153,6 @@ pub fn skip_n(input: Reader, distance: u64) Error!void { } } -pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) { - var len = @as(usize, 0); - - while (ptr[len] != sen) { - len += 1; - } - - return ptr[0 .. len:sen]; -} - pub fn stream_all(input: Reader, output: Writer) Error!usize { var buffer = @as([512]coral.Byte, undefined); var copied = @as(usize, 0); @@ -206,3 +190,9 @@ pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize { remaining -= read; } } + +pub fn write_all(writer: Writer, utf8: []const u8) Error!void { + if (try writer.yield(.{utf8}) != utf8.len) { + return error.UnavailableResource; + } +} diff --git a/src/coral/vectors.zig b/src/coral/lina.zig similarity index 66% rename from src/coral/vectors.zig rename to src/coral/lina.zig index 8e265f2..0748791 100644 --- a/src/coral/vectors.zig +++ b/src/coral/lina.zig @@ -1,5 +1,18 @@ 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; @@ -48,3 +61,16 @@ 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/coral/map.zig index 6ac848e..eb7de4d 100644 --- a/src/coral/map.zig +++ b/src/coral/map.zig @@ -147,7 +147,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( self.* = undefined; } - pub fn get_ptr(self: Self, key: Key) ?*Value { + pub fn get(self: Self, key: Key) ?*Value { if (self.len == 0) { return null; } @@ -169,14 +169,6 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( return null; } - pub fn get(self: Self, key: Key) ?Value { - if (self.get_ptr(key)) |value| { - return value.*; - } - - return null; - } - pub fn emplace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!bool { try self.rehash(load_max); diff --git a/src/coral/script/Chunk.zig b/src/coral/script/Chunk.zig deleted file mode 100644 index 9ca1028..0000000 --- a/src/coral/script/Chunk.zig +++ /dev/null @@ -1,998 +0,0 @@ -const coral = @import("coral"); - -const file = @import("../file.zig"); - -const script = @import("../script.zig"); - -const std = @import("std"); - -const tokens = @import("./tokens.zig"); - -const tree = @import("./tree.zig"); - -name: *script.Object, -arity: u8, -opcodes: coral.Stack(Opcode), -lines: coral.Stack(tokens.Line), -cursor: usize, -constants: coral.Stack(*script.Object), -bindings: []?*script.Object, -externals: *script.Object, - -const Compiler = struct { - chunk: *Self, - env: *script.Runtime, - - fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const tree.Expr) script.Error!u8 { - // TODO: Exceeding 255 arguments will make the VM crash. - var maybe_argument = initial_argument; - var argument_count = @as(u8, 0); - - while (maybe_argument) |argument| { - try self.compile_expression(environment, argument, null); - - maybe_argument = argument.next; - argument_count += 1; - } - - return argument_count; - } - - fn compile_expression(self: *const Compiler, environment: *const tree.Environment, expression: *const tree.Expr, name: ?[]const u8) script.Error!void { - const number_format = coral.utf8.DecimalFormat{ - .delimiter = "_", - .positive_prefix = .none, - }; - - switch (expression.kind) { - .nil_literal => try self.chunk.write(expression.line, .push_nil), - .true_literal => try self.chunk.write(expression.line, .push_true), - .false_literal => try self.chunk.write(expression.line, .push_false), - - .number_literal => |literal| { - for (literal) |codepoint| { - if (codepoint == '.') { - return self.chunk.write(expression.line, .{ - .push_const = try self.declare_float(number_format.parse(literal, script.Float) orelse unreachable), - }); - } - } - - try self.chunk.write(expression.line, .{ - .push_const = try self.declare_fixed(number_format.parse(literal, script.Fixed) orelse unreachable), - }); - }, - - .string_literal => |literal| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_string(literal)}); - }, - - .vector2 => |vector2| { - try self.compile_expression(environment, vector2.x, null); - try self.compile_expression(environment, vector2.y, null); - try self.chunk.write(expression.line, .push_vector2); - }, - - .vector3 => |vector3| { - try self.compile_expression(environment, vector3.x, null); - try self.compile_expression(environment, vector3.y, null); - try self.compile_expression(environment, vector3.z, null); - try self.chunk.write(expression.line, .push_vector3); - }, - - .string_template => { - var current_expression = expression.next orelse { - return self.chunk.write(expression.line, .{.push_const = try self.declare_string("")}); - }; - - var component_count = @as(u8, 0); - - while (true) { - try self.compile_expression(environment, current_expression, null); - - component_count += 1; - - current_expression = current_expression.next orelse { - return self.chunk.write(expression.line, .{.push_concat = component_count}); - }; - } - }, - - .symbol_literal => |literal| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(literal)}); - }, - - .table => |table| { - var entries = table.nodes(); - var num_entries = @as(u16, 0); - - while (entries.next()) |entry| { - try self.compile_expression(environment, entry.key, null); - try self.compile_expression(environment, entry.value, null); - - num_entries = coral.scalars.add(num_entries, 1) orelse { - return self.env.raise(error.OutOfMemory, "too many initializer values", .{}); - }; - } - - try self.chunk.write(expression.line, .{.push_table = num_entries}); - }, - - .lambda_construct => |lambda_construct| { - var chunk = try Self.init(self.env, name orelse "", lambda_construct.environment, &.{}); - - errdefer chunk.deinit(self.env); - - if (lambda_construct.environment.capture_count == 0) { - try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)}); - } else { - const lambda_captures = lambda_construct.environment.get_captures(); - var index = lambda_captures.len; - - while (index != 0) { - index -= 1; - - try self.chunk.write(expression.line, switch (lambda_captures[index]) { - .declaration_index => |declaration_index| .{.push_local = declaration_index}, - .capture_index => |capture_index| .{.push_binding = capture_index}, - }); - } - - try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)}); - try self.chunk.write(expression.line, .{.bind = lambda_construct.environment.capture_count}); - } - }, - - .binary_op => |binary_op| { - try self.compile_expression(environment, binary_op.lhs_operand, null); - try self.compile_expression(environment, binary_op.rhs_operand, null); - - try self.chunk.write(expression.line, switch (binary_op.operation) { - .addition => .add, - .subtraction => .sub, - .multiplication => .mul, - .divsion => .div, - .greater_equals_comparison => .cge, - .greater_than_comparison => .cgt, - .equals_comparison => .eql, - .less_than_comparison => .clt, - .less_equals_comparison => .cle, - }); - }, - - .unary_op => |unary_op| { - try self.compile_expression(environment, unary_op.operand, null); - - try self.chunk.write(expression.line, switch (unary_op.operation) { - .boolean_negation => .not, - .numeric_negation => .neg, - }); - }, - - .invoke => |invoke| { - const argument_count = try self.compile_argument(environment, invoke.argument); - - try self.compile_expression(environment, invoke.object, null); - try self.chunk.write(expression.line, .{.call = argument_count}); - }, - - .group => |group| try self.compile_expression(environment, group, null), - - .declaration_get => |declaration_get| { - if (get_local_index(environment, declaration_get.declaration)) |index| { - if (is_declaration_boxed(declaration_get.declaration)) { - try self.chunk.write(expression.line, .{.push_local = index}); - try self.chunk.write(expression.line, .get_box); - } else { - try self.chunk.write(expression.line, .{.push_local = index}); - } - - return; - } - - if (try get_binding_index(environment, declaration_get.declaration)) |index| { - try self.chunk.write(expression.line, .{.push_binding = index}); - - if (is_declaration_boxed(declaration_get.declaration)) { - try self.chunk.write(expression.line, .get_box); - } - - return; - } - - return self.env.raise(error.IllegalState, "local out of scope", .{}); - }, - - .declaration_set => |declaration_set| { - if (get_local_index(environment, declaration_set.declaration)) |index| { - if (is_declaration_boxed(declaration_set.declaration)) { - try self.chunk.write(expression.line, .{.push_local = index}); - try self.compile_expression(environment, declaration_set.assign, null); - try self.chunk.write(expression.line, .set_box); - } else { - try self.compile_expression(environment, declaration_set.assign, null); - try self.chunk.write(expression.line, .{.set_local = index}); - } - - return; - } - - if (try get_binding_index(environment, declaration_set.declaration)) |index| { - try self.compile_expression(environment, declaration_set.assign, null); - try self.chunk.write(expression.line, .{.push_binding = index}); - - if (is_declaration_boxed(declaration_set.declaration)) { - try self.chunk.write(expression.line, .set_box); - } - - return; - } - - return self.env.raise(error.IllegalState, "local out of scope", .{}); - }, - - .field_get => |field_get| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_get.identifier)}); - try self.compile_expression(environment, field_get.object, null); - try self.chunk.write(expression.line, .get_dynamic); - }, - - .field_set => |field_set| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_set.identifier)}); - try self.compile_expression(environment, field_set.assign, null); - try self.compile_expression(environment, field_set.object, null); - try self.chunk.write(expression.line, .set_dynamic); - }, - - .subscript_get => |subscript_get| { - try self.compile_expression(environment, subscript_get.index, null); - try self.compile_expression(environment, subscript_get.object, null); - try self.chunk.write(expression.line, .get_dynamic); - }, - - .subscript_set => |subscript_set| { - try self.compile_expression(environment, subscript_set.index, null); - try self.compile_expression(environment, subscript_set.assign, null); - try self.compile_expression(environment, subscript_set.object, null); - try self.chunk.write(expression.line, .set_dynamic); - }, - - .external_get => |external_get| { - try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(external_get.name)}); - try self.chunk.write(expression.line, .get_external); - }, - } - } - - pub fn compile_environment(self: *const Compiler, environment: *const tree.Environment) script.Error!void { - if (environment.statement) |statement| { - const last_statement = try self.compile_statement(environment, statement); - - if (last_statement.kind != .@"return") { - try self.chunk.write(last_statement.line, .push_nil); - } - } - } - - fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const tree.Stmt) script.Error!*const tree.Stmt { - var current_statement = initial_statement; - - while (true) { - switch (current_statement.kind) { - .@"return" => |@"return"| { - if (@"return".returned_expression) |expression| { - try self.compile_expression(environment, expression, null); - } else { - try self.chunk.write(current_statement.line, .push_nil); - } - - // TODO: Omit ret calls at ends of chunk. - try self.chunk.write(current_statement.line, .ret); - }, - - .@"while" => |@"while"| { - try self.compile_expression(environment, @"while".loop_expression, null); - try self.chunk.write(current_statement.line, .{.jf = 0}); - - const origin_index = @as(u16, @intCast(self.chunk.opcodes.values.len - 1)); - - _ = try self.compile_statement(environment, @"while".loop); - self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1); - - try self.compile_expression(environment, @"while".loop_expression, null); - try self.chunk.write(current_statement.line, .{.jt = origin_index}); - }, - - .@"if" => |@"if"| { - try self.compile_expression(environment, @"if".then_expression, null); - try self.chunk.write(current_statement.line, .{.jf = 0}); - - const origin_index = @as(u16, @intCast(self.chunk.opcodes.values.len - 1)); - - _ = try self.compile_statement(environment, @"if".@"then"); - self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1); - - if (@"if".@"else") |@"else"| { - _ = try self.compile_statement(environment, @"else"); - } - }, - - .declare => |declare| { - try self.compile_expression(environment, declare.initial_expression, declare.declaration.identifier); - - if (is_declaration_boxed(declare.declaration)) { - try self.chunk.write(current_statement.line, .push_boxed); - } - }, - - .top_expression => |top_expression| { - try self.compile_expression(environment, top_expression, null); - - if (top_expression.kind == .invoke) { - try self.chunk.write(current_statement.line, .pop); - } - }, - } - - current_statement = current_statement.next orelse return current_statement; - } - } - - const constants_max = @as(usize, std.math.maxInt(u16)); - - fn declare_chunk(self: *const Compiler, chunk: Self) script.Error!u16 { - if (self.chunk.constants.values.len == std.math.maxInt(u16)) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = @as(usize, std.math.maxInt(u16)), - }); - } - - const constant = (try self.env.new_dynamic(chunk)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_fixed(self: *const Compiler, fixed: script.Fixed) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_fixed(fixed)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_float(self: *const Compiler, float: script.Float) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_float(float)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_string(self: *const Compiler, string: []const u8) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_string(string)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_vector2(self: *const Compiler, vector: script.Vector2) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_vector2(vector)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_vector3(self: *const Compiler, vector: script.Vector3) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_vector3(vector)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn declare_symbol(self: *const Compiler, symbol: []const u8) script.Error!u16 { - if (self.chunk.constants.values.len == constants_max) { - return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ - .max = constants_max, - }); - } - - const constant = (try self.env.new_symbol(symbol)).pop().?; - - errdefer self.env.release(constant); - - try self.chunk.constants.push(constant); - - return @intCast(self.chunk.constants.values.len - 1); - } - - fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) script.Error!?u8 { - var binding_index = @as(u8, 0); - - while (binding_index < environment.capture_count) : (binding_index += 1) { - var capture = &environment.captures[binding_index]; - var target_environment = environment.enclosing orelse return null; - - while (capture.* == .capture_index) { - capture = &target_environment.captures[capture.capture_index]; - target_environment = target_environment.enclosing orelse return null; - } - - std.debug.assert(capture.* == .declaration_index); - - if (&target_environment.declarations[capture.declaration_index] == declaration) { - return binding_index; - } - } - - return null; - } - - fn get_local_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 { - var remaining = environment.declaration_count; - - while (remaining != 0) { - remaining -= 1; - - if (&environment.declarations[remaining] == declaration) { - return remaining; - } - } - - return null; - } - - fn is_declaration_boxed(declaration: *const tree.Declaration) bool { - return declaration.is.captured and !declaration.is.readonly; - } -}; - -pub const Opcode = union (enum) { - ret, - pop, - push_nil, - push_true, - push_false, - push_const: u16, - push_local: u8, - push_top, - push_vector2, - push_vector3, - push_table: u16, - push_binding: u8, - push_concat: u8, - push_boxed, - set_local: u8, - get_dynamic, - set_dynamic, - get_external, - get_box, - set_box, - call: u8, - bind: u8, - - not, - neg, - - add, - sub, - mul, - div, - - eql, - cgt, - clt, - cge, - cle, - - jt: u16, - jf: u16, -}; - -pub const External = struct {[]const u8, *script.Object}; - -const Self = @This(); - -pub fn deinit(self: *Self, env: *script.Runtime) void { - while (self.constants.pop()) |constant| { - env.release(constant.*); - } - - self.constants.deinit(); - self.opcodes.deinit(); - self.lines.deinit(); - env.release(self.name); - env.release(self.externals); - - if (self.bindings.len != 0) { - for (self.bindings) |binding| { - if (binding) |value| { - env.release(value); - } - } - - env.allocator.free(self.bindings); - } - - self.bindings = &.{}; -} - -pub fn dump(chunk: Self, env: *script.Runtime) script.Error!*script.Object { - var opcode_cursor = @as(u32, 0); - var buffer = coral.list.ByteStack.init(env.allocator); - - defer buffer.deinit(); - - const writer = coral.list.stack_as_writer(&buffer); - - _ = coral.utf8.print_string(writer, "\n"); - - while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) { - _ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor}); - - _ = switch (chunk.opcodes.values[opcode_cursor]) { - .ret => coral.utf8.print_string(writer, "ret\n"), - .pop => coral.utf8.print_string(writer, "pop\n"), - .push_nil => coral.utf8.print_string(writer, "push nil\n"), - .push_true => coral.utf8.print_string(writer, "push true\n"), - .push_false => coral.utf8.print_string(writer, "push false\n"), - - .push_const => |push_const| print: { - const string_ref = (try (try env.push(try chunk.get_constant(env, push_const))).to_string()).pop().?; - - defer env.release(string_ref); - - const string = string_ref.is_string(); - - break: print coral.utf8.print_formatted(writer, "push const ({value})\n", .{.value = string.?}); - }, - - .push_local => |push_local| coral.utf8.print_formatted(writer, "push local ({local})\n", .{ - .local = push_local, - }), - - .push_top => coral.utf8.print_string(writer, "push top\n"), - - .push_table => |push_table| coral.utf8.print_formatted(writer, "push table ({count})\n", .{ - .count = push_table, - }), - - .push_boxed => coral.utf8.print_string(writer, "push boxed\n"), - - .push_binding => |push_binding| coral.utf8.print_formatted(writer, "push binding ({binding})\n", .{ - .binding = push_binding, - }), - - .push_concat => |push_concat| coral.utf8.print_formatted(writer, "push concat ({count})\n", .{ - .count = push_concat, - }), - - .push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin ({builtin})\n", .{ - .builtin = switch (push_builtin) { - .import => "import", - .print => "print", - .vec2 => "vec2", - .vec3 => "vec3", - }, - }), - - .bind => |bind| coral.utf8.print_formatted(writer, "bind ({count})\n", .{ - .count = bind, - }), - - .set_local => |local_set| coral.utf8.print_formatted(writer, "set local ({local})\n", .{ - .local = local_set, - }), - - .get_box => coral.utf8.print_string(writer, "get box\n"), - .set_box => coral.utf8.print_string(writer, "set box\n"), - .get_dynamic => coral.utf8.print_string(writer, "get dynamic\n"), - .set_dynamic => coral.utf8.print_string(writer, "set dynamic\n"), - .call => |call| coral.utf8.print_formatted(writer, "call ({count})\n", .{.count = call}), - .not => coral.utf8.print_string(writer, "not\n"), - .neg => coral.utf8.print_string(writer, "neg\n"), - .add => coral.utf8.print_string(writer, "add\n"), - .sub => coral.utf8.print_string(writer, "sub\n"), - .mul => coral.utf8.print_string(writer, "mul\n"), - .div => coral.utf8.print_string(writer, "div\n"), - .eql => coral.utf8.print_string(writer, "eql\n"), - .cgt => coral.utf8.print_string(writer, "cgt\n"), - .clt => coral.utf8.print_string(writer, "clt\n"), - .cge => coral.utf8.print_string(writer, "cge\n"), - .cle => coral.utf8.print_string(writer, "cle\n"), - .jf => |jf| coral.utf8.print_formatted(writer, "jf ({instruction})\n", .{.instruction = jf}), - .jt => |jt| coral.utf8.print_formatted(writer, "jt ({instruction})\n", .{.instruction = jt}), - }; - } - - return (try env.new_string(buffer.values)).pop().?; -} - -pub fn execute(self: *Self, env: *script.Runtime) script.Error!?*script.Object { - self.cursor = 0; - - while (self.cursor < self.opcodes.values.len) : (self.cursor += 1) { - switch (self.opcodes.values[self.cursor]) { - .ret => break, - .pop => env.discard(), - .push_nil => _ = try env.push(null), - .push_true => _ = try env.new_boolean(true), - .push_false => _ = try env.new_boolean(false), - .push_const => |push_const| _ = try env.push(try self.get_constant(env, push_const)), - .push_local => |push_local| _ = try env.local_get(push_local), - .push_top => _ = try env.local_top(), - - .push_vector2 => { - const y = try env.expect_float(try env.expect_object(env.pop())); - const x = try env.expect_float(try env.expect_object(env.pop())); - - _ = try env.new_vector2(.{@floatCast(x), @floatCast(y)}); - }, - - .push_vector3 => { - const z = try env.expect_float(try env.expect_object(env.pop())); - const y = try env.expect_float(try env.expect_object(env.pop())); - const x = try env.expect_float(try env.expect_object(env.pop())); - - _ = try env.new_vector3(.{@floatCast(x), @floatCast(y), @floatCast(z)}); - }, - - .push_table => |push_table| { - const table = (try env.new_table()).pop().?; - - defer env.release(table); - - var popped = @as(usize, 0); - - while (popped < push_table) : (popped += 1) { - if (env.pop()) |object| { - defer env.release(object); - - try env.index_set(table, object); - } else { - env.release(try env.expect_object(env.pop())); - } - } - - _ = try env.push(table); - }, - - .push_boxed => { - const value = env.pop(); - - defer { - if (value) |object| { - env.release(object); - } - } - - _ = try env.new_boxed(value); - }, - - .push_binding => |push_binding| _ = try env.push(try self.get_binding(env, push_binding)), - .push_concat => |push_concat| _ = try env.concat(push_concat), - - .bind => |bind| { - const callable = try env.expect_object(env.pop()); - - defer env.release(callable); - - const chunk = try env.expect_dynamic(callable, Self); - - if (chunk.bindings.len != 0) { - return env.raise(error.IllegalState, "cannot bind values to an already-bound chunk", .{}); - } - - chunk.bindings = try env.allocator.alloc(?*script.Object, bind); - - errdefer env.allocator.free(chunk.bindings); - - for (0 .. bind) |index| { - const value = env.pop(); - - errdefer { - if (value) |object| { - env.release(object); - } - } - - chunk.bindings[index] = value; - } - - _ = try env.push(callable); - }, - - .set_local => |local_set| _ = try env.local_set(local_set, env.pop()), - .get_box => _ = try env.boxed_get(), - - .set_box => { - const value = env.pop(); - - defer { - if (value) |object| { - env.release(object); - } - } - - try env.boxed_set(value); - }, - - .get_dynamic => { - const indexable = try env.expect_object(env.pop()); - - defer env.release(indexable); - - _ = try env.index_get(indexable); - }, - - .set_dynamic => { - const indexable = try env.expect_object(env.pop()); - - defer env.release(indexable); - - const value = env.pop(); - - defer { - if (value) |object| { - env.release(object); - } - } - - try env.index_set(indexable, value); - }, - - .get_external => _ = try env.index_get(self.externals), - .call => |call| _ = try env.call(call), - - .not => { - const object = try env.expect_object(env.pop()); - - defer env.release(object); - - _ = try env.new_boolean(object.is_false()); - }, - - .neg => _ = try env.neg(), - - .add => { - const addable = try env.expect_object(env.pop()); - - defer env.release(addable); - - _ = switch (try env.expect_numeric(addable)) { - .fixed => |fixed| try env.fixed_add(fixed), - .float => |float| try env.float_add(float), - .vector2 => |vector2| try env.vector2_add(vector2), - .vector3 => |vector3| try env.vector3_add(vector3), - }; - }, - - .sub => { - const subtractable = try env.expect_object(env.pop()); - - defer env.release(subtractable); - - _ = switch (try env.expect_numeric(subtractable)) { - .fixed => |fixed| try env.fixed_subtract(fixed), - .float => |float| try env.float_subtract(float), - .vector2 => |vector2| try env.vector2_subtract(vector2), - .vector3 => |vector3| try env.vector3_subtract(vector3), - }; - }, - - .mul => { - const multiplicable = try env.expect_object(env.pop()); - - defer env.release(multiplicable); - - _ = switch (try env.expect_numeric(multiplicable)) { - .fixed => |fixed| try env.fixed_multiply(fixed), - .float => |float| try env.float_multiply(float), - .vector2 => |vector2| try env.vector2_multiply(vector2), - .vector3 => |vector3| try env.vector3_multiply(vector3), - }; - }, - - .div => { - const divisible = try env.expect_object(env.pop()); - - defer env.release(divisible); - - _ = switch (try env.expect_numeric(divisible)) { - .fixed => |fixed| try env.fixed_divide(fixed), - .float => |float| try env.float_divide(float), - .vector2 => |vector2| try env.vector2_divide(vector2), - .vector3 => |vector3| try env.vector3_divide(vector3), - }; - }, - - .eql => { - if (env.pop()) |equatable| { - defer env.release(equatable); - - _ = try env.equals_object(equatable); - } else { - _ = try env.equals_nil(); - } - }, - - .cgt => { - const comparable = try env.expect_object(env.pop()); - - defer env.release(comparable); - - _ = try env.compare_greater(comparable); - }, - - .clt => { - const comparable = try env.expect_object(env.pop()); - - defer env.release(comparable); - - _ = try env.compare_less(comparable); - }, - - .cge => { - const comparable = try env.expect_object(env.pop()); - - defer env.release(comparable); - - _ = try env.compare_greater_equals(comparable); - }, - - .cle => { - const comparable = try env.expect_object(env.pop()); - - defer env.release(comparable); - - _ = try env.compare_less_equals(comparable); - }, - - .jf => |jf| { - if (env.pop()) |condition| { - defer env.release(condition); - - if (condition.is_false()) { - self.cursor = jf; - } - } else { - self.cursor = jf; - } - }, - - .jt => |jt| { - if (env.pop()) |condition| { - defer env.release(condition); - - if (condition.is_true()) { - self.cursor = jt; - } - } - }, - } - } - - return env.pop(); -} - -fn get_binding(self: *Self, env: *script.Runtime, index: usize) script.Error!?*script.Object { - if (index >= self.bindings.len) { - return env.raise(error.IllegalState, "invalid binding", .{}); - } - - return self.bindings[index]; -} - -fn get_constant(self: *const Self, env: *script.Runtime, index: usize) script.Error!*script.Object { - if (index >= self.constants.values.len) { - return env.raise(error.IllegalState, "invalid constant", .{}); - } - - return self.constants.values[index]; -} - -pub fn init(env: *script.Runtime, name: []const u8, environment: *const tree.Environment, externals: []const External) script.Error!Self { - const name_symbol = (try env.new_symbol(name)).pop().?; - - errdefer env.release(name_symbol); - - const externals_table = (try env.new_table()).pop().?; - - errdefer env.release(externals_table); - - for (0 .. externals.len) |i| { - const external_name, const external_object = externals[i]; - - try (try env.new_symbol(external_name)).index_set(externals_table, external_object); - } - - var chunk = Self{ - .externals = externals_table, - .name = name_symbol, - .opcodes = .{.allocator = env.allocator}, - .constants = .{.allocator = env.allocator}, - .lines = .{.allocator = env.allocator}, - .bindings = &.{}, - .arity = environment.argument_count, - .cursor = 0, - }; - - var compiler = Compiler{ - .chunk = &chunk, - .env = env, - }; - - try compiler.compile_environment(environment); - - return chunk; -} - -pub const typeinfo = script.Typeinfo{ - .name = "lambda", - .destruct = typeinfo_destruct, - .call = typeinfo_call, - .to_string = typeinfo_to_string, -}; - -fn typeinfo_call(context: script.Typeinfo.CallContext) script.Error!?*script.Object { - return @as(*Self, @ptrCast(@alignCast(context.userdata))).execute(context.env); -} - -fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void { - @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); -} - -fn typeinfo_to_string(context: script.Typeinfo.ToStringContext) script.Error!*script.Object { - return (try (try context.env.push(@as(*Self, @ptrCast(@alignCast(context.userdata))).name)).to_string()).pop().?; -} - -pub fn write(self: *Self, line: tokens.Line, opcode: Opcode) std.mem.Allocator.Error!void { - try self.opcodes.push(opcode); - try self.lines.push(line); -} diff --git a/src/coral/script/Table.zig b/src/coral/script/Table.zig deleted file mode 100644 index a93a0a5..0000000 --- a/src/coral/script/Table.zig +++ /dev/null @@ -1,135 +0,0 @@ -const coral = @import("coral"); - -const script = @import("../script.zig"); - -associative: coral.map.Table(*script.Object, *script.Object, struct { - pub const hash = script.Object.hash; - - pub const equals = script.Object.equals; -}), - -contiguous: coral.Stack(?*script.Object), - -const Self = @This(); - -pub fn deinit(self: *Self, env: *script.Runtime) void { - { - var entries = self.associative.entries(); - - while (entries.next()) |entry| { - env.release(entry.key); - env.release(entry.value); - } - } - - self.associative.deinit(); - - while (self.contiguous.pop()) |value| { - if (value.*) |ref| { - env.release(ref); - } - } - - self.contiguous.deinit(); -} - -pub fn init(env: *script.Runtime) Self { - return .{ - .associative = .{ - .allocator = env.allocator, - .traits = .{}, - }, - - .contiguous = .{.allocator = env.allocator}, - }; -} - -pub const typeinfo = script.Typeinfo{ - .name = "table", - .destruct = typeinfo_destruct, - .get = typeinfo_get, - .set = typeinfo_set, - .count = typeinfo_count, -}; - -fn typeinfo_count(context: script.Typeinfo.CountContext) script.Error!script.Fixed { - const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); - - return @intCast(table.associative.len + table.contiguous.values.len); -} - -fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void { - @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); -} - -fn typeinfo_get(context: script.Typeinfo.GetContext) script.Error!?*script.Object { - const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); - const index = (try context.push_index()).pop().?; - - defer context.env.release(index); - - if (index.is_fixed()) |fixed| { - if (fixed < 0) { - // TODO: Negative indexing. - unreachable; - } - - if (fixed < table.contiguous.values.len) { - return table.contiguous.values[@intCast(fixed)]; - } - } - - if (table.associative.lookup(index)) |value| { - return value; - } - - return null; -} - -fn typeinfo_set(context: script.Typeinfo.SetContext) script.Error!void { - const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); - const index = (try context.push_index()).pop().?; - - errdefer context.env.release(index); - - if (index.is_fixed()) |fixed| { - if (fixed < 0) { - // TODO: Negative indexing. - unreachable; - } - - if (fixed < table.contiguous.values.len) { - const maybe_replacing = &table.contiguous.values[@intCast(fixed)]; - - if (maybe_replacing.*) |replacing| { - context.env.release(replacing); - } - - if ((try context.push_value()).pop()) |value| { - errdefer context.env.release(value); - - maybe_replacing.* = value; - } else { - maybe_replacing.* = null; - } - - return; - } - } - - const value = (try context.push_value()).pop() orelse { - if (table.associative.remove(index)) |removed| { - context.env.release(removed.key); - context.env.release(removed.value); - } - - return; - }; - - errdefer context.env.release(value); - - if (try table.associative.replace(index, value)) |replaced| { - context.env.release(replaced.key); - context.env.release(replaced.value); - } -} diff --git a/src/coral/script/tokens.zig b/src/coral/script/tokens.zig deleted file mode 100644 index b0ed5d4..0000000 --- a/src/coral/script/tokens.zig +++ /dev/null @@ -1,535 +0,0 @@ -const coral = @import("coral"); - -const std = @import("std"); - -pub const Line = struct { - number: u32, -}; - -pub const Token = union(enum) { - end, - unknown: u8, - newline, - identifier: []const u8, - builtin: []const u8, - - symbol_plus, - symbol_minus, - symbol_asterisk, - symbol_forward_slash, - symbol_paren_left, - symbol_paren_right, - symbol_bang, - symbol_comma, - symbol_at, - symbol_brace_left, - symbol_brace_right, - symbol_bracket_left, - symbol_bracket_right, - symbol_period, - symbol_colon, - symbol_less_than, - symbol_less_equals, - symbol_greater_than, - symbol_greater_equals, - symbol_equals, - symbol_double_equals, - - number: []const u8, - string: []const u8, - template_string: []const u8, - - keyword_nil, - keyword_false, - keyword_true, - keyword_return, - keyword_self, - keyword_const, - keyword_if, - keyword_do, - keyword_end, - keyword_while, - keyword_else, - keyword_elif, - keyword_var, - keyword_vec2, - keyword_vec3, - keyword_let, - keyword_lambda, - - pub fn text(self: Token) []const u8 { - return switch (self) { - .end => "end", - .unknown => |unknown| @as([*]const u8, @ptrCast(&unknown))[0 .. 1], - .newline => "newline", - - .identifier => |identifier| identifier, - .builtin => |identifier| identifier, - - .symbol_plus => "+", - .symbol_minus => "-", - .symbol_asterisk => "*", - .symbol_forward_slash => "/", - .symbol_paren_left => "(", - .symbol_paren_right => ")", - .symbol_bang => "!", - .symbol_comma => ",", - .symbol_at => "@", - .symbol_brace_left => "{", - .symbol_brace_right => "}", - .symbol_bracket_left => "[", - .symbol_bracket_right => "]", - .symbol_period => ".", - .symbol_colon => ":", - .symbol_less_than => "<", - .symbol_less_equals => "<=", - .symbol_greater_than => ">", - .symbol_greater_equals => ">=", - .symbol_equals => "=", - .symbol_double_equals => "==", - - .number => |literal| literal, - .string => |literal| literal, - .template_string => |literal| literal, - - .keyword_const => "const", - .keyword_nil => "nil", - .keyword_false => "false", - .keyword_true => "true", - .keyword_return => "return", - .keyword_self => "self", - .keyword_if => "if", - .keyword_do => "do", - .keyword_end => "end", - .keyword_while => "while", - .keyword_elif => "elif", - .keyword_else => "else", - .keyword_var => "var", - .keyword_vec2 => "vec2", - .keyword_vec3 => "vec3", - .keyword_let => "let", - .keyword_lambda => "lambda", - }; - } -}; - -pub const Stream = struct { - source: []const u8, - line: Line = .{.number = 1}, - token: Token = .newline, - - pub fn skip_newlines(self: *Stream) void { - self.step(); - - while (self.token == .newline) { - self.step(); - } - } - - pub fn step(self: *Stream) void { - var cursor = @as(usize, 0); - - defer self.source = self.source[cursor ..]; - - while (cursor < self.source.len) { - switch (self.source[cursor]) { - '#' => { - cursor += 1; - - while (cursor < self.source.len and self.source[cursor] != '\n') { - cursor += 1; - } - }, - - ' ', '\t' => cursor += 1, - - '\n' => { - cursor += 1; - self.token = .newline; - self.line.number += 1; - - return; - }, - - '0' ... '9' => { - const begin = cursor; - - cursor += 1; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '0' ... '9' => cursor += 1, - - '.' => { - cursor += 1; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '0' ... '9' => cursor += 1, - else => break, - }; - - self.token = .{.number = self.source[begin .. cursor]}; - - return; - }, - - else => break, - }; - - self.token = .{.number = self.source[begin .. cursor]}; - - return; - }, - - 'A' ... 'Z', 'a' ... 'z', '_' => { - const begin = cursor; - - cursor += 1; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1, - else => break, - }; - - const identifier = self.source[begin .. cursor]; - - std.debug.assert(identifier.len != 0); - - switch (identifier[0]) { - 'c' => { - if (coral.are_equal(identifier[1 ..], "onst")) { - self.token = .keyword_const; - - return; - } - }, - - 'd' => { - if (coral.are_equal(identifier[1 ..], "o")) { - self.token = .keyword_do; - - return; - } - }, - - 'e' => { - if (coral.are_equal(identifier[1 ..], "lse")) { - self.token = .keyword_else; - - return; - } - - if (coral.are_equal(identifier[1 ..], "lif")) { - self.token = .keyword_elif; - - return; - } - - if (coral.are_equal(identifier[1 ..], "nd")) { - self.token = .keyword_end; - - return; - } - }, - - 'f' => { - if (coral.are_equal(identifier[1 ..], "alse")) { - self.token = .keyword_false; - - return; - } - }, - - 'i' => { - if (coral.are_equal(identifier[1 ..], "f")) { - self.token = .keyword_if; - - return; - } - }, - - 'l' => { - if (coral.are_equal(identifier[1 ..], "ambda")) { - self.token = .keyword_lambda; - - return; - } - - if (coral.are_equal(identifier[1 ..], "et")) { - self.token = .keyword_let; - - return; - } - }, - - 'n' => { - if (coral.are_equal(identifier[1 ..], "il")) { - self.token = .keyword_nil; - - return; - } - }, - - 'r' => { - if (coral.are_equal(identifier[1 ..], "eturn")) { - self.token = .keyword_return; - - return; - } - }, - - 's' => { - if (coral.are_equal(identifier[1 ..], "elf")) { - self.token = .keyword_self; - - return; - } - }, - - 't' => { - if (coral.are_equal(identifier[1 ..], "rue")) { - self.token = .keyword_true; - - return; - } - }, - - 'v' => { - const rest = identifier[1 ..]; - - if (coral.are_equal(rest, "ar")) { - self.token = .keyword_var; - - return; - } - - if (coral.are_equal(rest, "vec2")) { - self.token = .keyword_vec2; - - return; - } - - if (coral.are_equal(rest, "vec3")) { - self.token = .keyword_vec3; - - return; - } - }, - - 'w' => { - if (coral.are_equal(identifier[1 ..], "hile")) { - self.token = .keyword_while; - - return; - } - }, - - else => {}, - } - - self.token = .{.identifier = identifier}; - - return; - }, - - '`' => { - cursor += 1; - - const begin = cursor; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '`' => break, - else => cursor += 1, - }; - - self.token = .{.template_string = self.source[begin .. cursor]}; - cursor += 1; - - return; - }, - - '"' => { - cursor += 1; - - const begin = cursor; - - while (cursor < self.source.len) switch (self.source[cursor]) { - '"' => break, - else => cursor += 1, - }; - - self.token = .{.string = self.source[begin .. cursor]}; - cursor += 1; - - return; - }, - - '{' => { - self.token = .symbol_brace_left; - cursor += 1; - - return; - }, - - '}' => { - self.token = .symbol_brace_right; - cursor += 1; - - return; - }, - - '[' => { - self.token = .symbol_bracket_left; - cursor += 1; - - return; - }, - - ']' => { - self.token = .symbol_bracket_right; - cursor += 1; - - return; - }, - - ',' => { - self.token = .symbol_comma; - cursor += 1; - - return; - }, - - '!' => { - self.token = .symbol_bang; - cursor += 1; - - return; - }, - - ')' => { - self.token = .symbol_paren_right; - cursor += 1; - - return; - }, - - '(' => { - self.token = .symbol_paren_left; - cursor += 1; - - return; - }, - - '/' => { - self.token = .symbol_forward_slash; - cursor += 1; - - return; - }, - - '*' => { - self.token = .symbol_asterisk; - cursor += 1; - - return; - }, - - '-' => { - self.token = .symbol_minus; - cursor += 1; - - return; - }, - - '+' => { - self.token = .symbol_plus; - cursor += 1; - - return; - }, - - ':' => { - self.token = .symbol_colon; - cursor += 1; - - return; - }, - - '=' => { - cursor += 1; - - if (cursor < self.source.len) { - switch (self.source[cursor]) { - '=' => { - cursor += 1; - self.token = .symbol_double_equals; - - return; - }, - - else => {}, - } - } - - self.token = .symbol_equals; - - return; - }, - - '<' => { - cursor += 1; - - if (cursor < self.source.len and (self.source[cursor] == '=')) { - cursor += 1; - self.token = .symbol_less_equals; - - return; - } - - self.token = .symbol_less_than; - - return; - }, - - '>' => { - cursor += 1; - - if (cursor < self.source.len and (self.source[cursor] == '=')) { - cursor += 1; - self.token = .symbol_greater_equals; - - return; - } - - self.token = .symbol_greater_than; - - return; - }, - - '.' => { - self.token = .symbol_period; - cursor += 1; - - return; - }, - - '@' => { - self.token = .symbol_at; - cursor += 1; - - return; - }, - - else => { - self.token = .{.unknown = self.source[cursor]}; - cursor += 1; - - return; - }, - } - } - - self.token = .end; - - return; - } -}; diff --git a/src/coral/script/tree.zig b/src/coral/script/tree.zig deleted file mode 100644 index bc77118..0000000 --- a/src/coral/script/tree.zig +++ /dev/null @@ -1,268 +0,0 @@ -pub const Expr = @import("./tree/Expr.zig"); - -pub const Stmt = @import("./tree/Stmt.zig"); - -const coral = @import("coral"); - -const std = @import("std"); - -const script = @import("../script.zig"); - -const tokens = @import("./tokens.zig"); - -pub const Declaration = struct { - identifier: []const coral.Byte, - - is: packed struct { - readonly: bool = false, - captured: bool = false, - } = .{}, -}; - -pub const Environment = struct { - captures: [capture_max]Capture = [_]Capture{.{.declaration_index = 0}} ** capture_max, - capture_count: u8 = 0, - declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max, - declaration_count: u8 = 0, - argument_count: u8 = 0, - statement: ?*const Stmt = null, - enclosing: ?*Environment = null, - - pub const Capture = union (enum) { - declaration_index: u8, - capture_index: u8, - }; - - pub const DeclareError = std.mem.Allocator.Error || error { - DeclarationExists, - }; - - const capture_max = std.math.maxInt(u8); - - const declaration_max = std.math.maxInt(u8); - - pub fn create_enclosed(self: *Environment, root: *Root) std.mem.Allocator.Error!*Environment { - const environment = try root.arena.allocator().create(Environment); - - environment.* = .{.enclosing = self}; - - return environment; - } - - fn declare(self: *Environment, declaration: Declaration) DeclareError!*const Declaration { - if (self.declaration_count == self.declarations.len) { - return error.OutOfMemory; - } - - { - var environment = self; - - while (true) { - var remaining_count = environment.declaration_count; - - while (remaining_count != 0) { - remaining_count -= 1; - - if (coral.are_equal(environment.declarations[remaining_count].identifier, declaration.identifier)) { - return error.DeclarationExists; - } - } - - environment = environment.enclosing orelse break; - } - } - - const declaration_slot = &self.declarations[self.declaration_count]; - - declaration_slot.* = declaration; - self.declaration_count += 1; - - return declaration_slot; - } - - pub fn declare_argument(self: *Environment, identifier: []const u8) DeclareError!*const Declaration { - std.debug.assert(self.declaration_count <= self.argument_count); - - defer self.argument_count += 1; - - return self.declare(.{ - .identifier = identifier, - .is = .{.readonly = true}, - }); - } - - pub fn declare_constant(self: *Environment, identifier: []const u8) DeclareError!*const Declaration { - return self.declare(.{ - .identifier = identifier, - .is = .{.readonly = true}, - }); - } - - pub fn declare_variable(self: *Environment, identifier: []const u8) DeclareError!*const Declaration { - return self.declare(.{.identifier = identifier}); - } - - pub fn resolve_declaration(self: *Environment, identifier: []const u8) std.mem.Allocator.Error!?*const Declaration { - var environment = self; - var ancestry = @as(u32, 0); - - while (true) : (ancestry += 1) { - var remaining_count = environment.declaration_count; - - while (remaining_count != 0) { - remaining_count -= 1; - - const declaration = &environment.declarations[remaining_count]; - - if (coral.are_equal(declaration.identifier, identifier)) { - if (ancestry != 0) { - declaration.is.captured = true; - environment = self; - ancestry -= 1; - - while (ancestry != 0) : (ancestry -= 1) { - if (environment.capture_count == environment.captures.len) { - return error.OutOfMemory; - } - - environment.captures[environment.capture_count] = .{ - .capture_index = environment.enclosing.?.capture_count - }; - - environment.capture_count += 1; - environment = environment.enclosing.?; - } - - environment.captures[environment.capture_count] = .{.declaration_index = remaining_count}; - environment.capture_count += 1; - } - - return declaration; - } - } - - environment = environment.enclosing orelse return null; - } - } - - pub fn get_captures(self: *const Environment) []const Capture { - return self.captures[0 .. self.capture_count]; - } - - pub fn get_declarations(self: *const Environment) []const Declaration { - return self.declarations[0 .. self.declaration_count]; - } -}; - -pub fn NodeChain(comptime Value: type) type { - return struct { - head: ?*Value = null, - tail: ?*Value = null, - - pub const Nodes = struct { - current: ?*const Value, - - pub fn next(self: *Nodes) ?*const Value { - const current = self.current orelse return null; - - defer self.current = current.next; - - return current; - } - }; - - const Self = @This(); - - pub fn append(self: *Self, value: *Value) void { - if (self.tail) |node| { - node.next = value; - self.tail = value; - } else { - self.tail = value; - self.head = value; - } - } - - pub fn nodes(self: *const Self) Nodes { - return .{.current = self.head}; - } - }; -} - -pub const ParseError = std.mem.Allocator.Error || error { - BadSyntax, -}; - -pub const Root = struct { - arena: std.heap.ArenaAllocator, - environment: Environment, - error_messages: MessageList, - - const MessageList = coral.Stack([]coral.Byte); - - pub fn report_error(self: *Root, line: tokens.Line, comptime format: []const u8, args: anytype) ParseError { - const allocator = self.arena.allocator(); - const message = try coral.utf8.alloc_formatted(allocator, format, args); - - defer allocator.free(message); - - try self.error_messages.push(try coral.utf8.alloc_formatted(allocator, "{line_number}: {message}", .{ - .message = message, - .line_number = line.number, - })); - - return error.BadSyntax; - } - - pub fn report_declare_error(self: *Root, line: tokens.Line, identifier: []const u8, @"error": Environment.DeclareError) ParseError { - return switch (@"error") { - error.OutOfMemory => error.OutOfMemory, - - error.DeclarationExists => self.report_error(line, "declaration `{identifier}` already exists", .{ - .identifier = identifier, - }), - }; - } - - pub fn create_node(self: *Root, node: anytype) std.mem.Allocator.Error!*@TypeOf(node) { - const copy = try self.arena.allocator().create(@TypeOf(node)); - - copy.* = node; - - return copy; - } - - pub fn create_string(self: *Root, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![]const u8 { - return coral.utf8.alloc_formatted(self.arena.allocator(), format, args); - } - - pub fn deinit(self: *Root) void { - self.error_messages.deinit(); - self.arena.deinit(); - } - - pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Root { - return .{ - .arena = std.heap.ArenaAllocator.init(allocator), - .error_messages = .{.allocator = allocator}, - .environment = .{}, - }; - } - - pub fn parse(self: *Root, stream: *tokens.Stream) ParseError!void { - stream.skip_newlines(); - - const first_statement = try Stmt.parse(self, stream, &self.environment); - var current_statement = first_statement; - - while (stream.token != .end) { - const next_statement = try Stmt.parse(self, stream, &self.environment); - - current_statement.next = next_statement; - current_statement = next_statement; - } - - self.environment.statement = first_statement; - } -}; - diff --git a/src/coral/script/tree/Expr.zig b/src/coral/script/tree/Expr.zig deleted file mode 100644 index 9f5fcb8..0000000 --- a/src/coral/script/tree/Expr.zig +++ /dev/null @@ -1,906 +0,0 @@ -const Stmt = @import("./Stmt.zig"); - -const coral = @import("coral"); - -const tokens = @import("../tokens.zig"); - -const tree = @import("../tree.zig"); - -const std = @import("std"); - -next: ?*const Self = null, -line: tokens.Line, -kind: Kind, - -pub const BinaryOp = struct { - rhs_operand: *Self, - lhs_operand: *Self, - operation: Operation, - - pub const Operation = enum { - addition, - subtraction, - multiplication, - divsion, - equals_comparison, - greater_than_comparison, - greater_equals_comparison, - less_than_comparison, - less_equals_comparison, - }; - - fn parser(comptime parse_next: Parser, comptime operations: []const BinaryOp.Operation) Parser { - const BinaryOpParser = struct { - fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - var expression = try parse_next(root, stream, environment); - - inline for (operations) |operation| { - const token = comptime @as(tokens.Token, switch (operation) { - .addition => .symbol_plus, - .subtraction => .symbol_minus, - .multiplication => .symbol_asterisk, - .divsion => .symbol_forward_slash, - .equals_comparison => .symbol_double_equals, - .greater_than_comparison => .symbol_greater_than, - .greater_equals_comparison => .symbol_greater_equals, - .less_than_comparison => .symbol_less_than, - .less_equals_comparison => .symbol_less_equals, - }); - - if (stream.token == std.meta.activeTag(token)) { - stream.step(); - - if (stream.token == .end) { - return root.report_error(stream.line, "expected other half of expression after `" ++ comptime token.text() ++ "`", .{}); - } - - // TODO: Remove once Zig has fixed struct self-reassignment. - const unnecessary_temp = expression; - - expression = try root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .binary_op = .{ - .rhs_operand = try parse_next(root, stream, environment), - .operation = operation, - .lhs_operand = unnecessary_temp, - }, - }, - }); - } - } - - return expression; - } - }; - - return BinaryOpParser.parse; - } -}; - -pub const DeclarationGet = struct { - declaration: *const tree.Declaration, -}; - -pub const DeclarationSet = struct { - declaration: *const tree.Declaration, - assign: *const Self, -}; - -pub const FieldGet = struct { - identifier: []const coral.Byte, - object: *const Self, -}; - -pub const FieldSet = struct { - identifier: []const coral.Byte, - object: *const Self, - assign: *const Self, -}; - -pub const Invoke = struct { - argument: ?*const Self, - object: *const Self, -}; - -const Kind = union (enum) { - nil_literal, - true_literal, - false_literal, - number_literal: []const u8, - string_literal: []const u8, - string_template, - symbol_literal: []const u8, - vector2: Vector2, - vector3: Vector3, - table: tree.NodeChain(TableEntry), - group: *Self, - lambda_construct: LambdaConstruct, - declaration_get: DeclarationGet, - declaration_set: DeclarationSet, - field_get: FieldGet, - field_set: FieldSet, - external_get: ExternalGet, - subscript_get: SubscriptGet, - subscript_set: SubscriptSet, - binary_op: BinaryOp, - unary_op: UnaryOp, - invoke: Invoke, -}; - -pub const LambdaConstruct = struct { - environment: *const tree.Environment, -}; - -const Parser = fn (root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self; - -const ExternalGet = struct { - name: []const u8, -}; - -const Self = @This(); - -pub const SubscriptGet = struct { - index: *const Self, - object: *const Self, -}; - -pub const SubscriptSet = struct { - index: *const Self, - object: *const Self, - assign: *const Self, -}; - -pub const TableEntry = struct { - next: ?*const TableEntry = null, - key: *const Self, - value: *const Self, -}; - -const TemplateToken = union (enum) { - invalid: []const coral.Byte, - literal: []const coral.Byte, - expression: []const coral.Byte, - - fn extract(source: *[]const coral.Byte) ?TemplateToken { - var cursor = @as(usize, 0); - - defer source.* = source.*[cursor ..]; - - while (cursor < source.len) { - switch (source.*[cursor]) { - '{' => { - cursor += 1; - - while (true) : (cursor += 1) { - if (cursor == source.len) { - return .{.invalid = source.*[0 .. cursor]}; - } - - if (source.*[cursor] == '}') { - const token = TemplateToken{.expression = source.*[1 .. cursor]}; - - cursor += 1; - - return token; - } - } - }, - - else => { - cursor += 1; - - while (true) : (cursor += 1) { - if (cursor == source.len) { - return .{.literal = source.*[0 .. cursor]}; - } - - if (source.*[cursor] == '{') { - const cursor_next = cursor + 1; - - if (cursor_next == source.len) { - return .{.invalid = source.*[0 .. cursor]}; - } - - if (source.*[cursor_next] == '{') { - cursor = cursor_next; - - return .{.literal = source.*[0 .. cursor]}; - } - - return .{.literal = source.*[0 .. cursor]}; - } - } - } - } - } - - return null; - } -}; - -pub const UnaryOp = struct { - operand: *Self, - operation: Operation, - - pub const Operation = enum { - numeric_negation, - boolean_negation, - }; -}; - -pub const Vector2 = struct { - x: *const Self, - y: *const Self, -}; - -pub const Vector3 = struct { - x: *const Self, - y: *const Self, - z: *const Self, -}; - -pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - const expression = try parse_additive(root, stream, environment); - - if (stream.token == .symbol_equals) { - stream.skip_newlines(); - - if (stream.token == .end) { - return root.report_error(stream.line, "expected assignment after `=`", .{}); - } - - return root.create_node(Self{ - .line = stream.line, - - .kind = switch (expression.kind) { - .declaration_get => |declaration_get| convert: { - if (declaration_get.declaration.is.readonly) { - return root.report_error(stream.line, "readonly declarations cannot be re-assigned", .{}); - } - - break: convert .{ - .declaration_set = .{ - .assign = try parse(root, stream, environment), - .declaration = declaration_get.declaration, - }, - }; - }, - - .field_get => |field_get| .{ - .field_set = .{ - .assign = try parse(root, stream, environment), - .object = field_get.object, - .identifier = field_get.identifier, - }, - }, - - .subscript_get => |subscript_get| .{ - .subscript_set = .{ - .assign = try parse(root, stream, environment), - .object = subscript_get.object, - .index = subscript_get.index, - }, - }, - - else => return root.report_error(stream.line, "expected local or field on left-hand side of expression", .{}), - }, - }); - } - - return expression; -} - -const parse_additive = BinaryOp.parser(parse_equality, &.{ - .addition, - .subtraction, -}); - -const parse_comparison = BinaryOp.parser(parse_term, &.{ - .greater_than_comparison, - .greater_equals_comparison, - .less_than_comparison, - .less_equals_comparison -}); - -const parse_equality = BinaryOp.parser(parse_comparison, &.{ - .equals_comparison, -}); - -fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - var expression = try parse_operand(root, stream, environment); - - while (true) { - switch (stream.token) { - .symbol_period => { - stream.skip_newlines(); - - // TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment. - const unnecessary_temp = expression; - - expression = try root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .field_get = .{ - .identifier = switch (stream.token) { - .identifier => |field_identifier| field_identifier, - else => return root.report_error(stream.line, "expected identifier after `.`", .{}), - }, - - .object = unnecessary_temp, - }, - }, - }); - - stream.skip_newlines(); - }, - - .symbol_bracket_left => { - stream.skip_newlines(); - - // TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment. - const unnecessary_temp = expression; - - expression = try root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .subscript_get = .{ - .index = try parse(root, stream, environment), - .object = unnecessary_temp, - }, - }, - }); - - if (stream.token != .symbol_bracket_right) { - return root.report_error(stream.line, "expected closing `]` on subscript", .{}); - } - - stream.skip_newlines(); - }, - - .symbol_paren_left => { - const lines_stepped = stream.line; - - stream.skip_newlines(); - - var first_argument = @as(?*Self, null); - - if (stream.token != .symbol_paren_right) { - var argument = try parse(root, stream, environment); - - first_argument = argument; - - while (true) { - switch (stream.token) { - .symbol_comma => stream.skip_newlines(), - .symbol_paren_right => break, - else => return root.report_error(stream.line, "expected `,` or `)` after lambda argument", .{}), - } - - const next_argument = try parse(root, stream, environment); - - argument.next = next_argument; - argument = next_argument; - } - } - - stream.skip_newlines(); - - // TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment. - const unnecessary_temp = expression; - - expression = try root.create_node(Self{ - .line = lines_stepped, - - .kind = .{ - .invoke = .{ - .argument = first_argument, - .object = unnecessary_temp, - }, - }, - }); - }, - - else => break, - } - } - - return expression; -} - -fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - switch (stream.token) { - .symbol_paren_left => { - stream.skip_newlines(); - - const expression = try parse(root, stream, environment); - - if (stream.token != .symbol_paren_right) { - return root.report_error(stream.line, "expected a closing `)` after expression", .{}); - } - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.group = expression}, - }); - }, - - .keyword_nil => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .nil_literal, - }); - }, - - .keyword_true => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .true_literal, - }); - }, - - .keyword_false => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .false_literal, - }); - }, - - .keyword_vec2 => { - stream.skip_newlines(); - - if (stream.token != .symbol_paren_left) { - return root.report_error(stream.line, "expected an opening `(` after `vec2`", .{}); - } - - stream.skip_newlines(); - - const x_expression = try parse(root, stream, environment); - - switch (stream.token) { - .symbol_paren_right => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .vector2 = .{ - .x = x_expression, - .y = x_expression, - }, - }, - }); - }, - - .symbol_comma => { - stream.skip_newlines(); - - const y_expression = try parse(root, stream, environment); - - stream.skip_newlines(); - - if (stream.token != .symbol_paren_right) { - return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}); - } - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .vector2 = .{ - .x = x_expression, - .y = y_expression, - }, - }, - }); - }, - - else => return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}), - } - }, - - .keyword_vec3 => { - stream.skip_newlines(); - - if (stream.token != .symbol_paren_left) { - return root.report_error(stream.line, "expected an opening `(` after `vec2`", .{}); - } - - stream.skip_newlines(); - - const x_expression = try parse(root, stream, environment); - - switch (stream.token) { - .symbol_paren_right => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .vector3 = .{ - .x = x_expression, - .y = x_expression, - .z = x_expression, - }, - }, - }); - }, - - .symbol_comma => { - stream.skip_newlines(); - - const y_expression = try parse(root, stream, environment); - - stream.skip_newlines(); - - const z_expression = try parse(root, stream, environment); - - stream.skip_newlines(); - - if (stream.token != .symbol_paren_right) { - return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}); - } - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .vector3 = .{ - .x = x_expression, - .y = y_expression, - .z = z_expression, - }, - }, - }); - }, - - else => return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}), - } - }, - - .number => |value| { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.number_literal = value}, - }); - }, - - .string => |value| { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.string_literal = value}, - }); - }, - - .template_string => |value| { - const line = stream.line; - - stream.skip_newlines(); - - return parse_template(root, value, line, environment); - }, - - .symbol_at => { - stream.step(); - - const identifier = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "expected identifier after `@`", .{}), - }; - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.external_get = .{.name = identifier}}, - }); - }, - - .symbol_period => { - stream.step(); - - const identifier = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "expected identifier after `.`", .{}), - }; - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.symbol_literal = identifier}, - }); - }, - - .identifier => |identifier| { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .declaration_get = .{ - .declaration = (try environment.resolve_declaration(identifier)) orelse { - return root.report_error(stream.line, "undefined identifier `{identifier}`", .{ - .identifier = identifier, - }); - } - }, - }, - }); - }, - - .keyword_lambda => { - stream.skip_newlines(); - - if (stream.token != .symbol_paren_left) { - return root.report_error(stream.line, "expected `(` after opening lambda block", .{}); - } - - stream.skip_newlines(); - - var lambda_environment = try environment.create_enclosed(root); - - while (stream.token != .symbol_paren_right) { - const identifier = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "expected identifier", .{}), - }; - - _ = lambda_environment.declare_argument(identifier) catch |declare_error| { - return root.report_declare_error(stream.line, identifier, declare_error); - }; - - stream.skip_newlines(); - - switch (stream.token) { - .symbol_comma => stream.skip_newlines(), - .symbol_paren_right => break, - else => return root.report_error(stream.line, "expected `,` or `)` after identifier", .{}), - } - } - - stream.skip_newlines(); - - if (stream.token != .symbol_colon) { - return root.report_error(stream.line, "expected `:` after closing `)` of lambda identifiers", .{}); - } - - stream.skip_newlines(); - - if (stream.token != .keyword_end) { - const first_statement = try Stmt.parse(root, stream, lambda_environment); - var current_statement = first_statement; - - while (stream.token != .keyword_end) { - const next_statement = try Stmt.parse(root, stream, lambda_environment); - - current_statement.next = next_statement; - current_statement = next_statement; - } - - lambda_environment.statement = first_statement; - } - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.lambda_construct = .{.environment = lambda_environment}}, - }); - }, - - .symbol_brace_left => { - stream.skip_newlines(); - - return parse_table(root, stream, environment); - }, - - .symbol_minus => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .unary_op = .{ - .operand = try parse_factor(root, stream, environment), - .operation = .numeric_negation, - }, - }, - }); - }, - - .symbol_bang => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .unary_op = .{ - .operand = try parse_factor(root, stream, environment), - .operation = .boolean_negation, - }, - }, - }); - }, - - else => return root.report_error(stream.line, "unexpected token in expression", .{}), - } -} - -fn parse_table(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - var entries = tree.NodeChain(TableEntry){}; - var sequential_index = @as(usize, 0); - - while (true) { - switch (stream.token) { - .symbol_brace_right => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.table = entries}, - }); - }, - - .symbol_bracket_left => { - stream.skip_newlines(); - - const key = try parse(root, stream, environment); - - if (stream.token != .symbol_bracket_right) { - return root.report_error(stream.line, "expected `]` after subscript index expression", .{}); - } - - stream.skip_newlines(); - - if (stream.token != .symbol_equals) { - return root.report_error(stream.line, "expected `=` after table expression key", .{}); - } - - stream.skip_newlines(); - - entries.append(try root.create_node(TableEntry{ - .value = try parse(root, stream, environment), - .key = key, - })); - }, - - .symbol_period => { - stream.step(); - - const field = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "invalid symbol literal", .{}), - }; - - stream.skip_newlines(); - - switch (stream.token) { - .symbol_comma => { - stream.skip_newlines(); - - entries.append(try root.create_node(TableEntry{ - .key = try root.create_node(Self{ - .line = stream.line, - .kind = .{.number_literal = try root.create_string("{i}", .{.i = sequential_index})}, - }), - - .value = try root.create_node(Self{ - .line = stream.line, - .kind = .{.symbol_literal = field}, - }), - })); - - sequential_index += 1; - }, - - .symbol_equals => { - stream.skip_newlines(); - - entries.append(try root.create_node(TableEntry{ - .value = try parse(root, stream, environment), - - .key = try root.create_node(Self{ - .line = stream.line, - .kind = .{.symbol_literal = field}, - }), - })); - }, - - else => return root.report_error(stream.line, "expected `,` or `=` after symbol", .{}), - } - }, - - else => { - entries.append(try root.create_node(TableEntry{ - .value = try parse(root, stream, environment), - - .key = try root.create_node(Self{ - .line = stream.line, - .kind = .{.number_literal = try root.create_string("{i}", .{.i = sequential_index})}, - }), - })); - - sequential_index += 1; - }, - } - - switch (stream.token) { - .symbol_brace_right => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.table = entries}, - }); - }, - - .symbol_comma => stream.skip_newlines(), - else => return root.report_error(stream.line, "expected `,` or '}' after table key value pair", .{}), - } - } -} - -fn parse_template(root: *tree.Root, template: []const coral.Byte, line: tokens.Line, environment: *tree.Environment) tree.ParseError!*Self { - const expression_head = try root.create_node(Self{ - .line = line, - .kind = .string_template, - }); - - var expression_tail = expression_head; - var source = template; - - while (TemplateToken.extract(&source)) |token| { - const expression = try switch (token) { - .invalid => |invalid| root.report_error(line, "invalid template format: `{invalid}`", .{ - .invalid = invalid, - }), - - .literal => |literal| root.create_node(Self{ - .line = line, - .kind = .{.string_literal = literal}, - }), - - .expression => |expression| create: { - var stream = tokens.Stream{ - .source = expression, - .line = line, - }; - - stream.step(); - - break: create try parse(root, &stream, environment); - }, - }; - - expression_tail.next = expression; - expression_tail = expression; - } - - return expression_head; -} - -const parse_term = BinaryOp.parser(parse_factor, &.{ - .multiplication, - .divsion, -}); diff --git a/src/coral/script/tree/Stmt.zig b/src/coral/script/tree/Stmt.zig deleted file mode 100644 index d507400..0000000 --- a/src/coral/script/tree/Stmt.zig +++ /dev/null @@ -1,242 +0,0 @@ -const Expr = @import("./Expr.zig"); - -const coral = @import("coral"); - -const tokens = @import("../tokens.zig"); - -const tree = @import("../tree.zig"); - -next: ?*const Self = null, -line: tokens.Line, - -kind: union (enum) { - top_expression: *const Expr, - @"return": Return, - declare: Declare, - @"if": If, - @"while": While, -}, - -pub const Declare = struct { - declaration: *const tree.Declaration, - initial_expression: *const Expr, -}; - -pub const If = struct { - then_expression: *const Expr, - @"then": *const Self, - @"else": ?*const Self, -}; - -pub const Return = struct { - returned_expression: ?*const Expr, -}; - -const Self = @This(); - -pub const While = struct { - loop_expression: *const Expr, - loop: *const Self, -}; - -pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - switch (stream.token) { - .keyword_return => { - stream.step(); - - if (stream.token != .end and stream.token != .newline) { - return root.create_node(Self{ - .line = stream.line, - .kind = .{.@"return" = .{.returned_expression = try Expr.parse(root, stream, environment)}}, - }); - } - - if (stream.token != .end and stream.token != .newline) { - return root.report_error(stream.line, "expected end or newline after return statement", .{}); - } - - return root.create_node(Self{ - .line = stream.line, - .kind = .{.@"return" = .{.returned_expression = null}}, - }); - }, - - .keyword_while => { - defer stream.skip_newlines(); - - stream.step(); - - const condition_expression = try Expr.parse(root, stream, environment); - - if (stream.token != .symbol_colon) { - return root.report_error(stream.line, "expected `:` after `while` statement", .{}); - } - - stream.skip_newlines(); - - const first_statement = try parse(root, stream, environment); - - { - var current_statement = first_statement; - - while (stream.token != .keyword_end) { - const next_statement = try parse(root, stream, environment); - - current_statement.next = next_statement; - current_statement = next_statement; - } - } - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .@"while" = .{ - .loop = first_statement, - .loop_expression = condition_expression, - }, - }, - }); - }, - - .keyword_var, .keyword_let => { - const is_constant = stream.token == .keyword_let; - - stream.skip_newlines(); - - const identifier = switch (stream.token) { - .identifier => |identifier| identifier, - else => return root.report_error(stream.line, "expected identifier after declaration", .{}), - }; - - stream.skip_newlines(); - - if (stream.token != .symbol_equals) { - return root.report_error(stream.line, "expected `=` after declaration `{identifier}`", .{ - .identifier = identifier, - }); - } - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .declare = .{ - .initial_expression = try Expr.parse(root, stream, environment), - - .declaration = declare: { - if (is_constant) { - break: declare environment.declare_constant(identifier) catch |declaration_error| { - return root.report_declare_error(stream.line, identifier, declaration_error); - }; - } - - break: declare environment.declare_variable(identifier) catch |declaration_error| { - return root.report_declare_error(stream.line, identifier, declaration_error); - }; - }, - }, - }, - }); - }, - - .keyword_if => return parse_branch(root, stream, environment), - - else => return root.create_node(Self{ - .line = stream.line, - .kind = .{.top_expression = try Expr.parse(root, stream, environment)}, - }), - } -} - -fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self { - stream.step(); - - const expression = try Expr.parse(root, stream, environment); - - if (stream.token != .symbol_colon) { - return root.report_error(stream.line, "expected `:` after `{token}`", .{.token = stream.token.text()}); - } - - stream.skip_newlines(); - - const first_then_statement = try parse(root, stream, environment); - var current_then_statement = first_then_statement; - - while (true) { - switch (stream.token) { - .keyword_end => { - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .@"if" = .{ - .then_expression = expression, - .@"then" = first_then_statement, - .@"else" = null, - }, - }, - }); - }, - - .keyword_else => { - stream.step(); - - if (stream.token != .symbol_colon) { - return root.report_error(stream.line, "expected `:` after `if` statement condition", .{}); - } - - stream.skip_newlines(); - - const first_else_statement = try parse(root, stream, environment); - var current_else_statement = first_else_statement; - - while (stream.token != .keyword_end) { - const next_statement = try parse(root, stream, environment); - - current_else_statement.next = next_statement; - current_else_statement = next_statement; - } - - stream.skip_newlines(); - - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .@"if" = .{ - .@"else" = first_else_statement, - .@"then" = first_then_statement, - .then_expression = expression, - }, - } - }); - }, - - .keyword_elif => { - return root.create_node(Self{ - .line = stream.line, - - .kind = .{ - .@"if" = .{ - .@"else" = try parse_branch(root, stream, environment), - .@"then" = first_then_statement, - .then_expression = expression, - }, - }, - }); - }, - - else => { - const next_statement = try parse(root, stream, environment); - - current_then_statement.next = next_statement; - current_then_statement = next_statement; - }, - } - } -} diff --git a/src/coral/slices.zig b/src/coral/slices.zig index 5a0953f..2a4f80a 100644 --- a/src/coral/slices.zig +++ b/src/coral/slices.zig @@ -115,24 +115,6 @@ pub fn get_ptr(slice: anytype, index: usize) ?ElementPtr(@TypeOf(slice)) { return &slice[index]; } -pub fn index_of(haystack: anytype, offset: usize, needle: std.meta.Child(@TypeOf(haystack))) ?usize { - for (offset .. haystack.len) |i| { - if (haystack[i] == needle) { - return i; - } - } - - return null; -} - -pub fn index_of_any(haystack: anytype, offset: usize, needle: []const std.meta.Child(@TypeOf(haystack))) ?usize { - return std.mem.indexOfAnyPos(std.meta.Child(@TypeOf(haystack)), haystack, offset, needle); -} - -pub fn index_of_seq(haystack: anytype, offset: usize, needle: []const std.meta.Child(@TypeOf(haystack))) ?usize { - return std.mem.indexOfPos(std.meta.Child(@TypeOf(haystack)), haystack, offset, needle); -} - 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); diff --git a/src/coral/slots.zig b/src/coral/slots.zig deleted file mode 100644 index 43adc7d..0000000 --- a/src/coral/slots.zig +++ /dev/null @@ -1,23 +0,0 @@ -const slices = @import("./slices.zig"); - -const std = @import("std"); - -pub fn Parallel(comptime Value: type) type { - const Slices = slices.Parallel(Value); - const alignment = @alignOf(Value); - - return struct { - allocator: std.mem.Allocator, - slices: slices.Parallel(Value) = .{}, - - const Self = @This(); - - pub fn len(self: Self) usize { - return self.slices.len; - } - - pub fn values(self: *Self, comptime field: Slices.Field) []align (alignment) Slices.Element(field) { - return self.slices.slice(field); - } - }; -} diff --git a/src/coral/utf8.zig b/src/coral/utf8.zig index 2d02b5c..44000e7 100644 --- a/src/coral/utf8.zig +++ b/src/coral/utf8.zig @@ -6,28 +6,64 @@ const io = @import("./io.zig"); const std = @import("std"); -pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![]coral.Byte { - var buffer = coral.Stack(coral.Byte){.allocator = allocator}; +pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![:0]u8 { const formatted_len = count_formatted(format, args); + const buffer = try allocator.allocSentinel(u8, formatted_len, 0); - try buffer.grow(formatted_len); + errdefer allocator.free(buffer); - errdefer buffer.deinit(); - - print_formatted(buffer.writer(), format, args) catch unreachable; - - return buffer.to_allocation(formatted_len, 0); + // TODO: This is dumb. + return @constCast(print_formatted(buffer, format, args) catch unreachable); } -fn count_formatted(comptime format: []const u8, args: anytype) usize { - var count = io.defaultWritable{}; +pub fn count_formatted(comptime format: []const u8, args: anytype) usize { + var count = io.NullWritable{}; - print_formatted(count.writer(), format, args) catch unreachable; + write_formatted(count.writer(), format, args) catch unreachable; return count.written; } -pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.PrintError!void { +pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]const u8 { + const Seekable = struct { + buffer: []coral.io.Byte, + cursor: usize, + + const Self = @This(); + + fn write(self: *Self, input: []const coral.io.Byte) io.Error!usize { + const range = @min(input.len, self.buffer.len - self.cursor); + const tail = self.cursor + range; + + @memcpy(self.buffer[self.cursor .. tail], input); + + self.cursor = tail; + + return range; + } + }; + + const len = count_formatted(format, args); + + if (len > buffer.len) { + return error.UnavailableResource; + } + + var seekable = Seekable{ + .buffer = buffer, + .cursor = 0, + }; + + try write_formatted(coral.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args); + + if (buffer.len < len) { + buffer[len] = 0; + } + + return buffer[0 .. len:0]; +} + +pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.Error!void { switch (@typeInfo(@TypeOf(args))) { .Struct => |arguments_struct| { comptime var arg_index = 0; @@ -67,7 +103,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: any @compileError("format specifiers cannot be named when using a tuple struct"); } - try io.print(writer, format[head .. (tail - 1)]); + try io.write_all(writer, format[head .. (tail - 1)]); head = tail; tail += 1; @@ -93,25 +129,25 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: any } } - try io.print(writer, format[head .. ]); + try 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.PrintError!void { +noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void { const Value = @TypeOf(value); return switch (@typeInfo(Value)) { - .Int => ascii.DecimalFormat.default.print(writer, value), - .Float => ascii.DecimalFormat.default.print(writer, value), + .Int => ascii.DecimalFormat.default.format(writer, value), + .Float => ascii.DecimalFormat.default.format(writer, value), .Enum => io.print(writer, @tagName(value)), .Pointer => |pointer| switch (pointer.size) { - .Many, .C => ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), - .One => if (pointer.child == []const u8) io.print(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), - .Slice => if (pointer.child == u8) io.print(writer, value) else @compileError(unformattableMessage(Value)), + .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)), }, else => @compileError(unformattableMessage(Value)), diff --git a/src/coral/World.zig b/src/flow/World.zig similarity index 52% rename from src/coral/World.zig rename to src/flow/World.zig index e7329ac..9041cc9 100644 --- a/src/coral/World.zig +++ b/src/flow/World.zig @@ -1,34 +1,27 @@ const builtin = @import("builtin"); -const heap = @import("./heap.zig"); +const flow = @import("./flow.zig"); -const map = @import("./map.zig"); +const coral = @import("coral"); -const resource = @import("./resource.zig"); +const states = @import("./states.zig"); const std = @import("std"); -const stack = @import("./stack.zig"); - const system = @import("./system.zig"); thread_pool: ?*std.Thread.Pool = null, -thread_restricted_resources: [std.enums.values(ThreadRestriction).len]resource.Table, -event_systems: stack.Sequential(system.Schedule), +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 const ThreadRestriction = enum { - none, - main, -}; - pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event { var systems = try system.Schedule.init(label); - errdefer systems.deinit(); + errdefer systems.deinit(self); const index = self.event_systems.len(); @@ -38,17 +31,17 @@ pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Even } pub fn deinit(self: *Self) void { + for (self.event_systems.values) |*schedule| { + schedule.deinit(self); + } + for (&self.thread_restricted_resources) |*resources| { resources.deinit(); } - for (self.event_systems.values) |*schedule| { - schedule.deinit(); - } - if (self.thread_pool) |thread_pool| { thread_pool.deinit(); - heap.allocator.destroy(thread_pool); + coral.heap.allocator.destroy(thread_pool); } self.event_systems.deinit(); @@ -56,25 +49,33 @@ pub fn deinit(self: *Self) void { self.* = undefined; } -pub fn get_resource(self: Self, thread_restriction: ThreadRestriction, comptime Resource: type) ?*Resource { - return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(thread_restriction)].get(Resource))); +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 set_get_resource(self: *Self, thread_restriction: ThreadRestriction, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) { - return self.thread_restricted_resources[@intFromEnum(thread_restriction)].set_get(value); +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 = .{resource.Table.init(), resource.Table.init()}, - .event_systems = .{.allocator = heap.allocator}, + .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 heap.allocator.create(std.Thread.Pool); + const thread_pool = try coral.heap.allocator.create(std.Thread.Pool); try thread_pool.init(.{ - .allocator = heap.allocator, + .allocator = coral.heap.allocator, .n_jobs = thread_count, }); @@ -92,6 +93,6 @@ pub fn run_event(self: *Self, event: Event) anyerror!void { try self.event_systems.values[@intFromEnum(event)].run(self); } -pub fn set_resource(self: *Self, thread_restriction: ThreadRestriction, value: anytype) std.mem.Allocator.Error!void { - try self.thread_restricted_resources[@intFromEnum(thread_restriction)].set(value); +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/coral/dag.zig b/src/flow/dag.zig similarity index 93% rename from src/coral/dag.zig rename to src/flow/dag.zig index 5aafa3c..931a49f 100644 --- a/src/coral/dag.zig +++ b/src/flow/dag.zig @@ -1,6 +1,4 @@ -const stack = @import("./stack.zig"); - -const slices = @import("./slices.zig"); +const coral = @import("coral"); const std = @import("std"); @@ -9,9 +7,9 @@ pub fn Graph(comptime Payload: type) type { node_count: usize = 0, table: NodeTables, - const NodeTables = stack.Parallel(struct { + const NodeTables = coral.stack.Parallel(struct { payload: Payload, - edges: stack.Sequential(Node), + edges: coral.stack.Sequential(Node), is_occupied: bool = true, is_visited: bool = false, }); @@ -82,7 +80,7 @@ pub fn Graph(comptime Payload: type) type { return false; }; - if (slices.index_of(edges.values, 0, edge_node) == null) { + if (std.mem.indexOfScalar(Node, edges.values, edge_node) == null) { try edges.push_grow(edge_node); } diff --git a/src/flow/flow.zig b/src/flow/flow.zig new file mode 100644 index 0000000..c9fd36d --- /dev/null +++ b/src/flow/flow.zig @@ -0,0 +1,329 @@ +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/coral/resource.zig b/src/flow/states.zig similarity index 71% rename from src/coral/resource.zig rename to src/flow/states.zig index 617c38b..d5874d5 100644 --- a/src/coral/resource.zig +++ b/src/flow/states.zig @@ -1,12 +1,10 @@ +const coral = @import("coral"); + const std = @import("std"); -const heap = @import("./heap.zig"); - -const map = @import("./map.zig"); - pub const Table = struct { arena: std.heap.ArenaAllocator, - table: map.Hashed(TypeID, Entry, map.enum_traits(TypeID)), + table: coral.map.Hashed(TypeID, Entry, coral.map.enum_traits(TypeID)), const Entry = struct { ptr: *anyopaque, @@ -20,7 +18,7 @@ pub const Table = struct { } pub fn get(self: Table, comptime Resource: type) ?*Resource { - if (self.table.get_ptr(type_id(Resource))) |entry| { + if (self.table.get(type_id(Resource))) |entry| { return @ptrCast(@alignCast(entry.ptr)); } @@ -29,8 +27,8 @@ pub const Table = struct { pub fn init() Table { return .{ - .arena = std.heap.ArenaAllocator.init(heap.allocator), - .table = .{.allocator = heap.allocator}, + .arena = std.heap.ArenaAllocator.init(coral.heap.allocator), + .table = .{.allocator = coral.heap.allocator}, }; } @@ -44,7 +42,7 @@ pub const Table = struct { const Value = @TypeOf(value); const value_id = type_id(Value); - if (self.table.get_ptr(value_id)) |entry| { + if (self.table.get(value_id)) |entry| { @as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value; } else { const resource_allocator = self.arena.allocator(); @@ -61,6 +59,11 @@ pub const Table = struct { } }; +pub const ThreadRestriction = enum { + none, + main, +}; + pub const TypeID = enum (usize) { _ }; pub fn type_id(comptime T: type) TypeID { @@ -74,3 +77,11 @@ pub fn type_id(comptime T: type) TypeID { 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/system.zig b/src/flow/system.zig similarity index 77% rename from src/coral/system.zig rename to src/flow/system.zig index 2224690..ba93cf8 100644 --- a/src/coral/system.zig +++ b/src/flow/system.zig @@ -1,14 +1,8 @@ +const coral = @import("coral"); + const dag = @import("./dag.zig"); -const heap = @import("./heap.zig"); - -const map = @import("./map.zig"); - -const resource = @import("./resource.zig"); - -const slices = @import("./slices.zig"); - -const stack = @import("./stack.zig"); +const states = @import("./states.zig"); const std = @import("std"); @@ -21,7 +15,7 @@ pub const BindContext = struct { pub const ResourceAccess = std.meta.Tag(Schedule.ResourceAccess); - pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: resource.TypeID) bool { + pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: states.TypeID) bool { const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses; for (resource_accesses.values) |resource_access| { @@ -43,56 +37,56 @@ pub const BindContext = struct { return false; } - pub fn register_read_write_resource_access(self: BindContext, thread_restriction: World.ThreadRestriction, comptime Resource: type) std.mem.Allocator.Error!?*Resource { - const value = self.world.get_resource(thread_restriction, Resource) orelse { + pub fn register_writable_state_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*Resource { + const value = self.world.get_state(Resource) orelse { return null; }; - const id = resource.type_id(Resource); + const id = states.type_id(Resource); if (!self.accesses_resource(.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_ptr(id) orelse insert: { + 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 = heap.allocator, + .allocator = coral.heap.allocator, })); - break: insert self.systems.read_write_resource_id_nodes.get_ptr(id).?; + break: insert self.systems.read_write_resource_id_nodes.get(id).?; }; }; - if (slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) { + if (std.mem.indexOfScalar(dag.Node, read_write_resource_nodes.values, self.node) == null) { try read_write_resource_nodes.push_grow(self.node); } return value; } - pub fn register_read_only_resource_access(self: BindContext, thread_restriction: World.ThreadRestriction, comptime Resource: type) std.mem.Allocator.Error!?*const Resource { - const value = self.world.get_resource(thread_restriction, Resource) orelse { + pub fn register_readable_state_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*const Resource { + const value = self.world.get_state(Resource) orelse { return null; }; - const id = resource.type_id(Resource); + const id = states.type_id(Resource); if (!self.accesses_resource(.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_ptr(id) orelse insert: { + 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 = heap.allocator, + .allocator = coral.heap.allocator, })); - break: insert self.systems.read_only_resource_id_nodes.get_ptr(id).?; + break: insert self.systems.read_only_resource_id_nodes.get(id).?; }; }; - if (slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) { + if (std.mem.indexOfScalar(dag.Node, read_only_resource_nodes.values, self.node) == null) { try read_only_resource_nodes.push_grow(self.node); } @@ -101,16 +95,16 @@ pub const BindContext = struct { }; pub const Info = struct { - execute: *const fn ([]const *const Parameter, *const [max_parameters]?*anyopaque) anyerror!void, + execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void, parameters: [max_parameters]*const Parameter = undefined, parameter_count: u4 = 0, - thread_restriction: World.ThreadRestriction = .none, + thread_restriction: states.ThreadRestriction = .none, pub const Parameter = struct { - thread_restriction: World.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) void, + 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, }; pub fn used_parameters(self: *const Info) []const *const Parameter { @@ -128,7 +122,7 @@ pub const Schedule = struct { label: [:0]const u8, graph: Graph, arena: std.heap.ArenaAllocator, - system_id_nodes: map.Hashed(usize, NodeBundle, map.usize_traits), + system_id_nodes: coral.map.Hashed(usize, NodeBundle, coral.map.usize_traits), read_write_resource_id_nodes: ResourceNodeBundle, read_only_resource_id_nodes: ResourceNodeBundle, parallel_work_bundles: ParallelNodeBundles, @@ -148,22 +142,22 @@ pub const Schedule = struct { info: *const Info, label: [:0]u8, dependencies: []Dependency, - parameter_states: [max_parameters]?*anyopaque = [_]?*anyopaque{null} ** max_parameters, - resource_accesses: stack.Sequential(ResourceAccess), + parameter_states: [max_parameters]*anyopaque = [_]*anyopaque{undefined} ** max_parameters, + resource_accesses: coral.stack.Sequential(ResourceAccess), }); - const NodeBundle = stack.Sequential(dag.Node); + const NodeBundle = coral.stack.Sequential(dag.Node); - const ParallelNodeBundles = stack.Sequential(NodeBundle); + const ParallelNodeBundles = coral.stack.Sequential(NodeBundle); const ResourceAccess = union (enum) { - read_only: resource.TypeID, - read_write: resource.TypeID, + read_only: states.TypeID, + read_write: states.TypeID, }; - const ResourceNodeBundle = map.Hashed(resource.TypeID, NodeBundle, map.enum_traits(resource.TypeID)); + const ResourceNodeBundle = coral.map.Hashed(states.TypeID, NodeBundle, coral.map.enum_traits(states.TypeID)); - pub fn deinit(self: *Schedule) void { + pub fn deinit(self: *Schedule, world: *World) void { { var nodes = self.system_id_nodes.entries(); @@ -194,12 +188,14 @@ pub const Schedule = struct { const system = self.graph.get_ptr(node).?; for (system.info.used_parameters(), system.parameter_states[0 .. system.info.parameter_count]) |parameter, state| { - parameter.unbind(self.arena.allocator(), state); + parameter.unbind(self.arena.allocator(), state, .{ + .world = world, + }); } system.resource_accesses.deinit(); - heap.allocator.free(system.dependencies); - heap.allocator.free(system.label); + coral.heap.allocator.free(system.dependencies); + coral.heap.allocator.free(system.label); } for (self.parallel_work_bundles.values) |*bundle| { @@ -304,7 +300,7 @@ pub const Schedule = struct { continue; } - try schedule.parallel_work_bundles.push_grow(.{.allocator = heap.allocator}); + try schedule.parallel_work_bundles.push_grow(.{.allocator = coral.heap.allocator}); const bundle = schedule.parallel_work_bundles.get_ptr().?; @@ -385,21 +381,21 @@ pub const Schedule = struct { } pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule { - var arena = std.heap.ArenaAllocator.init(heap.allocator); + var arena = std.heap.ArenaAllocator.init(coral.heap.allocator); errdefer arena.deinit(); const duped_label = try arena.allocator().dupeZ(u8, label); return .{ - .graph = Graph.init(heap.allocator), + .graph = Graph.init(coral.heap.allocator), .label = duped_label, .arena = arena, - .system_id_nodes = .{.allocator = heap.allocator}, - .read_write_resource_id_nodes = .{.allocator = heap.allocator}, - .read_only_resource_id_nodes = .{.allocator = heap.allocator}, - .parallel_work_bundles = .{.allocator = heap.allocator}, - .blocking_work = .{.allocator = heap.allocator}, + .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}, }; } @@ -421,18 +417,18 @@ pub const Schedule = struct { const nodes = lazily_create: { const system_id = @intFromPtr(info); - break: lazily_create self.system_id_nodes.get_ptr(system_id) orelse insert: { + break: lazily_create self.system_id_nodes.get(system_id) orelse insert: { std.debug.assert(try self.system_id_nodes.emplace(system_id, .{ .allocator = self.system_id_nodes.allocator, })); - break: insert self.system_id_nodes.get_ptr(system_id).?; + break: insert self.system_id_nodes.get(system_id).?; }; }; const dependencies = init: { const total_run_orders = order.run_after.len + order.run_before.len; - const dependencies = try heap.allocator.alloc(Dependency, total_run_orders); + const dependencies = try coral.heap.allocator.alloc(Dependency, total_run_orders); var dependencies_written = @as(usize, 0); for (order.run_after) |after_system| { @@ -456,26 +452,26 @@ pub const Schedule = struct { break: init dependencies; }; - errdefer heap.allocator.free(dependencies); + errdefer coral.heap.allocator.free(dependencies); - const label = try heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label); + const label = try coral.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label); - errdefer heap.allocator.free(label); + errdefer coral.heap.allocator.free(label); const node = try self.graph.append(.{ .info = info, .label = label, .dependencies = dependencies, - .resource_accesses = .{.allocator = heap.allocator}, + .resource_accesses = .{.allocator = coral.heap.allocator}, }); const system = self.graph.get_ptr(node).?; errdefer { for (info.used_parameters(), system.parameter_states[0 .. info.parameter_count]) |parameter, state| { - if (state) |initialized_state| { - parameter.unbind(self.arena.allocator(), initialized_state); - } + parameter.unbind(self.arena.allocator(), state, .{ + .world = world, + }); } std.debug.assert(self.graph.remove_node(node) != null); @@ -495,4 +491,8 @@ pub const Schedule = struct { } }; +pub const UnbindContext = struct { + world: *World, +}; + pub const max_parameters = 16; diff --git a/src/main.zig b/src/main.zig index 93e26de..50a72c5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,10 +4,25 @@ 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(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator}, - quad_mesh_2d: ona.gfx.Handle = .none, - body_texture: ona.gfx.Handle = .none, + 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 { @@ -22,48 +37,90 @@ pub fn main() !void { }); } -fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void { - display.res.width, display.res.height = .{1280, 720}; - actors.res.body_texture = try assets.res.open_file(coral.files.bundle, "actor.bmp"); - actors.res.quad_mesh_2d = try assets.res.open_quad_mesh_2d(@splat(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: coral.Write(Actors)) void { +fn exit(actors: ona.Write(Actors)) void { actors.res.instances.deinit(); } -fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors)) !void { - for (actors.res.instances.values) |instance| { - try queue.commands.append(.{ - .instance_2d = .{ - .mesh_2d = actors.res.quad_mesh_2d, - .texture = actors.res.body_texture, +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, + }); - .transform = .{ - .origin = instance, - .xbasis = .{64, 0}, - .ybasis = .{0, 64}, - }, - }, - }); - } + 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: coral.Read(Player), actors: coral.Write(Actors), mapping: coral.Read(ona.act.Mapping)) !void { +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), - mapping.res.axis_strength(player.res.move_y), + mapping.res.axis_strength(player.res.move_x) * 10, + mapping.res.axis_strength(player.res.move_y) * 10, }; } -fn setup(world: *coral.World, events: ona.App.Events) !void { - try world.set_resource(.none, Actors{}); - try world.set_resource(.none, Player{}); +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, coral.system_fn(load), .{.label = "load"}); - try world.on_event(events.update, coral.system_fn(update), .{.label = "update"}); - try world.on_event(events.exit, coral.system_fn(exit), .{.label = "exit"}); - try world.on_event(events.render, coral.system_fn(render), .{.label = "render actors"}); + 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/ona/App.zig b/src/ona/App.zig index e4452da..ab428d2 100644 --- a/src/ona/App.zig +++ b/src/ona/App.zig @@ -1,17 +1,20 @@ const coral = @import("coral"); +const flow = @import("flow"); + events: *const Events, target_frame_time: f64, +elapsed_time: f64, is_running: bool, pub const Events = struct { - load: coral.World.Event, - pre_update: coral.World.Event, - update: coral.World.Event, - post_update: coral.World.Event, - render: coral.World.Event, - finish: coral.World.Event, - exit: coral.World.Event, + 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, }; const Self = @This(); diff --git a/src/ona/act.zig b/src/ona/act.zig index 293945f..f8527d9 100644 --- a/src/ona/act.zig +++ b/src/ona/act.zig @@ -2,6 +2,8 @@ const App = @import("./App.zig"); const coral = @import("coral"); +const flow = @import("flow"); + const gfx = @import("./gfx.zig"); const msg = @import("./msg.zig"); @@ -35,15 +37,15 @@ pub const Mapping = struct { } }; -pub fn setup(world: *coral.World, events: App.Events) std.mem.Allocator.Error!void { - try world.set_resource(.none, Mapping{}); +pub fn setup(world: *flow.World, events: App.Events) std.mem.Allocator.Error!void { + try world.set_state(Mapping{}); - try world.on_event(events.pre_update, coral.system_fn(update), .{ + try world.on_event(events.pre_update, flow.system_fn(update), .{ .label = "update act", }); } -pub fn update(inputs: msg.Receive(gfx.Input), mapping: coral.Write(Mapping)) void { +pub fn update(inputs: msg.Receive(gfx.Input), mapping: flow.Write(Mapping)) void { mapping.res.keys_pressed = Mapping.ScancodeSet.initEmpty(); for (inputs.messages()) |message| { diff --git a/src/ona/gfx.zig b/src/ona/gfx.zig index d998ee2..d0567c9 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -1,126 +1,298 @@ const App = @import("./App.zig"); +pub const colors = @import("./gfx/colors.zig"); + const coral = @import("coral"); -const device = @import("./gfx/device.zig"); - const ext = @import("./ext.zig"); -const formats = @import("./gfx/formats.zig"); +const flow = @import("flow"); const msg = @import("./msg.zig"); +const rendering = @import("./gfx/rendering.zig"); + const std = @import("std"); pub const Assets = struct { - context: device.Context, - formats: coral.stack.Sequential(Format), - staging_arena: std.heap.ArenaAllocator, + 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 Format = struct { - extension: []const u8, - file_desc: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) Error!Desc, + pub const LoadError = std.mem.Allocator.Error; - pub const Error = std.mem.Allocator.Error || coral.files.Error || error { - FormatUnsupported, - }; + pub const LoadFileError = LoadError || coral.files.ReadAllError || error { + FormatUnsupported, }; - pub fn open_file(self: *Assets, storage: coral.files.Storage, path: []const u8) (OpenError || Format.Error)!Handle { - defer { - const max_cache_size = 536870912; + pub const TextureFormat = struct { + extension: []const u8, + load_file: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) LoadFileError!Texture.Desc, + }; - if (!self.staging_arena.reset(.{.retain_with_limit = max_cache_size})) { - std.log.warn("failed to retain staging arena size of {} bytes", .{max_cache_size}); - } + 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(); } - for (self.formats.values) |format| { + 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.context.open(try format.file_desc(&self.staging_arena, storage, path)); + return self.load_texture(try format.load_file(&arena, storage, path)); } - return .none; + return error.FormatUnsupported; } - pub fn open_quad_mesh_2d(self: *Assets, extents: Point2D) OpenError!Handle { - const width, const height = extents / @as(Point2D, @splat(2)); - - return self.context.open(.{ - .mesh_2d = .{ - .indices = &.{0, 1, 2, 0, 2, 3}, - - .vertices = &.{ - .{.xy = .{-width, height}, .uv = .{0, 1}}, - .{.xy = .{width, height}, .uv = .{1, 1}}, - .{.xy = .{width, -height}, .uv = .{1, 0}}, - .{.xy = .{-width, -height}, .uv = .{0, 0}}, - }, - }, - }); - } + pub const thread_restriction = .main; }; pub const Color = @Vector(4, f32); -pub const Desc = union (enum) { - texture: Texture, - mesh_2d: Mesh2D, +pub const Commands = struct { + pending: *List, - pub const Mesh2D = struct { - vertices: []const Vertex, - indices: []const u16, - - pub const Vertex = struct { - xy: Point2D, - uv: Point2D, - }; + const Command = union (enum) { + draw_texture: DrawTextureCommand, + set_effect: SetEffectCommand, + set_target: SetTargetCommand, }; - pub const Texture = struct { - data: []const coral.io.Byte, - width: u16, - format: Format, - access: Access, + pub const DrawTextureCommand = struct { + texture: Texture, + transform: Transform2D, + }; - pub const Access = enum { - static, - }; + pub const SetEffectCommand = struct { + effect: Effect, + properties: []const coral.io.Byte, + }; - pub const Format = enum { - rgba8, - bgra8, + pub const SetTargetCommand = struct { + texture: Texture, + clear_color: ?Color, + clear_depth: ?f32, + clear_stencil: ?u8, + }; - pub fn byte_size(self: Format) usize { - return switch (self) { - .rgba8, .bgra8 => 4, - }; + 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 Display = struct { +pub const Config = struct { width: u16 = 1280, height: u16 = 720, clear_color: Color = colors.black, }; -pub const Handle = enum (usize) { - none, - _, - - pub fn index(self: Handle) ?usize { - return switch (self) { - .none => null, - _ => @intFromEnum(self) - 1, - }; - } -}; - pub const Input = union (enum) { key_up: Key, key_down: Key, @@ -137,85 +309,135 @@ pub const Input = union (enum) { }; }; -pub const OpenError = std.mem.Allocator.Error || error { +pub const Effect = enum (u32) { + default, + _, + pub const Desc = struct { + fragment_spirv_ops: []const u32, + }; }; -pub const Point2D = @Vector(2, f32); +pub const Texture = enum (u32) { + default, + backbuffer, + _, -pub const Queue = struct { - commands: *device.RenderList, + pub const Desc = struct { + format: Format, + access: Access, - pub const State = struct { - command_index: usize, + 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 fn bind(_: coral.system.BindContext) std.mem.Allocator.Error!State { - // TODO: Review how good of an idea this global state is, even if bind is guaranteed to always be ran on main. - if (renders.is_empty()) { - renders = .{.allocator = coral.heap.allocator}; + pub const Format = enum { + rgba8, + bgra8, + + pub fn byte_size(self: Format) usize { + return switch (self) { + .rgba8, .bgra8 => 4, + }; } - - const command_index = renders.len(); - - try renders.push_grow(device.RenderChain.init(coral.heap.allocator)); - - return .{ - .command_index = command_index, - }; - } - - pub fn init(state: *State) Queue { - return .{ - .commands = renders.values[state.command_index].pending(), - }; - } - - pub fn unbind(state: *State) void { - std.debug.assert(!renders.is_empty()); - - const render = &renders.values[state.command_index]; - - render.deinit(); - std.mem.swap(device.RenderChain, render, renders.get_ptr().?); - std.debug.assert(renders.pop()); - - if (renders.is_empty()) { - Queue.renders.deinit(); - } - } - - var renders = coral.stack.Sequential(device.RenderChain){.allocator = coral.heap.allocator}; + }; }; pub const Transform2D = extern struct { - xbasis: Point2D = .{1, 0}, - ybasis: Point2D = .{0, 1}, - origin: Point2D = @splat(0), + xbasis: Vector = .{1, 0}, + ybasis: Vector = .{0, 1}, + origin: Vector = @splat(0), + + const Vector = @Vector(2, f32); }; -const builtin_formats = [_]Assets.Format{ - .{ - .extension = "bmp", - .file_desc = formats.bmp_file_desc, - }, -}; +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; + }; -pub const colors = struct { - pub const black = greyscale(0); - - pub const white = greyscale(1); - - pub fn greyscale(v: f32) Color { - return .{v, v, v, 1}; + if (!std.mem.eql(u8, &header.type, "BM")) { + return error.FormatUnsupported; } - pub fn rgb(r: f32, g: f32, b: f32) Color { - return .{r, g, b, 1}; - } -}; + const pixel_width = std.math.cast(u16, header.pixel_width) orelse { + return error.FormatUnsupported; + }; -pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void { + 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(App), inputs: msg.Send(Input)) !void { var event = @as(ext.SDL_Event, undefined); while (ext.SDL_PollEvent(&event) != 0) { @@ -228,45 +450,87 @@ pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void { } } -pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { +pub fn setup(world: *flow.World, events: App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) { return error.Unsupported; } - var context = try device.Context.init(); + const assets = create: { + var assets = try Assets.init(); - errdefer context.deinit(); + errdefer { + assets.deinit(); + } - var registered_formats = coral.stack.Sequential(Assets.Format){.allocator = coral.heap.allocator}; + break: create try world.set_get_state(assets); + }; - errdefer registered_formats.deinit(); + assets.frame_rendered.set(); - try registered_formats.grow(builtin_formats.len); - std.debug.assert(registered_formats.push_all(&builtin_formats)); + errdefer { + assets.deinit(); + } - try world.set_resource(.none, Assets{ - .staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator), - .formats = registered_formats, - .context = context, + assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{ + &assets.pending_work, + assets.window, }); - try world.set_resource(.none, Display{}); - try world.on_event(events.pre_update, coral.system_fn(poll), .{.label = "poll gfx"}); - try world.on_event(events.exit, coral.system_fn(stop), .{.label = "stop gfx"}); - try world.on_event(events.finish, coral.system_fn(synchronize), .{.label = "synchronize gfx"}); + 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: coral.Write(Assets)) void { - assets.res.staging_arena.deinit(); - assets.res.formats.deinit(); - assets.res.context.deinit(); +pub fn stop(assets: flow.Write(Assets)) void { + assets.res.deinit(); } -pub fn synchronize(assets: coral.Write(Assets), display: coral.Read(Display)) !void { - assets.res.context.submit(.{ - .width = display.res.width, - .height = display.res.height, - .clear_color = display.res.clear_color, - .renders = Queue.renders.values, - }); +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/Resources.zig b/src/ona/gfx/Resources.zig new file mode 100644 index 0000000..b4718de --- /dev/null +++ b/src/ona/gfx/Resources.zig @@ -0,0 +1,518 @@ +const coral = @import("coral"); + +const gfx = @import("../gfx.zig"); + +const sokol = @import("sokol"); + +const spirv = @import("./spirv.zig"); + +const std = @import("std"); + +effects: EffectPool, +textures: TexturePool, + +pub const Effect = struct { + shader: sokol.gfx.Shader, + pipeline: sokol.gfx.Pipeline, + properties: []coral.io.Byte, + + pub fn deinit(self: *Effect) void { + coral.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); + + defer { + spirv_arena.deinit(); + } + + const spirv_program = try spirv.analyze(&spirv_arena, .{ + .target = try switch (sokol.gfx.queryBackend()) { + .GLCORE => spirv.Target.glsl, + else => error.InvalidSPIRV, + }, + + .vertex_source = .{ + .ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), + }, + + .fragment_source = .{ + .ops = desc.fragment_spirv_ops, + }, + }); + + const shader = sokol.gfx.makeShader(shader_desc: { + const shader_desc = sokol.gfx.ShaderDesc{ + .vs = stage_desc(spirv_program.vertex_stage), + .fs = stage_desc(spirv_program.fragment_stage), + }; + + // TODO: Vertex attributes, for some reason they aren't needed? + + break: shader_desc shader_desc; + }); + + // TODO: Review blending rules. + const pipeline = sokol.gfx.makePipeline(pipeline_desc: { + var pipeline_desc = sokol.gfx.PipelineDesc{ + .label = "Effect pipeline", + .layout = vertex_layout_state, + .shader = shader, + .index_type = .UINT16, + .blend_color = .{.r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0}, + }; + + pipeline_desc.colors[0] = .{ + .write_mask = .RGBA, + + .blend = .{ + .enabled = true, + .src_factor_rgb = .SRC_ALPHA, + .dst_factor_rgb = .ONE_MINUS_SRC_ALPHA, + }, + }; + + break: pipeline_desc pipeline_desc; + }); + + const properties = try coral.heap.allocator.alloc( + coral.io.Byte, + if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0, + ); + + errdefer { + coral.heap.allocator.free(properties); + sokol.gfx.destroyPipeline(pipeline); + sokol.gfx.destroyShader(shader); + } + + return .{ + .shader = shader, + .pipeline = pipeline, + .properties = properties, + }; + } + + fn stage_desc(spirv_stage: spirv.Stage) sokol.gfx.ShaderStageDesc { + var stage = sokol.gfx.ShaderStageDesc{ + .entry = spirv_stage.entry_point, + .source = spirv_stage.source, + }; + + for (0 .. spirv.Stage.max_uniform_blocks) |slot| { + const uniform_block = &(spirv_stage.has_uniform_blocks[slot] orelse { + continue; + }); + + const stage_uniform_block = &stage.uniform_blocks[slot]; + + stage_uniform_block.layout = switch (uniform_block.layout) { + .std140 => .STD140, + }; + + stage_uniform_block.size = uniform_block.size(); + + for (stage_uniform_block.uniforms[0 .. uniform_block.uniforms.len], uniform_block.uniforms) |*stage_uniform, uniform| { + stage_uniform.* = .{ + .type = switch (uniform.type) { + .float => .FLOAT, + .float2 => .FLOAT2, + .float3 => .FLOAT3, + .float4 => .FLOAT4, + .integer => .INT, + .integer2 => .INT2, + .integer3 => .INT3, + .integer4 => .INT4, + .matrix4 => .MAT4, + }, + + .name = uniform.name, + .array_count = uniform.len, + }; + } + } + + for (0 .. spirv.Stage.max_images) |slot| { + const image = &(spirv_stage.has_images[slot] orelse { + continue; + }); + + stage.images[slot] = .{ + .multisampled = image.is_multisampled, + .image_type = ._2D, + .sample_type = .FLOAT, + .used = true, + }; + } + + for (0 .. spirv.Stage.max_samplers) |slot| { + const sampler = &(spirv_stage.has_samplers[slot] orelse { + continue; + }); + + stage.samplers[slot] = .{ + .sampler_type = switch (sampler.*) { + .filtering => .FILTERING, + .non_filtering => .NONFILTERING, + .comparison => .COMPARISON, + }, + + .used = true, + }; + } + + for (0 .. spirv.Stage.max_image_sampler_pairs) |slot| { + const image_sampler_pair = &(spirv_stage.has_image_sampler_pairs[slot] orelse { + continue; + }); + + stage.image_sampler_pairs[slot] = .{ + .glsl_name = image_sampler_pair.name, + .image_slot = @intCast(image_sampler_pair.image_slot), + .sampler_slot = @intCast(image_sampler_pair.sampler_slot), + .used = true, + }; + } + + return stage; + } + + const vertex_layout_state = sokol.gfx.VertexLayoutState{ + .attrs = get: { + var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; + + attrs[0] = .{ + .format = .FLOAT2, + .buffer_index = 0, + }; + + attrs[1] = .{ + .format = .FLOAT2, + .buffer_index = 0, + }; + + attrs[2] = .{ + .format = .FLOAT2, + .buffer_index = 1, + }; + + attrs[3] = .{ + .format = .FLOAT2, + .buffer_index = 1, + }; + + attrs[4] = .{ + .format = .FLOAT2, + .buffer_index = 1, + }; + + attrs[5] = .{ + .format = .UBYTE4N, + .buffer_index = 1, + }; + + attrs[6] = .{ + .format = .FLOAT, + .buffer_index = 1, + }; + + attrs[7] = .{ + .format = .FLOAT4, + .buffer_index = 1, + }; + + break: get attrs; + }, + + .buffers = get: { + var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; + + vertex_buffer_layouts[1].step_func = .PER_INSTANCE; + + break: get vertex_buffer_layouts; + }, + }; +}; + +const EffectPool = coral.Pool(Effect); + +const Self = @This(); + +pub const Texture = struct { + width: u16, + height: u16, + access: Access, + + pub const Access = union (enum) { + empty, + render: RenderAccess, + static: StaticAccess, + }; + + pub const RenderAccess = struct { + color_image: sokol.gfx.Image, + depth_image: sokol.gfx.Image, + attachments: sokol.gfx.Attachments, + }; + + pub const StaticAccess = struct { + image: sokol.gfx.Image, + }; + + pub fn deinit(self: *Texture) void { + switch (self.access) { + .render => |render| { + sokol.gfx.destroyImage(render.color_image); + sokol.gfx.destroyImage(render.depth_image); + sokol.gfx.destroyAttachments(render.attachments); + }, + + .static => |static| { + sokol.gfx.destroyImage(static.image); + }, + + .empty => {}, + } + + self.* = undefined; + } + + pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture { + const pixel_format = switch (desc.format) { + .rgba8 => sokol.gfx.PixelFormat.RGBA8, + .bgra8 => sokol.gfx.PixelFormat.BGRA8, + }; + + switch (desc.access) { + .render => |render| { + if (render.width == 0 or render.height == 0) { + return .{ + .width = render.width, + .height = render.height, + .access = .empty, + }; + } + + const color_image = sokol.gfx.makeImage(.{ + .pixel_format = pixel_format, + .width = render.width, + .height = render.height, + .render_target = true, + }); + + const depth_image = sokol.gfx.makeImage(.{ + .width = render.width, + .height = render.height, + .render_target = true, + .pixel_format = .DEPTH_STENCIL, + }); + + const attachments = sokol.gfx.makeAttachments(attachments_desc: { + var attachments_desc = sokol.gfx.AttachmentsDesc{ + .depth_stencil = .{ + .image = depth_image, + }, + }; + + attachments_desc.colors[0] = .{ + .image = color_image, + }; + + break: attachments_desc attachments_desc; + }); + + return .{ + .width = render.width, + .height = render.height, + + .access = .{ + .render = .{ + .attachments = attachments, + .color_image = color_image, + .depth_image = depth_image, + }, + }, + }; + }, + + .static => |static| { + const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse { + return error.OutOfMemory; + }; + + if (static.width == 0 or height == 0) { + return .{ + .width = static.width, + .height = height, + .access = .empty, + }; + } + + const image = sokol.gfx.makeImage(image_desc: { + var image_desc = sokol.gfx.ImageDesc{ + .height = height, + .pixel_format = pixel_format, + .width = static.width, + }; + + image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data); + + break: image_desc image_desc; + }); + + errdefer { + sokol.gfx.destroyImage(image); + } + + return .{ + .width = static.width, + .height = height, + + .access = .{ + .static = .{ + .image = image, + }, + }, + }; + }, + } + } +}; + +const TexturePool = coral.Pool(Texture); + +pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect { + var effect = try Effect.init(desc); + + errdefer effect.deinit(); + + return @enumFromInt(try self.effects.insert(effect)); +} + +pub fn create_texture(self: *Self, desc: gfx.Texture.Desc) !gfx.Texture { + var texture = try Texture.init(desc); + + errdefer texture.deinit(); + + return @enumFromInt(try self.textures.insert(texture)); +} + +pub fn deinit(self: *Self) void { + var textures = self.textures.values(); + + while (textures.next()) |texture| { + texture.deinit(); + } + + self.textures.deinit(); + + var effects = self.effects.values(); + + while (effects.next()) |effect| { + effect.deinit(); + } + + self.effects.deinit(); + + self.* = undefined; +} + +pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool { + switch (handle) { + .default => {}, + + else => { + var effect = self.effects.remove(@intFromEnum(handle)) orelse { + return false; + }; + + effect.deinit(); + }, + } + + return true; +} + +pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool { + switch (handle) { + .default => {}, + + else => { + var texture = self.textures.remove(@intFromEnum(handle)) orelse { + return false; + }; + + texture.deinit(); + }, + } + + return true; +} + +pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect { + return self.effects.get(@intFromEnum(handle)); +} + +pub fn get_texture(self: *Self, handle: gfx.Texture) ?*Texture { + return self.textures.get(@intFromEnum(handle)); +} + +pub fn init() !Self { + var pools = Self{ + .effects = EffectPool.init(coral.heap.allocator), + .textures = TexturePool.init(coral.heap.allocator), + }; + + errdefer { + pools.deinit(); + } + + const assert = struct { + fn is_handle(expected: anytype, actual: @TypeOf(expected)) void { + std.debug.assert(actual == expected); + } + }; + + assert.is_handle(gfx.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(.{ + .format = .rgba8, + + .access = .{ + .static = .{ + .data = std.mem.asBytes(&[_]u32{ + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, + 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, + }), + + .width = 8, + }, + }, + })); + + assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{ + .format = .rgba8, + + .access = .{ + .render = .{ + .width = 0, + .height = 0, + }, + } + })); + + return pools; +} diff --git a/src/ona/gfx/colors.zig b/src/ona/gfx/colors.zig new file mode 100644 index 0000000..e7ee635 --- /dev/null +++ b/src/ona/gfx/colors.zig @@ -0,0 +1,13 @@ +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/ona/gfx/commands.zig b/src/ona/gfx/commands.zig deleted file mode 100644 index cece28c..0000000 --- a/src/ona/gfx/commands.zig +++ /dev/null @@ -1,80 +0,0 @@ -const coral = @import("coral"); - -const std = @import("std"); - -pub fn Chain(comptime Command: type, comptime clone_command: ?Clone(Command)) type { - return struct { - swap_lists: [2]CommandList, - swap_state: u1 = 0, - - const CommandList = List(Command, clone_command); - - const Self = @This(); - - pub fn deinit(self: *Self) void { - for (&self.swap_lists) |*list| { - list.deinit(); - } - - self.* = undefined; - } - - pub fn init(allocator: std.mem.Allocator) Self { - return .{ - .swap_lists = .{CommandList.init(allocator), CommandList.init(allocator)}, - }; - } - - pub fn pending(self: *Self) *CommandList { - return &self.swap_lists[self.swap_state]; - } - - pub fn submitted(self: *Self) *CommandList { - return &self.swap_lists[self.swap_state ^ 1]; - } - - pub fn swap(self: *Self) void { - self.swap_state ^= 1; - } - }; -} - -pub fn Clone(comptime Command: type) type { - return fn (Command, *std.heap.ArenaAllocator) std.mem.Allocator.Error!Command; -} - -pub fn List(comptime Command: type, comptime clone_command: ?Clone(Command)) type { - return struct { - arena: std.heap.ArenaAllocator, - stack: coral.stack.Sequential(Command), - - const Self = @This(); - - pub fn append(self: *Self, command: Command) std.mem.Allocator.Error!void { - return self.stack.push_grow(if (clone_command) |clone| try clone(command, &self.arena) else command); - } - - pub fn clear(self: *Self) 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", .{}); - } - } - - pub fn deinit(self: *Self) void { - self.arena.deinit(); - self.stack.deinit(); - - self.* = undefined; - } - - pub fn init(allocator: std.mem.Allocator) Self { - return .{ - .arena = std.heap.ArenaAllocator.init(allocator), - .stack = .{.allocator = allocator}, - }; - } - }; -} - diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig deleted file mode 100644 index eb2f203..0000000 --- a/src/ona/gfx/device.zig +++ /dev/null @@ -1,691 +0,0 @@ -const commands = @import("./commands.zig"); - -const coral = @import("coral"); - -const ext = @import("../ext.zig"); - -const gfx = @import("../gfx.zig"); - -const sokol = @import("sokol"); - -const std = @import("std"); - -pub const Context = struct { - window: *ext.SDL_Window, - thread: std.Thread, - loop: *Loop, - - pub const Submission = struct { - width: u16, - height: u16, - clear_color: gfx.Color = gfx.colors.black, - renders: []RenderChain, - }; - - pub fn close(self: *Context, handle: gfx.Handle) void { - const handle_index = handle.index() orelse { - return; - }; - - const close_commands = self.loop.closes.pending(); - - std.debug.assert(close_commands.stack.cap > close_commands.stack.len()); - close_commands.append(.{.index = handle_index}) catch unreachable; - } - - pub fn deinit(self: *Context) void { - self.loop.is_running.store(false, .monotonic); - self.loop.ready.post(); - self.thread.join(); - self.loop.deinit(); - coral.heap.allocator.destroy(self.loop); - ext.SDL_DestroyWindow(self.window); - - self.* = undefined; - } - - pub fn init() !Context { - 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); - - const loop = try coral.heap.allocator.create(Loop); - - errdefer coral.heap.allocator.destroy(loop); - - loop.* = .{}; - - return .{ - .loop = loop, - .window = window, - - .thread = spawn: { - const thread = try std.Thread.spawn(.{}, Loop.run, .{loop, window}); - - thread.setName("Ona Graphics") catch { - std.log.warn("failed to name the graphics thread", .{}); - }; - - break: spawn thread; - }, - }; - } - - pub fn open(self: *Context, desc: gfx.Desc) gfx.OpenError!gfx.Handle { - const open_commands = self.loop.opens.pending(); - const index = self.loop.closed_indices.get() orelse open_commands.stack.len(); - - try open_commands.append(.{ - .index = index, - .desc = desc, - }); - - const pending_closes = self.loop.closes.pending(); - - if (pending_closes.stack.len() == pending_closes.stack.cap) { - try pending_closes.stack.grow(1); - } - - _ = self.loop.closed_indices.pop(); - - return @enumFromInt(index + 1); - } - - pub fn submit(self: *Context, submission: Submission) void { - self.loop.finished.wait(); - - defer self.loop.ready.post(); - - for (submission.renders) |*render| { - render.swap(); - } - - self.loop.opens.swap(); - self.loop.closes.swap(); - - var last_width, var last_height = [_]c_int{0, 0}; - - ext.SDL_GetWindowSize(self.window, &last_width, &last_height); - - if (submission.width != last_width or submission.height != last_height) { - ext.SDL_SetWindowSize(self.window, submission.width, submission.height); - } - - self.loop.clear_color = submission.clear_color; - self.loop.renders = submission.renders; - - self.loop.ready.post(); - } -}; - -const Loop = struct { - ready: std.Thread.Semaphore = .{}, - finished: std.Thread.Semaphore = .{}, - clear_color: gfx.Color = gfx.colors.black, - is_running: AtomicBool = AtomicBool.init(true), - renders: []RenderChain = &.{}, - closes: CloseChain = CloseChain.init(coral.heap.allocator), - opens: OpenChain = OpenChain.init(coral.heap.allocator), - closed_indices: coral.stack.Sequential(usize) = .{.allocator = coral.heap.allocator}, - - const AtomicBool = std.atomic.Value(bool); - - const CloseCommand = struct { - index: usize, - }; - - const OpenCommand = struct { - index: usize, - desc: gfx.Desc, - - fn clone(command: OpenCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!OpenCommand { - const allocator = arena.allocator(); - - return .{ - .desc = switch (command.desc) { - .texture => |texture| .{ - .texture = .{ - .data = try allocator.dupe(coral.io.Byte, texture.data), - .width = texture.width, - .format = texture.format, - .access = texture.access, - }, - }, - - .mesh_2d => |mesh_2d| .{ - .mesh_2d = .{ - .vertices = try allocator.dupe(gfx.Desc.Mesh2D.Vertex, mesh_2d.vertices), - .indices = try allocator.dupe(u16, mesh_2d.indices), - }, - }, - }, - - .index = command.index, - }; - } - }; - - const CloseChain = commands.Chain(CloseCommand, null); - - const OpenChain = commands.Chain(OpenCommand, OpenCommand.clone); - - fn deinit(self: *Loop) void { - self.closes.deinit(); - self.opens.deinit(); - self.closed_indices.deinit(); - } - - fn run(self: *Loop, window: *ext.SDL_Window) !void { - const context = configure_and_create: { - var result = @as(c_int, 0); - - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE); - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3); - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3); - result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1); - - if (result != 0) { - return error.Unsupported; - } - - break: configure_and_create ext.SDL_GL_CreateContext(window); - }; - - sokol.gfx.setup(.{ - .environment = .{ - .defaults = .{ - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .sample_count = 1, - }, - }, - - .logger = .{ - .func = sokol.log.func, - }, - }); - - defer { - sokol.gfx.shutdown(); - ext.SDL_GL_DeleteContext(context); - } - - var rendering = Rendering.init(); - - defer rendering.deinit(); - - self.finished.post(); - - while (self.is_running.load(.monotonic)) { - self.ready.wait(); - - defer self.finished.post(); - - const open_commands = self.opens.submitted(); - - defer open_commands.clear(); - - for (open_commands.stack.values) |command| { - switch (command.desc) { - .texture => |texture| { - const stride = texture.width * texture.format.byte_size(); - - const image = sokol.gfx.makeImage(.{ - .width = texture.width, - .height = @intCast(texture.data.len / stride), - - .data = .{ - .subimage = get: { - var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6; - - subimage[0][0] = sokol.gfx.asRange(texture.data); - - break: get subimage; - }, - }, - - .pixel_format = switch (texture.format) { - .rgba8 => .RGBA8, - .bgra8 => .BGRA8, - }, - }); - - errdefer sokol.gfx.destroyImage(image); - - const sampler = sokol.gfx.makeSampler(.{}); - - errdefer sokol.gfx.destroySampler(sampler); - - try rendering.insert_object(command.index, .{ - .texture = .{ - .sampler = sampler, - .image = image, - }, - }); - }, - - .mesh_2d => |mesh_2d| { - const index_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(mesh_2d.indices), - .type = .INDEXBUFFER, - }); - - const vertex_buffer = sokol.gfx.makeBuffer(.{ - .data = sokol.gfx.asRange(mesh_2d.vertices), - .type = .VERTEXBUFFER, - }); - - errdefer { - sokol.gfx.destroyBuffer(index_buffer); - sokol.gfx.destroyBuffer(vertex_buffer); - } - - if (mesh_2d.indices.len > std.math.maxInt(u32)) { - return error.OutOfMemory; - } - - try rendering.insert_object(command.index, .{ - .mesh_2d = .{ - .index_buffer = index_buffer, - .vertex_buffer = vertex_buffer, - .index_count = @intCast(mesh_2d.indices.len), - }, - }); - }, - } - } - - var frame = init_frame: { - var width, var height = [_]c_int{0, 0}; - - ext.SDL_GL_GetDrawableSize(window, &width, &height); - std.debug.assert(width > 0 and height > 0); - - break: init_frame Rendering.Frame{ - .width = @intCast(width), - .height = @intCast(height), - }; - }; - - sokol.gfx.beginPass(.{ - .swapchain = .{ - .width = frame.width, - .height = frame.height, - .sample_count = 1, - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .gl = .{.framebuffer = 0}, - }, - - .action = .{ - .colors = get: { - var actions = [_]sokol.gfx.ColorAttachmentAction{.{}} ** 4; - - actions[0] = .{ - .load_action = .CLEAR, - .clear_value = @as(sokol.gfx.Color, @bitCast(self.clear_color)), - }; - - break: get actions; - }, - }, - }); - - for (self.renders) |*render| { - const render_commands = render.submitted(); - - defer render_commands.clear(); - - for (render_commands.stack.values) |command| { - switch (command) { - .instance_2d => |instance_2d| { - try rendering.push_instance_2d(&frame, instance_2d); - }, - - .post_process => |post_process| { - rendering.flush_instance_2ds(&frame); - // sokol.gfx.applyPipeline(self.post_process_pipeline); - - _ = post_process; - }, - } - } - } - - rendering.flush_instance_2ds(&frame); - sokol.gfx.endPass(); - sokol.gfx.commit(); - ext.SDL_GL_SwapWindow(window); - - const close_commands = self.closes.submitted(); - - defer close_commands.clear(); - - for (close_commands.stack.values) |command| { - const object = &rendering.objects.values[command.index]; - - switch (object.*) { - .empty => {}, // TODO: Handle double-closes. - - .mesh_2d => |mesh_2d| { - sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer); - sokol.gfx.destroyBuffer(mesh_2d.index_buffer); - }, - - .texture => |texture| { - sokol.gfx.destroyImage(texture.image); - sokol.gfx.destroySampler(texture.sampler); - }, - } - - object.* = .empty; - } - } - } -}; - -const Rendering = struct { - objects: coral.stack.Sequential(Object), - instance_2d_pipeline: sokol.gfx.Pipeline, - instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer), - - const Instance2D = extern struct { - transform: gfx.Transform2D, - tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), - depth: f32 = 0, - texture_offset: gfx.Point2D = @splat(0), - texture_size: gfx.Point2D = @splat(1), - - const buffer_indices = .{ - .mesh = 0, - .instance = 1, - }; - - const instances_per_buffer = 512; - - const shader = @import("./shaders/instance_2d.glsl.zig"); - }; - - const Frame = struct { - width: u16, - height: u16, - flushed_instance_2d_count: usize = 0, - pushed_instance_2d_count: usize = 0, - mesh_2d: gfx.Handle = .none, - texture: gfx.Handle = .none, - - fn unflushed_instance_2d_count(self: Frame) usize { - return self.pushed_instance_2d_count - self.flushed_instance_2d_count; - } - }; - - const Object = union (enum) { - empty, - - mesh_2d: struct { - index_count: u32, - vertex_buffer: sokol.gfx.Buffer, - index_buffer: sokol.gfx.Buffer, - }, - - texture: struct { - image: sokol.gfx.Image, - sampler: sokol.gfx.Sampler, - }, - }; - - fn deinit(self: *Rendering) void { - for (self.instance_2d_buffers.values) |buffer| { - sokol.gfx.destroyBuffer(buffer); - } - - self.instance_2d_buffers.deinit(); - sokol.gfx.destroyPipeline(self.instance_2d_pipeline); - self.objects.deinit(); - } - - fn init() Rendering { - sokol.gfx.setup(.{ - .environment = .{ - .defaults = .{ - .color_format = .RGBA8, - .depth_format = .DEPTH_STENCIL, - .sample_count = 1, - }, - }, - - .logger = .{ - .func = sokol.log.func, - }, - }); - - return .{ - .instance_2d_pipeline = sokol.gfx.makePipeline(.{ - .label = "2D drawing pipeline", - - .layout = .{ - .attrs = get: { - var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; - - attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.mesh, - }; - - attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.mesh, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{ - .format = .FLOAT2, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{ - .format = .UBYTE4N, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{ - .format = .FLOAT, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{ - .format = .FLOAT4, - .buffer_index = Instance2D.buffer_indices.instance, - }; - - break: get attrs; - }, - - .buffers = get: { - var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8; - - buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE; - - break: get buffers; - }, - }, - - .shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())), - .index_type = .UINT16, - }), - - .instance_2d_buffers = .{.allocator = coral.heap.allocator}, - .objects = .{.allocator = coral.heap.allocator}, - }; - } - - fn flush_instance_2ds(self: *Rendering, frame: *Frame) void { - const unflushed_count = frame.unflushed_instance_2d_count(); - - if (unflushed_count == 0) { - return; - } - - sokol.gfx.applyPipeline(self.instance_2d_pipeline); - - sokol.gfx.applyUniforms(.VS, Instance2D.shader.SLOT_Screen, sokol.gfx.asRange(&Instance2D.shader.Screen{ - .screen_size = .{@floatFromInt(frame.width), @floatFromInt(frame.height)}, - })); - - const mesh_2d = self.objects.values[frame.mesh_2d.index().?].mesh_2d; - const texture = self.objects.values[frame.texture.index().?].texture; - - var bindings = sokol.gfx.Bindings{ - .vertex_buffers = get: { - var buffers = [_]sokol.gfx.Buffer{.{}} ** 8; - - buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer; - - break: get buffers; - }, - - .index_buffer = mesh_2d.index_buffer, - - .fs = .{ - .images = get: { - var images = [_]sokol.gfx.Image{.{}} ** 12; - - images[0] = texture.image; - - break: get images; - }, - - .samplers = get: { - var samplers = [_]sokol.gfx.Sampler{.{}} ** 8; - - samplers[0] = texture.sampler; - - break: get samplers; - }, - }, - }; - - while (frame.flushed_instance_2d_count < frame.pushed_instance_2d_count) { - const buffer_index = frame.flushed_instance_2d_count / Instance2D.instances_per_buffer; - const buffer_offset = frame.flushed_instance_2d_count % Instance2D.instances_per_buffer; - const instances_to_flush = @min(Instance2D.instances_per_buffer - buffer_offset, unflushed_count); - - bindings.vertex_buffers[Instance2D.buffer_indices.instance] = self.instance_2d_buffers.values[buffer_index]; - bindings.vertex_buffer_offsets[Instance2D.buffer_indices.instance] = @intCast(buffer_offset); - - sokol.gfx.applyBindings(bindings); - sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush)); - - frame.flushed_instance_2d_count += instances_to_flush; - } - } - - fn insert_object(self: *Rendering, index: usize, object: Object) !void { - const resource_count = self.objects.len(); - - if (index < resource_count) { - const empty_object = &self.objects.values[index]; - - if (empty_object.* != .empty) { - return error.InvalidHandle; - } - - empty_object.* = object; - } else { - if (index != resource_count) { - return error.InvalidIndex; - } - - try self.objects.push_grow(object); - } - } - - fn push_instance_2d(self: *Rendering, frame: *Frame, command: RenderCommand.Instance) std.mem.Allocator.Error!void { - if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) { - self.flush_instance_2ds(frame); - } - - frame.mesh_2d = command.mesh_2d; - frame.texture = command.texture; - - const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0; - const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer; - - if (has_filled_buffer and pushed_buffer_count == self.instance_2d_buffers.len()) { - const instance_buffer = sokol.gfx.makeBuffer(.{ - .size = @sizeOf(Instance2D) * Instance2D.instances_per_buffer, - .usage = .STREAM, - .label = "2D drawing instance buffer", - }); - - errdefer sokol.gfx.destroyBuffer(instance_buffer); - - try self.instance_2d_buffers.push_grow(instance_buffer); - } - - _ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{ - .transform = command.transform, - })); - - frame.pushed_instance_2d_count += 1; - } - - fn remove_object(self: *Rendering, index: usize) ?Object { - const object = self.objects.values[index]; - - if (object != .empty) { - self.objects.values[index] = .empty; - - return object; - } - - return null; - } -}; - -pub const RenderCommand = union (enum) { - instance_2d: Instance, - post_process: PostProcess, - - pub const Instance = struct { - texture: gfx.Handle, - mesh_2d: gfx.Handle, - transform: gfx.Transform2D, - }; - - pub const PostProcess = struct { - - }; - - fn clone(self: RenderCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!RenderCommand { - _ = arena; - - return switch (self) { - .instance_2d => |instance_2d| .{.instance_2d = instance_2d}, - .post_process => |post_process| .{.post_process = post_process}, - }; - } -}; - -pub const RenderChain = commands.Chain(RenderCommand, RenderCommand.clone); - -pub const RenderList = commands.List(RenderCommand, RenderCommand.clone); diff --git a/src/ona/gfx/formats.zig b/src/ona/gfx/formats.zig deleted file mode 100644 index a717cd4..0000000 --- a/src/ona/gfx/formats.zig +++ /dev/null @@ -1,81 +0,0 @@ -const coral = @import("coral"); - -const gfx = @import("../gfx.zig"); - -const std = @import("std"); - -pub fn bmp_file_desc( - arena: *std.heap.ArenaAllocator, - storage: coral.files.Storage, - path: []const u8, -) gfx.Assets.Format.Error!gfx.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_bytes(path, file_offset, line) != 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 .{ - .texture = .{ - .width = pixel_width, - .data = pixels, - .format = .rgba8, - .access = .static, - } - }; -} diff --git a/src/ona/gfx/rendering.zig b/src/ona/gfx/rendering.zig new file mode 100644 index 0000000..bba5fa6 --- /dev/null +++ b/src/ona/gfx/rendering.zig @@ -0,0 +1,428 @@ +const Resources = @import("./Resources.zig"); + +const coral = @import("coral"); + +const ext = @import("../ext.zig"); + +const gfx = @import("../gfx.zig"); + +const spirv = @import("./spirv.zig"); + +const sokol = @import("sokol"); + +const std = @import("std"); + +const Frame = struct { + texture_batch_buffers: coral.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, + + const DrawTexture = extern struct { + transform: gfx.Transform2D, + tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), + depth: f32 = 0, + texture_offset: @Vector(2, f32) = @splat(0), + texture_size: @Vector(2, f32) = @splat(1), + }; + + const batches_per_buffer = 512; + + pub fn deinit(self: *Frame) void { + for (self.texture_batch_buffers.values) |buffer| { + sokol.gfx.destroyBuffer(buffer); + } + + self.texture_batch_buffers.deinit(); + + self.* = undefined; + } + + pub fn init() !Frame { + const Vertex = struct { + xy: @Vector(2, f32), + uv: @Vector(2, f32), + }; + + const quad_index_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(&[_]u16{0, 1, 2, 0, 2, 3}), + .type = .INDEXBUFFER, + }); + + const quad_vertex_buffer = sokol.gfx.makeBuffer(.{ + .data = sokol.gfx.asRange(&[_]Vertex{ + .{.xy = .{-0.5, -0.5}, .uv = .{0, 1}}, + .{.xy = .{0.5, -0.5}, .uv = .{1, 1}}, + .{.xy = .{0.5, 0.5}, .uv = .{1, 0}}, + .{.xy = .{-0.5, 0.5}, .uv = .{0, 0}}, + }), + + .type = .VERTEXBUFFER, + }); + + return .{ + .texture_batch_buffers = .{.allocator = coral.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 { + if (command.texture != self.current_source_texture) { + self.flush(pools); + } + + self.current_source_texture = command.texture; + + const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0; + const buffer_count = self.drawn_count / batches_per_buffer; + + if (has_filled_current_buffer and buffer_count == self.texture_batch_buffers.len()) { + const instance_buffer = sokol.gfx.makeBuffer(.{ + .size = @sizeOf(DrawTexture) * batches_per_buffer, + .usage = .STREAM, + }); + + errdefer sokol.gfx.destroyBuffer(instance_buffer); + + try self.texture_batch_buffers.push_grow(instance_buffer); + } + + _ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?, sokol.gfx.asRange(&DrawTexture{ + .transform = command.transform, + })); + + self.drawn_count += 1; + } + + pub fn finish(self: *Frame, pools: *Resources) void { + self.flush(pools); + + self.drawn_count = 0; + self.flushed_count = 0; + self.current_source_texture = .default; + self.current_target_texture = .backbuffer; + self.current_effect = .default; + } + + pub fn flush(self: *Frame, pools: *Resources) void { + if (self.flushed_count == self.drawn_count) { + return; + } + + var bindings = sokol.gfx.Bindings{ + .index_buffer = self.quad_index_buffer, + }; + + bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer; + + switch (pools.get_texture(self.current_source_texture).?.access) { + .render => |render| { + bindings.fs.images[0] = render.color_image; + bindings.fs.samplers[0] = default_sampler; + }, + + .static => |static| { + bindings.fs.images[0] = static.image; + bindings.fs.samplers[0] = default_sampler; + }, + + .empty => { + @panic("Cannot render empty textures"); + }, + } + + const effect = pools.get_effect(self.current_effect).?; + + sokol.gfx.applyPipeline(effect.pipeline); + + const texture = pools.get_texture(self.current_target_texture).?; + + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{ + .left = 0, + .top = 0, + .right = @floatFromInt(texture.width), + .bottom = @floatFromInt(texture.height), + }))); + + if (effect.properties.len != 0) { + sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties)); + } + + while (true) { + const buffer_index = self.flushed_count / batches_per_buffer; + const buffer_offset = self.flushed_count % batches_per_buffer; + const instances_to_flush = @min(batches_per_buffer - buffer_offset, self.drawn_count - self.flushed_count); + + self.flushed_count += instances_to_flush; + bindings.vertex_buffers[vertex_indices.instance] = self.texture_batch_buffers.values[buffer_index]; + bindings.vertex_buffer_offsets[vertex_indices.instance] = @intCast(@sizeOf(DrawTexture) * buffer_offset); + + sokol.gfx.applyBindings(bindings); + sokol.gfx.draw(0, 6, @intCast(instances_to_flush)); + + if (self.flushed_count == self.drawn_count) { + break; + } + } + } + + pub fn set_effect(self: *Frame, pools: *Resources, command: gfx.Commands.SetEffectCommand) void { + if (command.effect != self.current_effect) { + self.flush(pools); + } + + self.current_effect = command.effect; + + if (pools.get_effect(self.current_effect)) |effect| { + @memcpy(effect.properties, command.properties); + } + } + + pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void { + sokol.gfx.endPass(); + + var pass = sokol.gfx.Pass{ + .action = .{ + .stencil = .{ + .load_action = .CLEAR, + }, + }, + }; + + if (command.clear_color) |color| { + pass.action.colors[0] = .{ + .load_action = .CLEAR, + .clear_value = @bitCast(color), + }; + } else { + pass.action.colors[0] = .{.load_action = .LOAD}; + } + + if (command.clear_depth) |depth| { + pass.action.depth = .{ + .load_action = .CLEAR, + .clear_value = depth, + }; + } else { + pass.action.depth = .{.load_action = .LOAD}; + } + + pass.attachments = switch (pools.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, + }; + + self.current_target_texture = command.texture; + + sokol.gfx.beginPass(pass); + } +}; + +var default_sampler: sokol.gfx.Sampler = undefined; + +const vertex_indices = .{ + .mesh = 0, + .instance = 1, +}; + +pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void { + const context = configure_and_create: { + var result = @as(c_int, 0); + + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE); + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3); + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3); + result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1); + + if (result != 0) { + return error.Unsupported; + } + + break: configure_and_create ext.SDL_GL_CreateContext(window); + }; + + defer ext.SDL_GL_DeleteContext(context); + + sokol.gfx.setup(.{ + .environment = .{ + .defaults = .{ + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .sample_count = 1, + }, + }, + + .logger = .{ + .func = sokol.log.func, + }, + }); + + defer { + sokol.gfx.shutdown(); + } + + var resources = try Resources.init(); + + defer { + resources.deinit(); + } + + var frame = try Frame.init(); + + defer { + frame.deinit(); + } + + default_sampler = sokol.gfx.makeSampler(.{}); + + while (true) { + switch (pending_work.dequeue()) { + .load_effect => |load| { + const effect = try resources.create_effect(load.desc); + + if (!load.loaded.resolve(effect)) { + std.debug.assert(resources.destroy_effect(effect)); + } + }, + + .load_texture => |load| { + const texture = try resources.create_texture(load.desc); + + if (!load.loaded.resolve(texture)) { + std.debug.assert(resources.destroy_texture(texture)); + } + }, + + .render_frame => |render_frame| { + const backbuffer = resources.get_texture(.backbuffer).?; + + if (backbuffer.width != render_frame.width or backbuffer.height != render_frame.height) { + backbuffer.deinit(); + + backbuffer.* = try Resources.Texture.init(.{ + .format = .rgba8, + + .access = .{ + .render = .{ + .width = render_frame.width, + .height = render_frame.height, + }, + }, + }); + } + + sokol.gfx.beginPass(pass: { + var pass = sokol.gfx.Pass{ + .action = .{ + .stencil = .{ + .load_action = .CLEAR, + }, + + .depth = .{ + .load_action = .CLEAR, + .clear_value = 0, + } + }, + }; + + pass.action.colors[0] = .{ + .load_action = .CLEAR, + .clear_value = @bitCast(render_frame.clear_color), + }; + + pass.attachments = resources.get_texture(.backbuffer).?.access.render.attachments; + + break: pass pass; + }); + + var has_command_params = render_frame.has_command_params; + + while (has_command_params) |command_params| : (has_command_params = command_params.has_next) { + for (command_params.param.submitted_commands()) |command| { + try switch (command) { + .draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture), + .set_effect => |set_effect| frame.set_effect(&resources, set_effect), + .set_target => |set_target| frame.set_target(&resources, set_target), + }; + } + + frame.flush(&resources); + + if (frame.current_target_texture != .backbuffer) { + frame.set_target(&resources, .{ + .texture = .backbuffer, + .clear_color = null, + .clear_depth = null, + .clear_stencil = null, + }); + } + } + + sokol.gfx.endPass(); + + sokol.gfx.beginPass(swapchain_pass: { + var pass = sokol.gfx.Pass{ + .swapchain = .{ + .width = render_frame.width, + .height = render_frame.height, + .sample_count = 1, + .color_format = .RGBA8, + .depth_format = .DEPTH_STENCIL, + .gl = .{.framebuffer = 0}, + }, + + .action = .{ + .stencil = .{.load_action = .CLEAR}, + .depth = .{.load_action = .CLEAR}, + }, + }; + + pass.action.colors[0] = .{.load_action = .CLEAR}; + + break: swapchain_pass pass; + }); + + try frame.draw_texture(&resources, .{ + .texture = .backbuffer, + + .transform = .{ + .origin = .{@as(f32, @floatFromInt(render_frame.width)) / 2, @as(f32, @floatFromInt(render_frame.height)) / 2}, + .xbasis = .{@floatFromInt(render_frame.width), 0}, + .ybasis = .{0, @floatFromInt(render_frame.height)}, + }, + }); + + frame.finish(&resources); + sokol.gfx.endPass(); + sokol.gfx.commit(); + ext.SDL_GL_SwapWindow(window); + render_frame.finished.set(); + }, + + .shutdown => { + break; + }, + + .unload_effect => |unload| { + if (!resources.destroy_effect(unload.handle)) { + @panic("Attempt to unload a non-existent effect"); + } + }, + + .unload_texture => |unload| { + if (!resources.destroy_texture(unload.handle)) { + @panic("Attempt to unload a non-existent texture"); + } + }, + } + } +} + +var work_thread: std.Thread = undefined; diff --git a/src/ona/gfx/shaders/2d_default.frag b/src/ona/gfx/shaders/2d_default.frag new file mode 100644 index 0000000..40e5c20 --- /dev/null +++ b/src/ona/gfx/shaders/2d_default.frag @@ -0,0 +1,16 @@ +#version 430 + +layout (binding = 0) uniform sampler2D sprite; + +layout (location = 0) in vec4 color; +layout (location = 1) in vec2 uv; + +layout (location = 0) out vec4 texel; + +void main() { + texel = texture(sprite, uv) * color; + + if (texel.a == 0) { + discard; + } +} diff --git a/src/ona/gfx/shaders/2d_default.vert b/src/ona/gfx/shaders/2d_default.vert new file mode 100644 index 0000000..d5045cb --- /dev/null +++ b/src/ona/gfx/shaders/2d_default.vert @@ -0,0 +1,29 @@ +#version 430 + +layout (location = 0) in vec2 mesh_xy; +layout (location = 1) in vec2 mesh_uv; + +layout (location = 2) in vec2 instance_xbasis; +layout (location = 3) in vec2 instance_ybasis; +layout (location = 4) in vec2 instance_origin; +layout (location = 5) in vec4 instance_tint; +layout (location = 6) in float instance_depth; +layout (location = 7) in vec4 instance_rect; + +layout (location = 0) out vec4 color; +layout (location = 1) out vec2 uv; + +layout (binding = 0) uniform View { + mat4 projection_matrix; +}; + +void main() { + const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; + const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy; + const vec2 rect_size = instance_rect.zw - instance_rect.xy; + const vec4 screen_position = vec4(projected_position, instance_depth, 1.0); + + gl_Position = screen_position; + color = instance_tint; + uv = instance_rect.xy + (mesh_uv * rect_size); +} diff --git a/src/ona/gfx/shaders/instance_2d.glsl b/src/ona/gfx/shaders/instance_2d.glsl deleted file mode 100644 index 160c15c..0000000 --- a/src/ona/gfx/shaders/instance_2d.glsl +++ /dev/null @@ -1,61 +0,0 @@ -@header const Vec2 = @Vector(2, f32) -@ctype vec2 Vec2 - -@vs vs -in vec2 mesh_xy; -in vec2 mesh_uv; - -in vec2 instance_xbasis; -in vec2 instance_ybasis; -in vec2 instance_origin; -in vec4 instance_tint; -in float instance_depth; -in vec4 instance_rect; - -uniform Screen { - vec2 screen_size; -}; - -out vec4 color; -out vec2 uv; - -void main() { - // Calculate the world position of the vertex - const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; - - // Convert world position to normalized device coordinates (NDC) - // Assuming the screen coordinates range from (0, 0) to (screen_size.x, screen_size.y) - const vec2 ndc_position = (vec2(world_position.x, -world_position.y) / screen_size) * 2.0 - vec2(1.0, -1.0); - - // Set the position of the vertex in clip space - gl_Position = vec4(ndc_position, instance_depth, 1.0); - color = instance_tint; - - // Calculate the width and height from left, top, right, bottom configuration - const vec2 rect_pos = instance_rect.xy; // left, top - const vec2 rect_size = instance_rect.zw - instance_rect.xy; // right - left, bottom - top - - // Calculate the adjusted UV coordinates using the instance_rect - uv = rect_pos + (mesh_uv * rect_size); -} -@end - -@fs fs -uniform texture2D tex; -uniform sampler smp; - -in vec4 color; -in vec2 uv; - -out vec4 texel; - -void main() { - texel = texture(sampler2D(tex, smp), uv) * color; - - if (texel.a == 0) { - discard; - } -} -@end - -@program draw_2d vs fs diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig new file mode 100644 index 0000000..6972e8a --- /dev/null +++ b/src/ona/gfx/spirv.zig @@ -0,0 +1,407 @@ +const coral = @import("coral"); + +const ext = @cImport({ + @cInclude("spirv-cross/spirv_cross_c.h"); +}); + +const std = @import("std"); + +pub const Error = std.mem.Allocator.Error || error { + InvalidSPIRV, + UnsupportedSPIRV, +}; + +pub const Options = struct { + target: Target, + vertex_source: Source, + fragment_source: Source, +}; + +pub const Program = struct { + vertex_stage: Stage, + fragment_stage: Stage, +}; + +pub const Source = struct { + entry_point: []const u8 = "main", + ops: []const u32, +}; + +pub const Stage = struct { + entry_point: [:0]const u8, + source: [:0]const u8, + has_image_sampler_pairs: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs, + has_images: [max_images]?Image = [_]?Image{null} ** max_images, + has_samplers: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers, + has_uniform_blocks: [max_uniform_blocks]?UniformBlock = [_]?UniformBlock{null} ** max_uniform_blocks, + + pub const Image = struct { + is_multisampled: bool, + }; + + pub const ImageSamplerPair = struct { + image_slot: usize, + sampler_slot: usize, + name: [:0]const u8, + }; + + pub const Layout = enum { + std140, + }; + + pub const Sampler = enum { + filtering, + non_filtering, + comparison, + }; + + pub const Uniform = struct { + name: [:0]const u8, + type: Type, + len: u16, + + pub const Type = enum { + float, + float2, + float3, + float4, + integer, + integer2, + integer3, + integer4, + matrix4, + }; + }; + + pub const UniformBlock = struct { + layout: Layout, + uniforms: []const Uniform, + + pub const max_uniforms = 16; + + pub fn size(self: UniformBlock) usize { + var accumulated_size: usize = 0; + + for (self.uniforms) |uniform| { + accumulated_size += @max(1, uniform.len) * @as(usize, switch (uniform.type) { + .float => 4, + .float2 => 8, + .float3 => 12, + .float4 => 16, + .integer => 4, + .integer2 => 8, + .integer3 => 12, + .integer4 => 16, + .matrix4 => 64, + }); + } + + const alignment: usize = switch (self.layout) { + .std140 => 16, + }; + + return (accumulated_size + (alignment - 1)) & ~(alignment - 1); + } + }; + + pub const max_images = 12; + + pub const max_image_sampler_pairs = 12; + + pub const max_samplers = 8; + + pub const max_storage_buffers = 8; + + pub const max_uniform_blocks = 8; + + fn reflect_images_samplers(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { + const arena_allocator = arena.allocator(); + var sampled_images: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE, + @ptrCast(&sampled_images.ptr), + &sampled_images.len, + )); + + if (sampled_images.len > max_image_sampler_pairs) { + return error.UnsupportedSPIRV; + } + + for (0 .. sampled_images.len, sampled_images) |i, sampled_image| { + const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id); + + if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) { + return error.InvalidSPIRV; + } + + try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) { + ext.SpvDim2D => {}, + else => error.InvalidSPIRV, + }; + + try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) { + ext.SPVC_BASETYPE_FP32 => {}, + else => error.InvalidSPIRV, + }; + + self.has_images[i] = .{ + .is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0, + }; + + self.has_samplers[i] = .filtering; + + self.has_image_sampler_pairs[i] = .{ + .name = try arena_allocator.dupeZ(u8, std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id))), + .image_slot = @intCast(i), + .sampler_slot = @intCast(i), + }; + } + } + + fn reflect_uniform_blocks(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void { + const arena_allocator = arena.allocator(); + var reflected_resources: []const ext.spvc_reflected_resource = &.{}; + + try to_error(ext.spvc_resources_get_resource_list_for_type( + resources, + ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, + @ptrCast(&reflected_resources.ptr), + &reflected_resources.len, + )); + + if (reflected_resources.len > max_uniform_blocks) { + return error.InvalidSPIRV; + } + + for (0 .. reflected_resources.len, reflected_resources) |i, reflected_resource| { + const type_handle = ext.spvc_compiler_get_type_handle(compiler, reflected_resource.base_type_id); + const is_std430 = ext.spvc_compiler_has_decoration(compiler, reflected_resource.id, ext.SpvDecorationBufferBlock) == ext.SPVC_TRUE; + + if (is_std430) { + return error.UnsupportedSPIRV; + } + + const uniform_count = ext.spvc_type_get_num_member_types(type_handle); + + if (uniform_count > UniformBlock.max_uniforms) { + return error.UnsupportedSPIRV; + } + + const uniforms = try arena_allocator.alloc(Uniform, uniform_count); + + for (uniforms, 0 .. uniform_count) |*uniform, j| { + const member_index: c_uint = @intCast(j); + const member_type_handle = ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_member_type(type_handle, member_index)); + + if (ext.spvc_type_get_num_array_dimensions(member_type_handle) > 1) { + return error.UnsupportedSPIRV; + } + + uniform.* = .{ + .name = try coral.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{ + .id = reflected_resource.id, + .member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)), + }), + + .type = try switch (ext.spvc_type_get_basetype(member_type_handle)) { + ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) { + 4 => switch (ext.spvc_type_get_columns(member_type_handle)) { + 4 => Uniform.Type.matrix4, + 1 => Uniform.Type.float4, + else => error.UnsupportedSPIRV, + }, + + 1 => Uniform.Type.float, + 2 => Uniform.Type.float2, + 3 => Uniform.Type.float3, + else => error.UnsupportedSPIRV, + }, + + ext.SPVC_BASETYPE_INT32 => try switch (ext.spvc_type_get_vector_size(member_type_handle)) { + 1 => Uniform.Type.integer, + 2 => Uniform.Type.integer2, + 3 => Uniform.Type.integer3, + 4 => Uniform.Type.integer4, + else => error.UnsupportedSPIRV, + }, + + else => error.UnsupportedSPIRV, + }, + + .len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse { + return error.UnsupportedSPIRV; + }, + }; + } + + self.has_uniform_blocks[i] = .{ + .uniforms = uniforms, + .layout = .std140, + }; + } + } +}; + +pub const Target = enum { + glsl, +}; + +pub fn analyze(arena: *std.heap.ArenaAllocator, options: Options) Error!Program { + var context: ext.spvc_context = null; + + if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) { + return error.OutOfMemory; + } + + defer { + ext.spvc_context_destroy(context); + } + + ext.spvc_context_set_error_callback(context, log_errors, null); + + const arena_allocator = arena.allocator(); + + return .{ + .vertex_stage = vertex_stage: { + const compiler = try parse(arena, context, options.target, options.vertex_source); + + var stage = Stage{ + .entry_point = try arena_allocator.dupeZ(u8, options.vertex_source.entry_point), + .source = try compile(arena, compiler), + }; + + const resources = create: { + var resources: ext.spvc_resources = null; + + try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources)); + + break: create resources; + }; + + try stage.reflect_images_samplers(arena, compiler, resources); + try stage.reflect_uniform_blocks(arena, compiler, resources); + + std.log.info("{s}", .{stage.source}); + + break: vertex_stage stage; + }, + + .fragment_stage = fragment_stage: { + const compiler = try parse(arena, context, options.target, options.fragment_source); + + var stage = Stage{ + .entry_point = try arena_allocator.dupeZ(u8, options.fragment_source.entry_point), + .source = try compile(arena, compiler), + }; + + const resources = create: { + var resources: ext.spvc_resources = null; + + try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources)); + + break: create resources; + }; + + try stage.reflect_images_samplers(arena, compiler, resources); + try stage.reflect_uniform_blocks(arena, compiler, resources); + + std.log.info("{s}", .{stage.source}); + + break: fragment_stage stage; + }, + }; +} + +fn compile(arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler) Error![:0]const u8 { + const arena_allocator = arena.allocator(); + var source: [*:0]const u8 = ""; + + try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&source))); + + return arena_allocator.dupeZ(u8, std.mem.span(source)); +} + +fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void { + std.debug.assert(userdata == null); + std.log.err("{s}", .{error_message}); +} + +fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Target, source: Source) Error!ext.spvc_compiler { + var parsed_ir: ext.spvc_parsed_ir = null; + + try to_error(ext.spvc_context_parse_spirv(context, source.ops.ptr, source.ops.len, &parsed_ir)); + + var compiler: ext.spvc_compiler = null; + + const spvc_target = switch (target) { + .glsl => ext.SPVC_BACKEND_GLSL, + }; + + try to_error(ext.spvc_context_create_compiler(context, spvc_target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler)); + + try to_error(ext.spvc_compiler_build_combined_image_samplers(compiler)); + + var combined_image_samplers: []const ext.spvc_combined_image_sampler = &.{}; + + try to_error(ext.spvc_compiler_get_combined_image_samplers(compiler, @ptrCast(&combined_image_samplers.ptr), &combined_image_samplers.len)); + + const arena_allocator = arena.allocator(); + var binding: u32 = 0; + + for (combined_image_samplers) |combined_image_sampler| { + const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{ + .image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)), + .sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)), + }); + + ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name); + ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding); + + binding += 1; + } + + const option_values: []const struct {ext.spvc_compiler_option, c_uint} = switch (target) { + .glsl => &.{ + .{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430}, + .{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)}, + .{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)}, + .{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)}, + .{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)}, + }, + }; + + var compiler_options: ext.spvc_compiler_options = null; + + try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options)); + + for (option_values) |option_value| { + const entry, const value = option_value; + + try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value)); + } + + try to_error(ext.spvc_compiler_install_compiler_options(compiler, compiler_options)); + + return compiler; +} + +fn to_error(result: ext.spvc_result) Error!void { + return switch (result) { + ext.SPVC_SUCCESS => {}, + ext.SPVC_ERROR_INVALID_SPIRV => error.InvalidSPIRV, + ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSPIRV, + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_ERROR_INVALID_ARGUMENT, ext.SPVC_ERROR_INT_MAX => unreachable, + else => unreachable, + }; +} + +pub fn to_ops(raw: anytype) [raw.len / @alignOf(u32)]u32 { + var ops: [raw.len / @alignOf(u32)]u32 = undefined; + + @memcpy(std.mem.sliceAsBytes(&ops), raw); + + return ops; +} diff --git a/src/ona/msg.zig b/src/ona/msg.zig index 54d2f1b..becee99 100644 --- a/src/ona/msg.zig +++ b/src/ona/msg.zig @@ -2,6 +2,8 @@ const App = @import("./App.zig"); const coral = @import("coral"); +const flow = @import("flow"); + const std = @import("std"); fn Channel(comptime Message: type) type { @@ -12,7 +14,7 @@ fn Channel(comptime Message: type) type { const Self = @This(); - fn cleanup(channel: coral.Write(Self)) void { + fn cleanup(channel: flow.Write(Self)) void { channel.res.deinit(); } @@ -24,7 +26,7 @@ fn Channel(comptime Message: type) type { self.* = undefined; } - fn swap(channel: coral.Write(Self)) void { + fn swap(channel: flow.Write(Self)) void { channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0; if (channel.res.ticks == 0) { @@ -61,23 +63,23 @@ pub fn Receive(comptime Message: type) type { const Self = @This(); - pub const State = struct { + pub const Param = struct { channel: *const TypedChannel, }; - pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State { + pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param { return .{ - .channel = (try context.register_read_only_resource_access(thread_restriction, TypedChannel)) orelse set: { - try context.world.set_resource(thread_restriction, TypedChannel.init(coral.heap.allocator)); + .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_read_only_resource_access(thread_restriction, TypedChannel)).?; + break: set (try context.register_readable_state_access(TypedChannel)).?; }, }; } - pub fn init(state: *State) Self { + pub fn init(param: *Param) Self { return .{ - .channel = state.channel, + .channel = param.channel, }; } @@ -95,33 +97,33 @@ pub fn Send(comptime Message: type) type { const Self = @This(); - pub const State = struct { + pub const Param = struct { channel: *TypedChannel, }; - pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State { + pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param { return .{ - .channel = (try context.register_read_write_resource_access(thread_restriction, TypedChannel)) orelse set: { - try context.world.set_resource(thread_restriction, TypedChannel.init(coral.heap.allocator)); + .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_resource(.none, App).?; + const app = context.world.get_state(App).?; - try context.world.on_event(app.events.post_update, coral.system_fn(TypedChannel.swap), .{ + 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, coral.system_fn(TypedChannel.cleanup), .{ + 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_read_write_resource_access(thread_restriction, TypedChannel)).?; + break: set (try context.register_writable_state_access(TypedChannel)).?; }, }; } - pub fn init(state: *State) Self { + pub fn init(param: *Param) Self { return .{ - .channel = state.channel, + .channel = param.channel, }; } @@ -130,5 +132,3 @@ pub fn Send(comptime Message: type) type { } }; } - -const thread_restriction = coral.World.ThreadRestriction.none; diff --git a/src/ona/ona.zig b/src/ona/ona.zig index 51f6f1e..b14e529 100644 --- a/src/ona/ona.zig +++ b/src/ona/ona.zig @@ -6,6 +6,8 @@ const coral = @import("coral"); const ext = @import("./ext.zig"); +const flow = @import("flow"); + pub const gfx = @import("./gfx.zig"); pub const msg = @import("./msg.zig"); @@ -23,7 +25,13 @@ pub const Options = struct { }; }; -pub const Setup = fn (*coral.World, App.Events) anyerror!void; +pub const Read = flow.Read; + +pub const Setup = fn (*flow.World, App.Events) anyerror!void; + +pub const World = flow.World; + +pub const Write = flow.Write; pub const default_middlewares = &.{ gfx.setup, @@ -37,7 +45,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { } var world = try switch (options.execution) { - .single_threaded => coral.World.init(0), + .single_threaded => flow.World.init(0), .thread_share => |thread_share| init: { const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| { @@ -48,7 +56,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { }); }, 0, std.math.maxInt(u32)))); - break: init coral.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0); + break: init flow.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0); }, }; @@ -64,9 +72,10 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { .exit = try world.create_event("exit"), }; - const app = try world.set_get_resource(.none, App{ + const app = try world.set_get_state(App{ .events = &events, .target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)), + .elapsed_time = 0, .is_running = true, }); @@ -77,7 +86,8 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { try setup(&world, events); try world.run_event(events.load); - var ticks_previous = std.time.milliTimestamp(); + const ticks_initial = std.time.milliTimestamp(); + var ticks_previous = ticks_initial; var accumulated_time = @as(f64, 0); while (app.is_running) { @@ -85,6 +95,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { const milliseconds_per_second = 1000.0; const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; + app.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second; ticks_previous = ticks_current; accumulated_time += delta_time; @@ -101,3 +112,5 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void { try world.run_event(events.exit); } + +pub const system_fn = flow.system_fn; diff --git a/tools/sokol-shdc b/tools/sokol-shdc deleted file mode 100755 index 2c16e3a..0000000 --- a/tools/sokol-shdc +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:0331c07b47617a76af9ea8bd87fc103af1f09ea0ce82c991925e47b3887e0618 -size 15195920 diff --git a/tools/sokol-shdc.exe b/tools/sokol-shdc.exe deleted file mode 100644 index a3811e3..0000000 --- a/tools/sokol-shdc.exe +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ccf08bc0d7cd96b83273340104728204ed2673ff1c47d782494313354eba2635 -size 9369088