From 40067c067b913a513a6ae59bc4b40506f307e57b Mon Sep 17 00:00:00 2001 From: kayomn Date: Tue, 2 Jul 2024 23:08:25 +0100 Subject: [PATCH] Initial replacement of Sokol shdc with a runtime compilation system --- .gitattributes | 2 - .gitignore | 4 +- .gitmodules | 3 + .vscode/settings.json | 14 +- b64_dump.py | 29 + build.zig | 166 ++-- build.zig.zon | 24 +- ext/spirv-cross | 1 + src/coral/coral.zig | 290 ------ src/coral/script/Chunk.zig | 998 -------------------- src/coral/script/Table.zig | 135 --- src/coral/script/tokens.zig | 535 ----------- src/coral/script/tree.zig | 268 ------ src/coral/script/tree/Expr.zig | 906 ------------------ src/coral/script/tree/Stmt.zig | 242 ----- src/coral/slots.zig | 23 - src/{coral => flow}/World.zig | 39 +- src/{coral => flow}/dag.zig | 10 +- src/flow/flow.zig | 273 ++++++ src/{coral/resource.zig => flow/states.zig} | 25 +- src/{coral => flow}/system.zig | 82 +- src/main.zig | 22 +- src/ona/App.zig | 16 +- src/ona/act.zig | 10 +- src/ona/gfx.zig | 58 +- src/ona/gfx/device.zig | 194 +++- src/ona/gfx/formats.zig | 2 +- src/ona/gfx/shaders/default_2d.glsl | 60 -- src/ona/gfx/shaders/render_2d.frag | 17 + src/ona/gfx/shaders/render_2d.vert | 36 + src/ona/gfx/spirv.zig | 130 +++ src/ona/msg.zig | 30 +- src/ona/ona.zig | 18 +- tools/sokol-shdc | 3 - tools/sokol-shdc.exe | 3 - 35 files changed, 915 insertions(+), 3753 deletions(-) create mode 100644 .gitmodules create mode 100755 b64_dump.py create mode 160000 ext/spirv-cross delete mode 100644 src/coral/script/Chunk.zig delete mode 100644 src/coral/script/Table.zig delete mode 100644 src/coral/script/tokens.zig delete mode 100644 src/coral/script/tree.zig delete mode 100644 src/coral/script/tree/Expr.zig delete mode 100644 src/coral/script/tree/Stmt.zig delete mode 100644 src/coral/slots.zig rename src/{coral => flow}/World.zig (54%) rename src/{coral => flow}/dag.zig (93%) create mode 100644 src/flow/flow.zig rename src/{coral/resource.zig => flow/states.zig} (76%) rename src/{coral => flow}/system.zig (83%) delete mode 100644 src/ona/gfx/shaders/default_2d.glsl create mode 100644 src/ona/gfx/shaders/render_2d.frag create mode 100644 src/ona/gfx/shaders/render_2d.vert create mode 100644 src/ona/gfx/spirv.zig delete mode 100755 tools/sokol-shdc delete mode 100644 tools/sokol-shdc.exe 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/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/b64_dump.py b/b64_dump.py new file mode 100755 index 0000000..720bac9 --- /dev/null +++ b/b64_dump.py @@ -0,0 +1,29 @@ +#!/bin/python + +import sys +import base64 +import struct + +def format_base64_to_u32(base64_string): + # Decode the Base64 string + decoded_bytes = base64.b64decode(base64_string) + + # Interpret the bytes as a sequence of u32 values + u32_values = struct.unpack('I' * (len(decoded_bytes) // 4), decoded_bytes) + + # Format the u32 values as C-style hex values + formatted_u32_values = ', '.join(f'0x{value:08x}' for value in u32_values) + + # Split the formatted string into lines of 8 values each + lines = [', '.join(formatted_u32_values.split(', ')[i:i+8]) for i in range(0, len(u32_values), 8)] + + # Print the formatted values + print(',\n'.join(lines)) + +if __name__ == "__main__": + if len(sys.argv) != 2: + print(f"Usage: python {sys.argv[0]} ") + sys.exit(1) + + base64_string = sys.argv[1] + format_base64_to_u32(base64_string) diff --git a/build.zig b/build.zig index 234db97..e7da8c8 100644 --- a/build.zig +++ b/build.zig @@ -6,34 +6,49 @@ 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", .{ - .target = target, - .optimize = optimize, - }); + const flow_module = b.createModule(.{ + .root_source_file = b.path("src/flow/flow.zig"), - const module = b.addModule("ona", .{ - .root_source_file = b.path("src/ona/ona.zig"), - - .imports = &.{ - .{ - .name = "sokol", - .module = sokol_dependency.module("sokol"), - }, - - .{ - .name = "coral", - .module = coral_module, - }, + .imports = &.{ + .{ + .name = "coral", + .module = coral_module, }, - }); + } + }); - break: add 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(build_spirvcross(b, target, optimize)); b.step("test", "Run unit tests").dependOn(create: { const tests = b.addTest(.{ @@ -46,92 +61,59 @@ pub fn build(b: *std.Build) !void { }); 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); - - break: add compile_step; + break: add exe; }); } -fn depend_on_shaders( +fn build_spirvcross( 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 }); + mode: std.builtin.OptimizeMode, +) *std.Build.Step.Compile { + const dir = "ext/spirv-cross/"; - defer dir.close(); - - var walker = try dir.walk(b.allocator); - - 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 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 path_buffer_max = 255; - var input_path_buffer = [_]u8{undefined} ** path_buffer_max; - var output_path_buffer = [_]u8{undefined} ** path_buffer_max; + const lib = b.addStaticLibrary(.{ + .name = "spirvcross", + .target = target, + .optimize = mode, + }); - 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; - }; - - 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", - }); - - step.dependOn(&cmd.step); - } + switch (lib.rootModuleTarget().abi) { + .msvc => lib.linkLibC(), + else => lib.linkLibCpp(), } + + inline for (sources) |src| { + lib.addCSourceFile(.{ + .file = b.path(dir ++ src), + .flags = &.{"-fstrict-aliasing", "-DSPIRV_CROSS_C_API_GLSL", "-DSPIRV_CROSS_C_API_HLSL", "-DSPIRV_CROSS_C_API_MSL"}, + }); + } + + return lib; } 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/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/src/coral/coral.zig b/src/coral/coral.zig index d760db7..fae4ce8 100644 --- a/src/coral/coral.zig +++ b/src/coral/coral.zig @@ -1,7 +1,5 @@ pub const ascii = @import("./ascii.zig"); -pub const dag = @import("./dag.zig"); - pub const files = @import("./files.zig"); pub const hashes = @import("./hashes.zig"); @@ -16,296 +14,8 @@ 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, - }, - }), - }; - - return struct { - res: Qualified, - - const Self = @This(); - - pub const State = struct { - res: Qualified, - }; - - pub fn bind(context: system.BindContext) std.mem.Allocator.Error!State { - const thread_restriction_name = switch (thread_restriction) { - .main => "main thread-restricted ", - .none => "" - }; - - 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 init(state: *State) Self { - return .{ - .res = state.res, - }; - } - - pub const thread_restriction = options.thread_restriction; - }; -} - -pub fn Write(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .none, - }); -} - -pub fn WriteBlocking(comptime Value: type) type { - return Resource(Value, .{ - .thread_restriction = .main, - }); -} - -fn parameter_type(comptime Value: type) *const system.Info.Parameter { - const has_state = @hasDecl(Value, "State"); - - 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 { - if (has_state) { - const value_name = @typeName(Value); - - if (!@hasDecl(Value, "bind")) { - @compileError( - "a `bind` declaration on " ++ - value_name ++ - " is requied for parameter types with a `State` 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 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 { - return null; - } - } - - fn init(argument: *anyopaque, state: ?*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 (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(); - } - } - - 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(); - } - } - } - }; - - 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/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/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/World.zig b/src/flow/World.zig similarity index 54% rename from src/coral/World.zig rename to src/flow/World.zig index e7329ac..e0d3650 100644 --- a/src/coral/World.zig +++ b/src/flow/World.zig @@ -1,30 +1,21 @@ const builtin = @import("builtin"); -const heap = @import("./heap.zig"); +const coral = @import("coral"); -const map = @import("./map.zig"); - -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); @@ -48,7 +39,7 @@ pub fn deinit(self: *Self) void { 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 +47,25 @@ 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_resource(self: Self, comptime State: type) ?*State { + return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(states.thread_restriction(State))].get(State))); } -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 set_get_resource(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 +83,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_resource(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..ea35bc0 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 (coral.slices.index_of(edges.values, 0, 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..34b84b9 --- /dev/null +++ b/src/flow/flow.zig @@ -0,0 +1,273 @@ +const std = @import("std"); + +pub const states = @import("./states.zig"); + +pub const system = @import("./system.zig"); + +pub const World = @import("./World.zig"); + +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 State = struct { + res: Qualified, + }; + + pub fn bind(context: system.BindContext) std.mem.Allocator.Error!State { + const thread_restriction_name = switch (info.thread_restriction) { + .main => "main thread-restricted ", + .none => "" + }; + + const res = switch (info.read_only) { + true => (try context.register_readable_resource_access(Value)), + false => (try context.register_writable_resource_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(state: *State) Self { + return .{ + .res = state.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 has_state = @hasDecl(Value, "State"); + + 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 { + if (has_state) { + const value_name = @typeName(Value); + + if (!@hasDecl(Value, "bind")) { + @compileError( + "a `bind` declaration on " ++ + value_name ++ + " is requied for parameter types with a `State` 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 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 { + return null; + } + } + + fn init(argument: *anyopaque, state: ?*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 (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(); + } + } + + 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(); + } + } + } + }; + + 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 76% rename from src/coral/resource.zig rename to src/flow/states.zig index 617c38b..d4a0e2e 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, @@ -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}, }; } @@ -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 83% rename from src/coral/system.zig rename to src/flow/system.zig index 2224690..25dce4d 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,12 +37,12 @@ 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_resource_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*Resource { + const value = self.world.get_resource(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}); @@ -57,26 +51,26 @@ pub const BindContext = struct { const read_write_resource_nodes = lazily_create: { break: lazily_create self.systems.read_write_resource_id_nodes.get_ptr(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).?; }; }; - if (slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) { + if (coral.slices.index_of(read_write_resource_nodes.values, 0, 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_resource_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*const Resource { + const value = self.world.get_resource(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}); @@ -85,14 +79,14 @@ pub const BindContext = struct { const read_only_resource_nodes = lazily_create: { break: lazily_create self.systems.read_only_resource_id_nodes.get_ptr(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).?; }; }; - if (slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) { + if (coral.slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) { try read_only_resource_nodes.push_grow(self.node); } @@ -104,10 +98,10 @@ pub const Info = struct { execute: *const fn ([]const *const Parameter, *const [max_parameters]?*anyopaque) anyerror!void, parameters: [max_parameters]*const Parameter = undefined, parameter_count: u4 = 0, - thread_restriction: World.ThreadRestriction = .none, + thread_restriction: states.ThreadRestriction = .none, pub const Parameter = struct { - thread_restriction: World.ThreadRestriction, + 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) void, @@ -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, @@ -149,19 +143,19 @@ pub const Schedule = struct { label: [:0]u8, dependencies: []Dependency, parameter_states: [max_parameters]?*anyopaque = [_]?*anyopaque{null} ** max_parameters, - resource_accesses: stack.Sequential(ResourceAccess), + 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 { { @@ -198,8 +192,8 @@ pub const Schedule = struct { } 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 +298,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 +379,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}, }; } @@ -432,7 +426,7 @@ pub const Schedule = struct { 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,17 +450,17 @@ 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).?; diff --git a/src/main.zig b/src/main.zig index 6a79895..b49021c 100644 --- a/src/main.zig +++ b/src/main.zig @@ -23,7 +23,7 @@ pub fn main() !void { }); } -fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void { +fn load(display: ona.Write(ona.gfx.Display), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void { display.res.width, display.res.height = .{1280, 720}; actors.res.body_texture = try assets.res.create_from_file(coral.files.bundle, "actor.bmp"); actors.res.quad_mesh_2d = try assets.res.create_quad_mesh_2d(@splat(1)); @@ -44,11 +44,11 @@ fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), asse 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), display: coral.Read(ona.gfx.Display)) !void { +fn render(queue: ona.gfx.Queue, actors: ona.Write(Actors), display: ona.Read(ona.gfx.Display)) !void { try queue.commands.append(.{.target = .{ .texture = actors.res.render_texture.?, .clear_color = .{0, 0, 0, 0}, @@ -85,19 +85,19 @@ fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors), display: coral.Read }}); } -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) * 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_resource(Actors{}); + try world.set_resource(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..e985626 100644 --- a/src/ona/App.zig +++ b/src/ona/App.zig @@ -1,17 +1,19 @@ const coral = @import("coral"); +const flow = @import("flow"); + events: *const Events, target_frame_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..eb0ab3d 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_resource(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 50c973b..8c314ee 100644 --- a/src/ona/gfx.zig +++ b/src/ona/gfx.zig @@ -6,27 +6,35 @@ const device = @import("./gfx/device.zig"); const ext = @import("./ext.zig"); +const flow = @import("flow"); + const formats = @import("./gfx/formats.zig"); const msg = @import("./msg.zig"); const std = @import("std"); -pub const Assets = struct { - context: device.Context, - formats: coral.stack.Sequential(Format), - staging_arena: std.heap.ArenaAllocator, +pub const AssetFormat = struct { + extension: []const u8, + file_desc: *const FileDesc, - pub const Format = struct { - extension: []const u8, - file_desc: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) Error!Desc, - - pub const Error = std.mem.Allocator.Error || coral.files.Error || error { - FormatUnsupported, - }; + pub const Error = std.mem.Allocator.Error || coral.files.Error || error { + FormatUnsupported, }; - pub fn create_from_file(self: *Assets, storage: coral.files.Storage, path: []const u8) (OpenError || Format.Error)!*Handle { + pub const FileDesc = fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) Error!Desc; +}; + +pub const Assets = struct { + context: device.Context, + formats: coral.stack.Sequential(AssetFormat), + staging_arena: std.heap.ArenaAllocator, + + pub fn create_from_file( + self: *Assets, + storage: coral.files.Storage, + path: []const u8, + ) (OpenError || AssetFormat.Error)!*Handle { defer { const max_cache_size = 536870912; @@ -62,6 +70,8 @@ pub const Assets = struct { }, }); } + + pub const thread_restriction = .main; }; pub const Color = @Vector(4, f32); @@ -163,7 +173,7 @@ pub const Queue = struct { command_index: usize, }; - pub fn bind(_: coral.system.BindContext) std.mem.Allocator.Error!State { + pub fn bind(_: flow.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}; @@ -207,7 +217,7 @@ pub const Transform2D = extern struct { origin: Point2D = @splat(0), }; -const builtin_formats = [_]Assets.Format{ +const builtin_formats = [_]AssetFormat{ .{ .extension = "bmp", .file_desc = formats.bmp_file_desc, @@ -228,7 +238,7 @@ pub const colors = struct { } }; -pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void { +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) { @@ -241,7 +251,7 @@ 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; } @@ -250,32 +260,32 @@ pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || st errdefer context.deinit(); - var registered_formats = coral.stack.Sequential(Assets.Format){.allocator = coral.heap.allocator}; + var registered_formats = coral.stack.Sequential(AssetFormat){.allocator = coral.heap.allocator}; errdefer registered_formats.deinit(); try registered_formats.grow(builtin_formats.len); std.debug.assert(registered_formats.push_all(&builtin_formats)); - try world.set_resource(.none, Assets{ + try world.set_resource(Assets{ .staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator), .formats = registered_formats, .context = context, }); - 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"}); + try world.set_resource(Display{}); + 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 { +pub fn stop(assets: flow.Write(Assets)) void { assets.res.staging_arena.deinit(); assets.res.formats.deinit(); assets.res.context.deinit(); } -pub fn synchronize(assets: coral.Write(Assets), display: coral.Read(Display)) !void { +pub fn synchronize(assets: flow.Write(Assets), display: flow.Read(Display)) !void { assets.res.context.submit(.{ .width = display.res.width, .height = display.res.height, diff --git a/src/ona/gfx/device.zig b/src/ona/gfx/device.zig index 679475e..4cfb5dd 100644 --- a/src/ona/gfx/device.zig +++ b/src/ona/gfx/device.zig @@ -2,14 +2,14 @@ const commands = @import("./commands.zig"); const coral = @import("coral"); -const default_2d = @import("./shaders/default_2d.glsl.zig"); - const ext = @import("../ext.zig"); const gfx = @import("../gfx.zig"); const sokol = @import("sokol"); +const spirv = @import("./spirv.zig"); + const std = @import("std"); pub const Context = struct { @@ -125,7 +125,7 @@ pub const Context = struct { }; const Frame = struct { - projection: default_2d.Mat4 = .{@splat(0), @splat(0), @splat(0), @splat(0)}, + projection: Projection = .{@splat(0), @splat(0), @splat(0), @splat(0)}, flushed_batch_count: usize = 0, pushed_batch_count: usize = 0, render_passes: usize = 0, @@ -380,7 +380,7 @@ const Loop = struct { ext.SDL_GL_DeleteContext(context); } - var rendering_2d = Rendering2D.init(); + var rendering_2d = try Rendering2D.init(coral.heap.allocator); defer rendering_2d.deinit(); @@ -455,6 +455,8 @@ const Loop = struct { } }; +const Projection = [4]@Vector(4, f32); + pub const RenderChain = commands.Chain(gfx.Command, clone_command); pub const RenderList = commands.List(gfx.Command, clone_command); @@ -529,10 +531,7 @@ const Rendering2D = struct { } sokol.gfx.applyPipeline(self.batching_pipeline); - - sokol.gfx.applyUniforms(.VS, default_2d.SLOT_Projection, sokol.gfx.asRange(&default_2d.Projection{ - .projection = frame.projection, - })); + sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&frame.projection)); const mesh_2d = resource_cast(frame.mesh.?).payload.mesh_2d; @@ -603,7 +602,7 @@ const Rendering2D = struct { } } - fn init() Rendering2D { + fn init(allocator: std.mem.Allocator) spirv.CompileError!Rendering2D { sokol.gfx.setup(.{ .environment = .{ .defaults = .{ @@ -618,6 +617,18 @@ const Rendering2D = struct { }, }); + var arena = std.heap.ArenaAllocator.init(allocator); + + defer arena.deinit(); + + const shader_desc = try spirv.compile(&arena, .{ + .fragment_spirv = shader_sources.fragment_spirv[0 ..], + .vertex_spirv = shader_sources.vertex_spirv[0 ..], + }); + + std.log.info("{s}\n", .{shader_desc.fs.source}); + std.log.info("{s}", .{shader_desc.vs.source}); + return .{ .batching_pipeline = sokol.gfx.makePipeline(.{ .label = "2D drawing pipeline", @@ -626,42 +637,42 @@ const Rendering2D = struct { .attrs = get: { var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16; - attrs[default_2d.ATTR_vs_mesh_xy] = .{ + attrs[0] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.mesh, }; - attrs[default_2d.ATTR_vs_mesh_uv] = .{ + attrs[1] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.mesh, }; - attrs[default_2d.ATTR_vs_instance_xbasis] = .{ + attrs[2] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_ybasis] = .{ + attrs[3] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_origin] = .{ + attrs[4] = .{ .format = .FLOAT2, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_tint] = .{ + attrs[5] = .{ .format = .UBYTE4N, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_depth] = .{ + attrs[6] = .{ .format = .FLOAT, .buffer_index = buffer_indices.instance, }; - attrs[default_2d.ATTR_vs_instance_rect] = .{ + attrs[7] = .{ .format = .FLOAT4, .buffer_index = buffer_indices.instance, }; @@ -678,7 +689,7 @@ const Rendering2D = struct { }, }, - .shader = sokol.gfx.makeShader(default_2d.draw2dShaderDesc(sokol.gfx.queryBackend())), + .shader = sokol.gfx.makeShader(shader_desc), .index_type = .UINT16, }), @@ -698,7 +709,6 @@ const Rendering2D = struct { var pass = sokol.gfx.Pass{ .action = .{ - // TODO: Review if stencil buffer is needed. .stencil = .{.load_action = .CLEAR}, }, }; @@ -725,7 +735,7 @@ const Rendering2D = struct { const render_target = resource_cast(render_texture).payload.render_target; pass.attachments = render_target.attachments; - frame.projection = ortho_projection(0.0, @floatFromInt(render_target.width), 0.0, @floatFromInt(render_target.height), -1.0, 1.0); + frame.projection = orthographic_projection(0.0, @floatFromInt(render_target.width), 0.0, @floatFromInt(render_target.height), -1.0, 1.0); } else { var target_width, var target_height = [_]c_int{0, 0}; @@ -741,11 +751,127 @@ const Rendering2D = struct { .gl = .{.framebuffer = 0}, }; - frame.projection = ortho_projection(0.0, @floatFromInt(target_width), @floatFromInt(target_height), 0.0, -1.0, 1.0); + frame.projection = orthographic_projection(0.0, @floatFromInt(target_width), @floatFromInt(target_height), 0.0, -1.0, 1.0); } sokol.gfx.beginPass(pass); } + + const shader_sources = .{ + .vertex_spirv = &[_]u32{ + 0x07230203, 0x00010000, 0x0008000b, 0x00000053, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0010000f, 0x00000000, 0x00000004, 0x6e69616d, 0x00000000, 0x0000000b, 0x0000000d, 0x00000013, + 0x0000001a, 0x00000034, 0x00000036, 0x0000003d, 0x0000003f, 0x00000042, 0x0000004c, 0x0000004e, + 0x00030003, 0x00000002, 0x000001ae, 0x00040005, 0x00000004, 0x6e69616d, 0x00000000, 0x00060005, + 0x00000009, 0x6c726f77, 0x6f705f64, 0x69746973, 0x00006e6f, 0x00060005, 0x0000000b, 0x74736e69, + 0x65636e61, 0x69726f5f, 0x006e6967, 0x00040005, 0x0000000d, 0x6873656d, 0x0079785f, 0x00060005, + 0x00000013, 0x74736e69, 0x65636e61, 0x6162785f, 0x00736973, 0x00060005, 0x0000001a, 0x74736e69, + 0x65636e61, 0x6162795f, 0x00736973, 0x00070005, 0x0000001e, 0x6a6f7270, 0x65746365, 0x6f705f64, + 0x69746973, 0x00006e6f, 0x00050005, 0x00000021, 0x6a6f7250, 0x69746365, 0x00006e6f, 0x00080006, + 0x00000021, 0x00000000, 0x6a6f7270, 0x69746365, 0x6d5f6e6f, 0x69727461, 0x00000078, 0x00030005, + 0x00000023, 0x00000000, 0x00060005, 0x00000032, 0x505f6c67, 0x65567265, 0x78657472, 0x00000000, + 0x00060006, 0x00000032, 0x00000000, 0x505f6c67, 0x7469736f, 0x006e6f69, 0x00070006, 0x00000032, + 0x00000001, 0x505f6c67, 0x746e696f, 0x657a6953, 0x00000000, 0x00070006, 0x00000032, 0x00000002, + 0x435f6c67, 0x4470696c, 0x61747369, 0x0065636e, 0x00030005, 0x00000034, 0x00000000, 0x00060005, + 0x00000036, 0x74736e69, 0x65636e61, 0x7065645f, 0x00006874, 0x00040005, 0x0000003d, 0x6f6c6f63, + 0x00000072, 0x00060005, 0x0000003f, 0x74736e69, 0x65636e61, 0x6e69745f, 0x00000074, 0x00050005, + 0x00000041, 0x74636572, 0x736f705f, 0x00000000, 0x00060005, 0x00000042, 0x74736e69, 0x65636e61, + 0x6365725f, 0x00000074, 0x00050005, 0x00000045, 0x74636572, 0x7a69735f, 0x00000065, 0x00030005, + 0x0000004c, 0x00007675, 0x00040005, 0x0000004e, 0x6873656d, 0x0076755f, 0x00040047, 0x0000000b, + 0x0000001e, 0x00000004, 0x00040047, 0x0000000d, 0x0000001e, 0x00000000, 0x00040047, 0x00000013, + 0x0000001e, 0x00000002, 0x00040047, 0x0000001a, 0x0000001e, 0x00000003, 0x00040048, 0x00000021, + 0x00000000, 0x00000005, 0x00050048, 0x00000021, 0x00000000, 0x00000023, 0x00000000, 0x00050048, + 0x00000021, 0x00000000, 0x00000007, 0x00000010, 0x00030047, 0x00000021, 0x00000002, 0x00040047, + 0x00000023, 0x00000022, 0x00000000, 0x00040047, 0x00000023, 0x00000021, 0x00000000, 0x00050048, + 0x00000032, 0x00000000, 0x0000000b, 0x00000000, 0x00050048, 0x00000032, 0x00000001, 0x0000000b, + 0x00000001, 0x00050048, 0x00000032, 0x00000002, 0x0000000b, 0x00000003, 0x00030047, 0x00000032, + 0x00000002, 0x00040047, 0x00000036, 0x0000001e, 0x00000006, 0x00040047, 0x0000003d, 0x0000001e, + 0x00000000, 0x00040047, 0x0000003f, 0x0000001e, 0x00000005, 0x00040047, 0x00000042, 0x0000001e, + 0x00000007, 0x00040047, 0x0000004c, 0x0000001e, 0x00000001, 0x00040047, 0x0000004e, 0x0000001e, + 0x00000001, 0x00020013, 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, + 0x00000020, 0x00040017, 0x00000007, 0x00000006, 0x00000002, 0x00040020, 0x00000008, 0x00000007, + 0x00000007, 0x00040020, 0x0000000a, 0x00000001, 0x00000007, 0x0004003b, 0x0000000a, 0x0000000b, + 0x00000001, 0x0004003b, 0x0000000a, 0x0000000d, 0x00000001, 0x00040015, 0x0000000e, 0x00000020, + 0x00000000, 0x0004002b, 0x0000000e, 0x0000000f, 0x00000000, 0x00040020, 0x00000010, 0x00000001, + 0x00000006, 0x0004003b, 0x0000000a, 0x00000013, 0x00000001, 0x0004002b, 0x0000000e, 0x00000017, + 0x00000001, 0x0004003b, 0x0000000a, 0x0000001a, 0x00000001, 0x00040017, 0x0000001f, 0x00000006, + 0x00000004, 0x00040018, 0x00000020, 0x0000001f, 0x00000004, 0x0003001e, 0x00000021, 0x00000020, + 0x00040020, 0x00000022, 0x00000002, 0x00000021, 0x0004003b, 0x00000022, 0x00000023, 0x00000002, + 0x00040015, 0x00000024, 0x00000020, 0x00000001, 0x0004002b, 0x00000024, 0x00000025, 0x00000000, + 0x00040020, 0x00000026, 0x00000002, 0x00000020, 0x0004002b, 0x00000006, 0x0000002a, 0x00000000, + 0x0004002b, 0x00000006, 0x0000002b, 0x3f800000, 0x0004001c, 0x00000031, 0x00000006, 0x00000017, + 0x0005001e, 0x00000032, 0x0000001f, 0x00000006, 0x00000031, 0x00040020, 0x00000033, 0x00000003, + 0x00000032, 0x0004003b, 0x00000033, 0x00000034, 0x00000003, 0x0004003b, 0x00000010, 0x00000036, + 0x00000001, 0x00040020, 0x0000003b, 0x00000003, 0x0000001f, 0x0004003b, 0x0000003b, 0x0000003d, + 0x00000003, 0x00040020, 0x0000003e, 0x00000001, 0x0000001f, 0x0004003b, 0x0000003e, 0x0000003f, + 0x00000001, 0x0004003b, 0x0000003e, 0x00000042, 0x00000001, 0x00040020, 0x0000004b, 0x00000003, + 0x00000007, 0x0004003b, 0x0000004b, 0x0000004c, 0x00000003, 0x0004003b, 0x0000000a, 0x0000004e, + 0x00000001, 0x00050036, 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, + 0x0004003b, 0x00000008, 0x00000009, 0x00000007, 0x0004003b, 0x00000008, 0x0000001e, 0x00000007, + 0x0004003b, 0x00000008, 0x00000041, 0x00000007, 0x0004003b, 0x00000008, 0x00000045, 0x00000007, + 0x0004003d, 0x00000007, 0x0000000c, 0x0000000b, 0x00050041, 0x00000010, 0x00000011, 0x0000000d, + 0x0000000f, 0x0004003d, 0x00000006, 0x00000012, 0x00000011, 0x0004003d, 0x00000007, 0x00000014, + 0x00000013, 0x0005008e, 0x00000007, 0x00000015, 0x00000014, 0x00000012, 0x00050081, 0x00000007, + 0x00000016, 0x0000000c, 0x00000015, 0x00050041, 0x00000010, 0x00000018, 0x0000000d, 0x00000017, + 0x0004003d, 0x00000006, 0x00000019, 0x00000018, 0x0004003d, 0x00000007, 0x0000001b, 0x0000001a, + 0x0005008e, 0x00000007, 0x0000001c, 0x0000001b, 0x00000019, 0x00050081, 0x00000007, 0x0000001d, + 0x00000016, 0x0000001c, 0x0003003e, 0x00000009, 0x0000001d, 0x00050041, 0x00000026, 0x00000027, + 0x00000023, 0x00000025, 0x0004003d, 0x00000020, 0x00000028, 0x00000027, 0x0004003d, 0x00000007, + 0x00000029, 0x00000009, 0x00050051, 0x00000006, 0x0000002c, 0x00000029, 0x00000000, 0x00050051, + 0x00000006, 0x0000002d, 0x00000029, 0x00000001, 0x00070050, 0x0000001f, 0x0000002e, 0x0000002c, + 0x0000002d, 0x0000002a, 0x0000002b, 0x00050091, 0x0000001f, 0x0000002f, 0x00000028, 0x0000002e, + 0x0007004f, 0x00000007, 0x00000030, 0x0000002f, 0x0000002f, 0x00000000, 0x00000001, 0x0003003e, + 0x0000001e, 0x00000030, 0x0004003d, 0x00000007, 0x00000035, 0x0000001e, 0x0004003d, 0x00000006, + 0x00000037, 0x00000036, 0x00050051, 0x00000006, 0x00000038, 0x00000035, 0x00000000, 0x00050051, + 0x00000006, 0x00000039, 0x00000035, 0x00000001, 0x00070050, 0x0000001f, 0x0000003a, 0x00000038, + 0x00000039, 0x00000037, 0x0000002b, 0x00050041, 0x0000003b, 0x0000003c, 0x00000034, 0x00000025, + 0x0003003e, 0x0000003c, 0x0000003a, 0x0004003d, 0x0000001f, 0x00000040, 0x0000003f, 0x0003003e, + 0x0000003d, 0x00000040, 0x0004003d, 0x0000001f, 0x00000043, 0x00000042, 0x0007004f, 0x00000007, + 0x00000044, 0x00000043, 0x00000043, 0x00000000, 0x00000001, 0x0003003e, 0x00000041, 0x00000044, + 0x0004003d, 0x0000001f, 0x00000046, 0x00000042, 0x0007004f, 0x00000007, 0x00000047, 0x00000046, + 0x00000046, 0x00000002, 0x00000003, 0x0004003d, 0x0000001f, 0x00000048, 0x00000042, 0x0007004f, + 0x00000007, 0x00000049, 0x00000048, 0x00000048, 0x00000000, 0x00000001, 0x00050083, 0x00000007, + 0x0000004a, 0x00000047, 0x00000049, 0x0003003e, 0x00000045, 0x0000004a, 0x0004003d, 0x00000007, + 0x0000004d, 0x00000041, 0x0004003d, 0x00000007, 0x0000004f, 0x0000004e, 0x0004003d, 0x00000007, + 0x00000050, 0x00000045, 0x00050085, 0x00000007, 0x00000051, 0x0000004f, 0x00000050, 0x00050081, + 0x00000007, 0x00000052, 0x0000004d, 0x00000051, 0x0003003e, 0x0000004c, 0x00000052, 0x000100fd, + 0x00010038, + }, + + .fragment_spirv = &[_]u32{ + 0x07230203, 0x00010000, 0x0008000b, 0x00000028, 0x00000000, 0x00020011, 0x00000001, 0x0006000b, + 0x00000001, 0x4c534c47, 0x6474732e, 0x3035342e, 0x00000000, 0x0003000e, 0x00000000, 0x00000001, + 0x0008000f, 0x00000004, 0x00000004, 0x6e69616d, 0x00000000, 0x00000009, 0x00000016, 0x0000001a, + 0x00030010, 0x00000004, 0x00000007, 0x00030003, 0x00000002, 0x000001ae, 0x00040005, 0x00000004, + 0x6e69616d, 0x00000000, 0x00040005, 0x00000009, 0x65786574, 0x0000006c, 0x00030005, 0x0000000c, + 0x00786574, 0x00030005, 0x00000010, 0x00706d73, 0x00030005, 0x00000016, 0x00007675, 0x00040005, + 0x0000001a, 0x6f6c6f63, 0x00000072, 0x00040047, 0x00000009, 0x0000001e, 0x00000000, 0x00040047, + 0x0000000c, 0x00000022, 0x00000000, 0x00040047, 0x0000000c, 0x00000021, 0x00000000, 0x00040047, + 0x00000010, 0x00000022, 0x00000000, 0x00040047, 0x00000010, 0x00000021, 0x00000000, 0x00040047, + 0x00000016, 0x0000001e, 0x00000001, 0x00040047, 0x0000001a, 0x0000001e, 0x00000000, 0x00020013, + 0x00000002, 0x00030021, 0x00000003, 0x00000002, 0x00030016, 0x00000006, 0x00000020, 0x00040017, + 0x00000007, 0x00000006, 0x00000004, 0x00040020, 0x00000008, 0x00000003, 0x00000007, 0x0004003b, + 0x00000008, 0x00000009, 0x00000003, 0x00090019, 0x0000000a, 0x00000006, 0x00000001, 0x00000000, + 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00040020, 0x0000000b, 0x00000000, 0x0000000a, + 0x0004003b, 0x0000000b, 0x0000000c, 0x00000000, 0x0002001a, 0x0000000e, 0x00040020, 0x0000000f, + 0x00000000, 0x0000000e, 0x0004003b, 0x0000000f, 0x00000010, 0x00000000, 0x0003001b, 0x00000012, + 0x0000000a, 0x00040017, 0x00000014, 0x00000006, 0x00000002, 0x00040020, 0x00000015, 0x00000001, + 0x00000014, 0x0004003b, 0x00000015, 0x00000016, 0x00000001, 0x00040020, 0x00000019, 0x00000001, + 0x00000007, 0x0004003b, 0x00000019, 0x0000001a, 0x00000001, 0x00040015, 0x0000001d, 0x00000020, + 0x00000000, 0x0004002b, 0x0000001d, 0x0000001e, 0x00000003, 0x00040020, 0x0000001f, 0x00000003, + 0x00000006, 0x0004002b, 0x00000006, 0x00000022, 0x00000000, 0x00020014, 0x00000023, 0x00050036, + 0x00000002, 0x00000004, 0x00000000, 0x00000003, 0x000200f8, 0x00000005, 0x0004003d, 0x0000000a, + 0x0000000d, 0x0000000c, 0x0004003d, 0x0000000e, 0x00000011, 0x00000010, 0x00050056, 0x00000012, + 0x00000013, 0x0000000d, 0x00000011, 0x0004003d, 0x00000014, 0x00000017, 0x00000016, 0x00050057, + 0x00000007, 0x00000018, 0x00000013, 0x00000017, 0x0004003d, 0x00000007, 0x0000001b, 0x0000001a, + 0x00050085, 0x00000007, 0x0000001c, 0x00000018, 0x0000001b, 0x0003003e, 0x00000009, 0x0000001c, + 0x00050041, 0x0000001f, 0x00000020, 0x00000009, 0x0000001e, 0x0004003d, 0x00000006, 0x00000021, + 0x00000020, 0x000500b4, 0x00000023, 0x00000024, 0x00000021, 0x00000022, 0x000300f7, 0x00000026, + 0x00000000, 0x000400fa, 0x00000024, 0x00000025, 0x00000026, 0x000200f8, 0x00000025, 0x000100fc, + 0x000200f8, 0x00000026, 0x000100fd, 0x00010038, + }, + }; }; const Resource = struct { @@ -805,19 +931,19 @@ fn clone_command(self: gfx.Command, arena: *std.heap.ArenaAllocator) std.mem.All }; } +fn orthographic_projection(left: f32, right: f32, bottom: f32, top: f32, zNear: f32, zFar: f32) Projection { + const width = right - left; + const height = top - bottom; + const depth = zFar - zNear; + + return .{ + .{2 / width, 0, 0, 0}, + .{0, 2 / height, 0, 0}, + .{0, 0, -(2 / depth), 0}, + .{-((right + left) / width), -((top + bottom) / height), -((zFar + zNear) / depth), 1}, + }; +} + fn resource_cast(handle: *gfx.Handle) *Resource { return @ptrCast(@alignCast(handle)); } - -fn ortho_projection(left: f32, right: f32, bottom: f32, top: f32, zNear: f32, zFar: f32) default_2d.Mat4 { - var result = default_2d.Mat4{.{1, 0, 0, 0}, .{0, 1, 0, 0}, .{0, 0, 1, 0}, .{0, 0, 0, 1}}; - - result[0][0] = 2 / (right - left); - result[1][1] = 2 / (top - bottom); - result[2][2] = - 2 / (zFar - zNear); - result[3][0] = - (right + left) / (right - left); - result[3][1] = - (top + bottom) / (top - bottom); - result[3][2] = - (zFar + zNear) / (zFar - zNear); - - return result; -} diff --git a/src/ona/gfx/formats.zig b/src/ona/gfx/formats.zig index a767d7f..98d5952 100644 --- a/src/ona/gfx/formats.zig +++ b/src/ona/gfx/formats.zig @@ -8,7 +8,7 @@ pub fn bmp_file_desc( arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8, -) gfx.Assets.Format.Error!gfx.Desc { +) gfx.AssetFormat.Error!gfx.Desc { const header = try storage.read_little(path, 0, extern struct { type: [2]u8 align (1), file_size: u32 align (1), diff --git a/src/ona/gfx/shaders/default_2d.glsl b/src/ona/gfx/shaders/default_2d.glsl deleted file mode 100644 index 111abec..0000000 --- a/src/ona/gfx/shaders/default_2d.glsl +++ /dev/null @@ -1,60 +0,0 @@ -@header pub const Vec2 = @Vector(2, f32) -@header pub const Mat4 = [4]@Vector(4, f32) -@ctype vec2 Vec2 -@ctype mat4 Mat4 - -@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 Projection { - mat4 projection; -}; - -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; - const vec2 projected_position = (projection * vec4(world_position, 0, 1)).xy; - - // Set the position of the vertex in clip space - gl_Position = vec4(projected_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/shaders/render_2d.frag b/src/ona/gfx/shaders/render_2d.frag new file mode 100644 index 0000000..a059a43 --- /dev/null +++ b/src/ona/gfx/shaders/render_2d.frag @@ -0,0 +1,17 @@ +#version 430 + +layout (binding = 0) uniform texture2D tex; +layout (binding = 0) uniform sampler smp; + +layout (location = 0) in vec4 color; +layout (location = 1) in vec2 uv; + +layout (location = 0) out vec4 texel; + +void main() { + texel = texture(sampler2D(tex, smp), uv) * color; + + if (texel.a == 0) { + discard; + } +} diff --git a/src/ona/gfx/shaders/render_2d.vert b/src/ona/gfx/shaders/render_2d.vert new file mode 100644 index 0000000..cea5831 --- /dev/null +++ b/src/ona/gfx/shaders/render_2d.vert @@ -0,0 +1,36 @@ +#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 (binding = 0) uniform Projection { + mat4 projection_matrix; +}; + +layout (location = 0) out vec4 color; +layout (location = 1) 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; + const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy; + + // Set the position of the vertex in clip space + gl_Position = vec4(projected_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); +} + diff --git a/src/ona/gfx/spirv.zig b/src/ona/gfx/spirv.zig new file mode 100644 index 0000000..857f4c2 --- /dev/null +++ b/src/ona/gfx/spirv.zig @@ -0,0 +1,130 @@ +const coral = @import("coral"); + +const ext = @cImport({ + @cInclude("spirv-cross/spirv_cross_c.h"); +}); + +const sokol = @import("sokol"); + +const std = @import("std"); + +pub const CompileError = std.mem.Allocator.Error || error { + UnsupportedTarget, + InvalidSpirV, + UnsupportedSpirv, + UnknownFailure, +}; + +pub const Sources = struct { + vertex_spirv: []const u32, + fragment_spirv: []const u32, +}; + +pub fn compile(arena: *std.heap.ArenaAllocator, sources: Sources) CompileError!sokol.gfx.ShaderDesc { + var context = @as(ext.spvc_context, null); + + try switch (ext.spvc_context_create(&context)) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + ext.spvc_context_set_error_callback(context, log_errors, null); + + defer ext.spvc_context_destroy(context); + + const arena_allocator = arena.allocator(); + const shader_source_sentinel = @as(u8, 0); + + return .{ + .vs = .{ + .source = try arena_allocator.dupeZ(u8, coral.io.slice_sentineled( + shader_source_sentinel, + @ptrCast(try compile_shader(context, ext.SpvExecutionModelVertex, sources.vertex_spirv)))), + }, + + .fs = .{ + .source = try arena_allocator.dupeZ(u8, coral.io.slice_sentineled( + shader_source_sentinel, + @ptrCast(try compile_shader(context, ext.SpvExecutionModelFragment, sources.fragment_spirv)))), + }, + }; +} + +fn compile_shader(context: ext.spvc_context, model: ext.SpvExecutionModel, spirv: []const u32) CompileError![*]const u8 { + var parsed_ir = @as(ext.spvc_parsed_ir, null); + + try switch (ext.spvc_context_parse_spirv(context, spirv.ptr, spirv.len, &parsed_ir)) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_ERROR_INVALID_SPIRV => error.InvalidSpirV, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + var compiler = @as(ext.spvc_compiler, null); + + try switch (ext.spvc_context_create_compiler(context, ext.SPVC_BACKEND_GLSL, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler)) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_ERROR_INVALID_ARGUMENT => error.UnsupportedTarget, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_build_combined_image_samplers(compiler)) { + ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSpirv, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_set_entry_point(compiler, "main", model)) { + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + var options = @as(ext.spvc_compiler_options, null); + + try switch (ext.spvc_compiler_create_compiler_options(compiler, &options)) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_options_set_uint(options, ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430)) { + ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_options_set_bool(options, ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false))) { + ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_options_set_bool(options, ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false))) { + ext.SPVC_ERROR_INVALID_ARGUMENT => error.InvalidSpirV, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + try switch (ext.spvc_compiler_install_compiler_options(compiler, options)) { + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + var source = @as([*]const u8, undefined); + + try switch (ext.spvc_compiler_compile(compiler, @ptrCast(&source))) { + ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory, + ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSpirv, + ext.SPVC_SUCCESS => {}, + else => error.UnknownFailure, + }; + + return 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}); +} diff --git a/src/ona/msg.zig b/src/ona/msg.zig index 54d2f1b..f90b225 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) { @@ -65,12 +67,12 @@ pub fn Receive(comptime Message: type) type { 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!State { 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_resource_access(TypedChannel)) orelse set: { + try context.world.set_resource(TypedChannel.init(coral.heap.allocator)); - break: set (try context.register_read_only_resource_access(thread_restriction, TypedChannel)).?; + break: set (try context.register_readable_resource_access(TypedChannel)).?; }, }; } @@ -99,22 +101,22 @@ pub fn Send(comptime Message: type) type { 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!State { 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_resource_access(TypedChannel)) orelse set: { + try context.world.set_resource(TypedChannel.init(coral.heap.allocator)); - const app = context.world.get_resource(.none, App).?; + const app = context.world.get_resource(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_resource_access(TypedChannel)).?; }, }; } @@ -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..c3b51ea 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,7 +72,7 @@ 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_resource(App{ .events = &events, .target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)), .is_running = true, @@ -101,3 +109,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