From b0c34d11b6227e7485128bbfd1b1af4f2f8b4f52 Mon Sep 17 00:00:00 2001 From: kayomn Date: Mon, 20 Nov 2023 21:39:42 +0000 Subject: [PATCH] First pass of Ona middleware API --- build.zig | 1 + debug/app.ona | 10 +- source/coral/math.zig | 11 + source/coral/utf8.zig | 1 + source/ona/gfx/canvas.zig | 0 source/ona/gfx/lina.zig | 181 ---------- source/ona/ona.zig | 403 +++++++++-------------- source/ona/{kym.zig => script.zig} | 194 +++++------ source/ona/{kym => script}/Chunk.zig | 109 +++--- source/ona/{kym => script}/Table.zig | 22 +- source/ona/{kym => script}/tokens.zig | 0 source/ona/{kym => script}/tree.zig | 0 source/ona/{kym => script}/tree/Expr.zig | 0 source/ona/{kym => script}/tree/Stmt.zig | 0 source/runner.zig | 182 +++++++++- 15 files changed, 522 insertions(+), 592 deletions(-) delete mode 100644 source/ona/gfx/canvas.zig delete mode 100644 source/ona/gfx/lina.zig rename source/ona/{kym.zig => script.zig} (92%) rename source/ona/{kym => script}/Chunk.zig (89%) rename source/ona/{kym => script}/Table.zig (76%) rename source/ona/{kym => script}/tokens.zig (100%) rename source/ona/{kym => script}/tree.zig (100%) rename source/ona/{kym => script}/tree/Expr.zig (100%) rename source/ona/{kym => script}/tree/Stmt.zig (100%) diff --git a/build.zig b/build.zig index 4ff8fb2..c0ca844 100644 --- a/build.zig +++ b/build.zig @@ -25,6 +25,7 @@ pub fn build(b: *std.Build) void { .optimize = optimize, }); + compile_step.addModule("coral", coral_module); compile_step.addModule("ona", ona_module); compile_step.linkLibC(); compile_step.linkSystemLibrary("SDL2"); diff --git a/debug/app.ona b/debug/app.ona index a3db98d..4e22ec7 100644 --- a/debug/app.ona +++ b/debug/app.ona @@ -1,12 +1,12 @@ -let ona = @import("ona") +let events = @import("events") var timer = 0 -ona.on_update(lambda (dt): +events.on_update(lambda (dt): timer = timer + dt if (timer > 1): - @print("test hello world") + @print("test") timer = 0 end @@ -14,7 +14,7 @@ end) return { .title = "Game", - .width = 1280, - .height = 800, + .res_width = 1280, + .res_height = 800, .tick_rate = 60, } diff --git a/source/coral/math.zig b/source/coral/math.zig index 360da13..eef2869 100644 --- a/source/coral/math.zig +++ b/source/coral/math.zig @@ -14,6 +14,17 @@ pub fn checked_add(a: anytype, b: anytype) ?@TypeOf(a + b) { return if (result.@"1" == 0) result.@"0" else null; } +pub fn checked_cast(comptime dest_int: std.builtin.Type.Int, value: anytype) ?@Type(.{.Int = dest_int}) { + const dest_min = min_int(dest_int); + const dest_max = max_int(dest_int); + + if (value < dest_min or value > dest_max) { + return null; + } + + return @intCast(value); +} + pub fn checked_mul(a: anytype, b: anytype) ?@TypeOf(a * b) { const result = @mulWithOverflow(a, b); diff --git a/source/coral/utf8.zig b/source/coral/utf8.zig index c6d82b6..287b755 100644 --- a/source/coral/utf8.zig +++ b/source/coral/utf8.zig @@ -332,6 +332,7 @@ noinline fn print_value(writer: io.Writer, value: anytype) ?usize { return switch (@typeInfo(Value)) { .Int => DecimalFormat.default.print(writer, value), .Float => DecimalFormat.default.print(writer, value), + .ComptimeInt => print_value(writer, @as(@import("std").math.IntFittingRange(value, value), value)), .Pointer => |pointer| switch (pointer.size) { .Many, .C => HexadecimalFormat.default.print(writer, @intFromPtr(value)), diff --git a/source/ona/gfx/canvas.zig b/source/ona/gfx/canvas.zig deleted file mode 100644 index e69de29..0000000 diff --git a/source/ona/gfx/lina.zig b/source/ona/gfx/lina.zig deleted file mode 100644 index 8022321..0000000 --- a/source/ona/gfx/lina.zig +++ /dev/null @@ -1,181 +0,0 @@ - -pub const Vector2 = struct { - x: f32, - y: f32, - - pub const Scalars = [2]f32; - - pub fn equals(self: Vector2, vector: Vector2) bool { - return self.x == vector.x and self.y == vector.y; - } - - pub fn from_scalar(scalar: f32) Vector2 { - return .{ - .x = scalar, - .y = scalar, - }; - } - - pub fn from_scalars(scalars: Scalars) Vector2 { - return .{ - .x = scalars[0], - .y = scalars[1], - }; - } - - pub fn scalar_added(self: Vector2, scalar: f32) Vector2 { - return .{ - .x = self.x + scalar, - .y = self.y + scalar, - }; - } - - pub fn scalar_divided(self: Vector2, scalar: f32) Vector2 { - return .{ - .x = self.x / scalar, - .y = self.y / scalar, - }; - } - - pub fn scalar_multiplied(self: Vector2, scalar: f32) Vector2 { - return .{ - .x = self.x * scalar, - .y = self.y * scalar, - }; - } - - pub fn to_scalars(self: Vector2) Scalars { - return .{self.x, self.y}; - } - - pub fn scalar_subtracted(self: Vector2, scalar: f32) Vector2 { - return .{ - .x = self.x - scalar, - .y = self.y - scalar, - }; - } - - pub fn vector_added(self: Vector2, vector: Vector2) Vector2 { - return .{ - .x = self.x + vector.x, - .y = self.y + vector.y, - }; - } - - pub fn vector_divided(self: Vector2, vector: Vector2) Vector2 { - return .{ - .x = self.x / vector.x, - .y = self.y / vector.y, - }; - } - - pub fn vector_multiplied(self: Vector2, vector: Vector2) Vector2 { - return .{ - .x = self.x * vector.x, - .y = self.y * vector.y, - }; - } - - pub fn vector_subtracted(self: Vector2, vector: Vector2) Vector2 { - return .{ - .x = self.x - vector.x, - .y = self.y - vector.y, - }; - } -}; - -pub const Vector3 = struct { - x: f32, - y: f32, - z: f32, - - pub const Scalars = [3]f32; - - pub fn equals(self: Vector3, vector: Vector3) bool { - return self.x == vector.x and self.y == vector.y and self.z == vector.z; - } - - pub fn from_scalar(scalar: f32) Vector3 { - return .{ - .x = scalar, - .y = scalar, - .z = scalar, - }; - } - - pub fn from_scalars(scalars: Scalars) Vector3 { - return .{ - .x = scalars[0], - .y = scalars[1], - .z = scalars[2], - }; - } - - pub fn scalar_added(self: Vector3, scalar: f32) Vector3 { - return .{ - .x = self.x + scalar, - .y = self.y + scalar, - .z = self.z + scalar, - }; - } - - pub fn scalar_divided(self: Vector3, scalar: f32) Vector3 { - return .{ - .x = self.x / scalar, - .y = self.y / scalar, - .z = self.z / scalar, - }; - } - - pub fn scalar_multiplied(self: Vector3, scalar: f32) Vector3 { - return .{ - .x = self.x * scalar, - .y = self.y * scalar, - .z = self.z * scalar, - }; - } - - pub fn scalar_subtracted(self: Vector3, scalar: f32) Vector3 { - return .{ - .x = self.x - scalar, - .y = self.y - scalar, - .z = self.z - scalar, - }; - } - - pub fn to_scalars(self: Vector3) Scalars { - return .{self.x, self.y, self.z}; - } - - pub fn vector_added(self: Vector3, other: Vector3) Vector3 { - return .{ - .x = self.x + other.x, - .y = self.y + other.y, - .z = self.z + other.z, - }; - } - - pub fn vector_divided(self: Vector3, other: Vector3) Vector3 { - return .{ - .x = self.x / other.x, - .y = self.y / other.y, - .z = self.z / other.z, - }; - } - - pub fn vector_multiplied(self: Vector3, other: Vector3) Vector3 { - return .{ - .x = self.x * other.x, - .y = self.y * other.y, - .z = self.z * other.z, - }; - } - - pub fn vector_subtracted(self: Vector3, other: Vector3) Vector3 { - return .{ - .x = self.x - other.x, - .y = self.y - other.y, - .z = self.z - other.z, - }; - } -}; diff --git a/source/ona/ona.zig b/source/ona/ona.zig index b875c5e..ac150c2 100644 --- a/source/ona/ona.zig +++ b/source/ona/ona.zig @@ -4,230 +4,182 @@ const ext = @import("./ext.zig"); pub const file = @import("./file.zig"); -const heap = @import("./heap.zig"); +pub const heap = @import("./heap.zig"); -const kym = @import("./kym.zig"); +pub const script = @import("./script.zig"); -const App = struct { - renderer: *ext.SDL_Renderer, - window: *ext.SDL_Window, - systems: Systems, - target_frame_time: f64, +pub const GraphicsLoop = opaque { + const Internal = struct { + window: *ext.SDL_Window, + renderer: *ext.SDL_Renderer, + loaders: GraphicsLoaderList, + updaters: GraphicsUpdaterList, + target_frame_time: f64, - keys: struct { - pressed: [ext.SDL_NUM_SCANCODES]bool = [_]bool{false} ** ext.SDL_NUM_SCANCODES, - released: [ext.SDL_NUM_SCANCODES]bool = [_]bool{false} ** ext.SDL_NUM_SCANCODES, - held: [ext.SDL_NUM_SCANCODES]bool = [_]bool{false} ** ext.SDL_NUM_SCANCODES, - }, + keys: struct { + pressed: [ext.SDL_NUM_SCANCODES]bool = [_]bool{false} ** ext.SDL_NUM_SCANCODES, + released: [ext.SDL_NUM_SCANCODES]bool = [_]bool{false} ** ext.SDL_NUM_SCANCODES, + held: [ext.SDL_NUM_SCANCODES]bool = [_]bool{false} ** ext.SDL_NUM_SCANCODES, + }, - const DynamicObject = struct { - self: *App, + const GraphicsLoaderList = coral.list.Stack(GraphicsLoader); - fn get(context: kym.Typeinfo.GetContext) kym.RuntimeError!?*kym.RuntimeObj { - const index = context.get_index(); + const GraphicsUpdaterList = coral.list.Stack(GraphicsUpdater); - defer context.env.release(index); - - const on_update_symbol = (try context.env.new_symbol("on_update")).pop().?; - - defer context.env.release(on_update_symbol); - - if (index.equals(on_update_symbol)) { - return (try context.env.new_syscall(on_update)).pop(); - } - - return null; + fn deinit(self: *Internal) void { + ext.SDL_DestroyWindow(self.window); + ext.SDL_DestroyRenderer(self.renderer); } - fn on_update(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { - const caller_object = try env.expect_object(try env.get_caller()); + fn init(allocator: coral.io.Allocator) coral.io.AllocationError!Internal { + const window = create: { + const height = 480; + const width = 640; + const pos = ext.SDL_WINDOWPOS_CENTERED; + const flags = ext.SDL_WINDOW_HIDDEN; - defer env.release(caller_object); - - const app = @as(*DynamicObject, @ptrCast(@alignCast(try env.expect_dynamic(caller_object, typeinfo)))); - - const callable = try env.expect_object((try env.arg(0)).pop()); - - errdefer env.release(callable); - - try app.self.systems.push_one(callable); - - return null; - } - - const typeinfo = &kym.Typeinfo{ - .name = "ona", - .size = @sizeOf(DynamicObject), - .get = get, - }; - }; - - pub const InitError = error { - RendererUnavailable, - WindowUnavailable, - }; - - const Systems = coral.list.Stack(*kym.RuntimeObj); - - pub fn as_importer(self: *App) kym.Importer { - return kym.Importer.bind(App, self, import); - } - - pub fn deinit(self: *App) void { - ext.SDL_DestroyWindow(self.window); - ext.SDL_DestroyRenderer(self.renderer); - self.systems.deinit(); - } - - fn import(self: *App, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { - return (try env.new_dynamic(DynamicObject.typeinfo, DynamicObject{.self = self})).pop(); - } - - pub fn init() InitError!App { - const window = create: { - const height = 480; - const width = 640; - const pos = ext.SDL_WINDOWPOS_CENTERED; - const flags = ext.SDL_WINDOW_HIDDEN; - - break: create ext.SDL_CreateWindow("", pos, pos, width, height, flags) orelse { - return error.WindowUnavailable; - }; - }; - - errdefer ext.SDL_DestroyWindow(window); - - const renderer = create: { - const default_driver_index = -1; - const flags = ext.SDL_RENDERER_ACCELERATED; - - break: create ext.SDL_CreateRenderer(window, default_driver_index, flags) orelse { - return error.RendererUnavailable; - }; - }; - - errdefer ext.SDL_DestroyRenderer(renderer); - - return .{ - .systems = Systems.init(heap.allocator), - .target_frame_time = 1.0 / 60.0, - .renderer = renderer, - .window = window, - .keys = .{}, - }; - } - - pub fn load(self: *App, env: *kym.RuntimeEnv) kym.RuntimeError!void { - var previous_width = @as(c_int, 0); - var previous_height = @as(c_int, 0); - - ext.SDL_GetWindowSize(self.window, &previous_width, &previous_height); - - var width = previous_width; - var height = previous_height; - - defer { - const pos = ext.SDL_WINDOWPOS_CENTERED; - - ext.SDL_SetWindowSize(self.window, width, height); - ext.SDL_SetWindowPosition(self.window, pos, pos); - ext.SDL_ShowWindow(self.window); - } - - if ((try env.import(file.Path.of(&.{"app.ona"}))).pop()) |manifest| { - defer env.release(manifest); - - if (try kym.get_field(env, manifest, "width")) |object| { - defer env.release(object); - - const fixed = try env.expect_fixed(object); - - if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(width)).Int)) { - width = @intCast(fixed); - } - } - - if (try kym.get_field(env, manifest, "height")) |object| { - defer env.release(object); - - const fixed = try env.expect_fixed(object); - - if (fixed > 0 and fixed < coral.math.max_int(@typeInfo(@TypeOf(height)).Int)) { - height = @intCast(fixed); - } - } - - if (try kym.get_field(env, manifest, "tick_rate")) |object| { - defer env.release(object); - - self.target_frame_time = 1.0 / try env.expect_float(object); - } - - if (try kym.get_field(env, manifest, "title")) |object| { - defer env.release(object); - - const title = coral.utf8.SmallString.from_units(try env.expect_string(object)) catch |from_error| { - return switch (from_error) { - error.InvalidUtf8 => env.raise(error.TypeMismatch, "`title` cannot contain invalid utf8", .{}), - error.TooBig => env.raise(error.TypeMismatch, "`title` is too long", .{}), - }; + break: create ext.SDL_CreateWindow("", pos, pos, width, height, flags) orelse { + return error.OutOfMemory; }; + }; - ext.SDL_SetWindowTitle(self.window, title.to_units()); - } else { - ext.SDL_SetWindowTitle(self.window, "Ona"); - } + errdefer ext.SDL_DestroyWindow(window); + + const renderer = create: { + const default_driver_index = -1; + const flags = ext.SDL_RENDERER_ACCELERATED; + + break: create ext.SDL_CreateRenderer(window, default_driver_index, flags) orelse { + return error.OutOfMemory; + }; + }; + + errdefer ext.SDL_DestroyRenderer(renderer); + + return .{ + .loaders = GraphicsLoaderList.init(allocator), + .updaters = GraphicsUpdaterList.init(allocator), + .target_frame_time = 1.0 / 60.0, + .window = window, + .renderer = renderer, + .keys = .{}, + }; } - } - pub fn poll(self: *App) bool { - var event = @as(ext.SDL_Event, undefined); + fn run(self: *Internal) void { + var ticks_previous = ext.SDL_GetTicks64(); + var accumulated_time = @as(f64, 0); - coral.io.zero(@ptrCast(&self.keys.pressed)); - coral.io.zero(@ptrCast(&self.keys.released)); + for (self.loaders.values) |loader| { + loader.invoke(@ptrCast(self)); + } - while (ext.SDL_PollEvent(&event) != 0) { - switch (event.type) { - ext.SDL_QUIT => return false, + ext.SDL_ShowWindow(self.window); - ext.SDL_KEYDOWN => { - self.keys.pressed[event.key.keysym.scancode] = true; - self.keys.held[event.key.keysym.scancode] = true; - }, + running_loop: while (true) { + const ticks_current = ext.SDL_GetTicks64(); + const milliseconds_per_second = 1000.0; + const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; - ext.SDL_KEYUP => { - self.keys.released[event.key.keysym.scancode] = true; - self.keys.held[event.key.keysym.scancode] = false; - }, + ticks_previous = ticks_current; + accumulated_time += delta_time; - else => {}, + while (accumulated_time >= self.target_frame_time) : (accumulated_time -= self.target_frame_time) { + var event = @as(ext.SDL_Event, undefined); + + coral.io.zero(@ptrCast(&self.keys.pressed)); + coral.io.zero(@ptrCast(&self.keys.released)); + + while (ext.SDL_PollEvent(&event) != 0) { + switch (event.type) { + ext.SDL_QUIT => break: running_loop, + + ext.SDL_KEYDOWN => { + self.keys.pressed[event.key.keysym.scancode] = true; + self.keys.held[event.key.keysym.scancode] = true; + }, + + ext.SDL_KEYUP => { + self.keys.released[event.key.keysym.scancode] = true; + self.keys.held[event.key.keysym.scancode] = false; + }, + + else => {}, + } + } + + for (self.updaters.values) |updater| { + updater.invoke(.{ + .loop = @ptrCast(self), + .delta_time = self.target_frame_time, + }); + } + } + + _ = ext.SDL_SetRenderDrawColor(self.renderer, 0, 0, 0, 255); + _ = ext.SDL_RenderClear(self.renderer); + _ = ext.SDL_RenderPresent(self.renderer); + + ext.SDL_Delay(1); } } - return true; - } - - pub fn render(self: *App) void { - _ = ext.SDL_SetRenderDrawColor(self.renderer, 0, 0, 0, 255); - _ = ext.SDL_RenderClear(self.renderer); - _ = ext.SDL_RenderPresent(self.renderer); - } - - pub fn update(self: *App, env: *kym.RuntimeEnv, time_delta: f32) kym.RuntimeError!void { - for (self.systems.values) |system| { - (try (try (try env.new_float(time_delta)).push(system)).call(1, null)).discard(); + fn use(self: *Internal, middleware: GraphicsMiddleware) coral.io.AllocationError!void { + try switch (middleware) { + .loader => |loader| self.loaders.push_one(loader), + .updater => |updater| self.updaters.push_one(updater), + }; } + }; + + fn internal(self: *GraphicsLoop) *Internal { + return @ptrCast(@alignCast(self)); } + + pub fn is_key_pressed(self: *GraphicsLoop, key: Key) bool { + return self.internal().keys.pressed[@intFromEnum(key)]; + } + + pub fn set_resolution(self: *GraphicsLoop, width: u16, height: u16) callconv(.C) void { + const pos = ext.SDL_WINDOWPOS_CENTERED; + const internal_state = self.internal(); + + ext.SDL_SetWindowSize(internal_state.window, width, height); + ext.SDL_SetWindowPosition(internal_state.window, pos, pos); + ext.SDL_ShowWindow(internal_state.window); + } + + pub fn set_tick_rate(self: *GraphicsLoop, tick_rate: f64) callconv(.C) void { + self.internal().target_frame_time = 1.0 / tick_rate; + } + + pub fn set_title(self: *GraphicsLoop, title: coral.utf8.SmallString) callconv(.C) void { + ext.SDL_SetWindowTitle(self.internal().window, title.to_units()); + } +}; + +pub const GraphicsUpdate = struct { + loop: *GraphicsLoop, + delta_time: f64, +}; + +pub const GraphicsMiddleware = union (enum) { + loader: GraphicsLoader, + updater: GraphicsUpdater, +}; + +pub const GraphicsLoader = coral.io.Generator(void, *GraphicsLoop); + +pub const GraphicsUpdater = coral.io.Generator(void, GraphicsUpdate); + +pub const Key = enum (usize) { + f5 = ext.SDL_SCANCODE_F5, }; fn last_sdl_error() [:0]const u8 { return coral.io.slice_sentineled(@as(u8, 0), @as([*:0]const u8, @ptrCast(ext.SDL_GetError()))); } -fn load_prompt() void { - -} - pub fn log_info(message: []const coral.io.Byte) void { ext.SDL_LogInfo( ext.SDL_LOG_CATEGORY_APPLICATION, @@ -255,59 +207,32 @@ pub fn log_fail(message: []const coral.io.Byte) void { ); } -const milliseconds_per_second = 1000; +pub fn start_graphics(middlewares: []const GraphicsMiddleware) void { + const init_flags = ext.SDL_INIT_EVERYTHING; -pub fn run_app(file_access: file.Access) void { - defer heap.trace_leaks(); + if (ext.SDL_WasInit(init_flags) == init_flags) { + return log_fail("a loop is already running"); + } - if (ext.SDL_Init(ext.SDL_INIT_EVERYTHING) != 0) { + if (ext.SDL_Init(init_flags) != 0) { return log_fail(last_sdl_error()); } defer ext.SDL_Quit(); - var app = App.init() catch |init_error| { - return log_fail(switch (init_error) { - error.WindowUnavailable => "unable to acquire window", - error.RendererUnavailable => "unable to acquire renderer", - }); + var loop_internal = GraphicsLoop.Internal.init(heap.allocator) catch { + return log_fail(last_sdl_error()); }; - defer app.deinit(); + defer loop_internal.deinit(); - var script_env = kym.RuntimeEnv.init(heap.allocator, 255, .{ - .import_access = file_access, - .print_out = log_info, - .print_err = log_fail, - }) catch { - return log_fail("failed to initialize script runtime"); - }; - - defer script_env.deinit(); - - script_env.override_import(file.Path.of(&.{"ona"}), app.as_importer()) catch return; - - app.load(&script_env) catch load_prompt(); - - var ticks_previous = ext.SDL_GetTicks64(); - var accumulated_time = @as(f64, 0); - - running_loop: while (true) { - const ticks_current = ext.SDL_GetTicks64(); - const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; - - ticks_previous = ticks_current; - accumulated_time += delta_time; - - while (accumulated_time >= app.target_frame_time) : (accumulated_time -= app.target_frame_time) { - if (!app.poll()) { - break: running_loop; - } - - app.update(&script_env, @floatCast(app.target_frame_time)) catch return; - } - - app.render(); - ext.SDL_Delay(1); + for (middlewares) |middleware| { + loop_internal.use(middleware) catch |allocation_error| { + return log_fail(switch (allocation_error) { + error.OutOfMemory => "could not use middleware: out of memory", + }); + }; } + + loop_internal.run(); } diff --git a/source/ona/kym.zig b/source/ona/script.zig similarity index 92% rename from source/ona/kym.zig rename to source/ona/script.zig index d00dd54..45df5c5 100644 --- a/source/ona/kym.zig +++ b/source/ona/script.zig @@ -1,16 +1,14 @@ -const Chunk = @import("./kym/Chunk.zig"); +const Chunk = @import("./script/Chunk.zig"); -const Table = @import("./kym/Table.zig"); +const Table = @import("./script/Table.zig"); const coral = @import("coral"); const file = @import("./file.zig"); -const gfx = @import("./gfx.zig"); +const tokens = @import("./script/tokens.zig"); -const tokens = @import("./kym/tokens.zig"); - -const tree = @import("./kym/tree.zig"); +const tree = @import("./script/tree.zig"); /// /// Fixed-length integer number type. @@ -78,15 +76,7 @@ pub const RuntimeEnv = struct { } }); - const ImporterTable = coral.map.Table(file.Path, Importer, struct { - pub fn hash(file_path: file.Path) usize { - return coral.io.djb2_hash(@typeInfo(usize).Int, file_path.to_bytes()); - } - - pub fn equals(a: file.Path, b: file.Path) bool { - return coral.io.are_equal(a.to_bytes(), b.to_bytes()); - } - }); + const ImporterTable = coral.map.StringTable(Importer); const LocalList = coral.list.Stack(?*RuntimeObj); @@ -94,13 +84,22 @@ pub const RuntimeEnv = struct { /// Optional settings for a [RuntimeEnv]. /// pub const Options = struct { - import_access: file.Access = .null, + import_access: file.Access = .{.sandboxed_path = &file.Path.cwd}, print_out: ?*const Print = null, print_err: ?*const Print = null, }; const SymbolSet = coral.map.StringTable([:0]coral.io.Byte); + /// + /// + /// + pub fn acquire(_: *RuntimeEnv, object: *RuntimeObj) *RuntimeObj { + object.internal().ref_count += 1; + + return object; + } + /// /// Attempts to push the argument located at `arg_index` to the top of `self`, pushing `null` instead if the given /// argument does not exist or was provided as a nil value. @@ -148,10 +147,21 @@ pub const RuntimeEnv = struct { defer self.release(callable); + if (callable.is_dynamic(Chunk.typeinfo)) |chunk_userdata| { + const arity = @as(*Chunk, @ptrCast(@alignCast(chunk_userdata))).arity; + + if (arg_count < arity) { + return self.raise(error.BadOperation, "expected {expected} arguments, {provided} provided", .{ + .expected = arity, + .provided = arg_count, + }); + } + } + const result = get_result: { try self.frames.push_one(.{ .locals_top = self.locals.values.len - arg_count, - .callable = callable.internal().acquire(), + .callable = self.acquire(callable), .caller = caller, .arg_count = arg_count, }); @@ -160,7 +170,7 @@ pub const RuntimeEnv = struct { const popped_frame = self.frames.pop().?; self.release(popped_frame.callable); - coral.debug.assert(popped_frame.locals_top < self.locals.values.len); + coral.debug.assert(popped_frame.locals_top <= self.locals.values.len); var to_discard = self.locals.values.len - popped_frame.locals_top; @@ -530,8 +540,8 @@ pub const RuntimeEnv = struct { self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + @as(Float, @floatFromInt(rhs_fixed))), .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_added(@floatFromInt(rhs_fixed))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_added(@floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 + @as(Vector2, @splat(@floatFromInt(rhs_fixed)))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 + @as(Vector3, @splat(@floatFromInt(rhs_fixed)))), else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ .typename = addable.typename(), @@ -558,8 +568,8 @@ pub const RuntimeEnv = struct { return switch (addable.internal().payload) { .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / @as(Float, @floatFromInt(rhs_fixed))), .float => |lhs_float| self.new_float(lhs_float / @as(Float, @floatFromInt(rhs_fixed))), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_divided(@floatFromInt(rhs_fixed))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_divided(@floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 / @as(Vector2, @splat(@floatFromInt(rhs_fixed)))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 / @as(Vector3, @splat(@floatFromInt(rhs_fixed)))), else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ .typename = addable.typename(), @@ -587,8 +597,8 @@ pub const RuntimeEnv = struct { self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * @as(Float, @floatFromInt(rhs_fixed))), .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_multiplied(@floatFromInt(rhs_fixed))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_multiplied(@floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 * @as(Vector2, @splat(@floatFromInt(rhs_fixed)))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 * @as(Vector3, @splat(@floatFromInt(rhs_fixed)))), else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ .typename = addable.typename(), @@ -616,8 +626,8 @@ pub const RuntimeEnv = struct { self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - @as(Float, @floatFromInt(rhs_fixed))), .float => |lhs_float| self.new_float(lhs_float + @as(Float, @floatFromInt(rhs_fixed))), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_subtracted(@floatFromInt(rhs_fixed))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_subtracted(@floatFromInt(rhs_fixed))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 - @as(Vector2, @splat(@floatFromInt(rhs_fixed)))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 - @as(Vector3, @splat(@floatFromInt(rhs_fixed)))), else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ .typename = addable.typename(), @@ -640,8 +650,8 @@ pub const RuntimeEnv = struct { return switch (addable.internal().payload) { .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) + rhs_float), .float => |lhs_float| self.new_float(lhs_float + rhs_float), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_added(@floatCast(rhs_float))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_added(@floatCast(rhs_float))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 + @as(Vector2, @splat(@floatCast(rhs_float)))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 + @as(Vector3, @splat(@floatCast(rhs_float)))), else => self.raise(error.TypeMismatch, "fixed types are not addable with {typename}", .{ .typename = addable.typename(), @@ -668,8 +678,8 @@ pub const RuntimeEnv = struct { return switch (addable.internal().payload) { .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) / rhs_float), .float => |lhs_float| self.new_float(lhs_float / lhs_float), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_divided(@floatCast(rhs_float))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_divided(@floatCast(rhs_float))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 / @as(Vector2, @splat(@floatCast(rhs_float)))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 / @as(Vector3, @splat(@floatCast(rhs_float)))), else => self.raise(error.TypeMismatch, "fixed types are not divisible with {typename}", .{ .typename = addable.typename(), @@ -693,8 +703,8 @@ pub const RuntimeEnv = struct { return switch (addable.internal().payload) { .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) * rhs_float), .float => |lhs_float| self.new_float(lhs_float * lhs_float), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_multiplied(@floatCast(rhs_float))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_multiplied(@floatCast(rhs_float))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 * @as(Vector2, @splat(@floatCast(rhs_float)))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 * @as(Vector3, @splat(@floatCast(rhs_float)))), else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ .typename = addable.typename(), @@ -718,8 +728,8 @@ pub const RuntimeEnv = struct { return switch (addable.internal().payload) { .fixed => |lhs_fixed| self.new_float(@as(Float, @floatFromInt(lhs_fixed)) - rhs_float), .float => |lhs_float| self.new_float(lhs_float - lhs_float), - .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2.scalar_subtracted(@floatCast(rhs_float))), - .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3.scalar_subtracted(@floatCast(rhs_float))), + .vector2 => |lhs_vector2| self.new_vector2(lhs_vector2 - @as(Vector2, @splat(@floatCast(rhs_float)))), + .vector3 => |lhs_vector3| self.new_vector3(lhs_vector3 - @as(Vector3, @splat(@floatCast(rhs_float)))), else => self.raise(error.TypeMismatch, "fixed types are not multiplicable with {typename}", .{ .typename = addable.typename(), @@ -742,7 +752,7 @@ pub const RuntimeEnv = struct { }; if (frame.caller) |object| { - return object.internal().acquire(); + return self.acquire(object); } return null; @@ -776,18 +786,13 @@ pub const RuntimeEnv = struct { } swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) { - 'x' => vector2.x, - 'y' => vector2.y, + 'x' => vector2[0], + 'y' => vector2[1], 0 => return switch (swizzle_count) { 1 => self.new_float(swizzle_buffer[0]), - - 2 => self.new_vector2(.{ - .x = swizzle_buffer[0], - .y = swizzle_buffer[1], - }), - - 3 => self.new_vector3(Vector3.from_scalars(swizzle_buffer)), + 2 => self.new_vector2(.{swizzle_buffer[0], swizzle_buffer[1]}), + 3 => self.new_vector3(swizzle_buffer), else => unreachable, }, @@ -811,19 +816,14 @@ pub const RuntimeEnv = struct { } swizzle_buffer[swizzle_count] = switch (swizzle_symbol[swizzle_count]) { - 'x' => vector3.x, - 'y' => vector3.y, - 'z' => vector3.z, + 'x' => vector3[0], + 'y' => vector3[1], + 'z' => vector3[2], 0 => return switch (swizzle_count) { 1 => self.new_float(swizzle_buffer[0]), - - 2 => self.new_vector2(.{ - .x = swizzle_buffer[0], - .y = swizzle_buffer[1], - }), - - 3 => self.new_vector3(Vector3.from_scalars(swizzle_buffer)), + 2 => self.new_vector2(.{swizzle_buffer[0], swizzle_buffer[1]}), + 3 => self.new_vector3(swizzle_buffer), else => unreachable, }, @@ -897,7 +897,9 @@ pub const RuntimeEnv = struct { /// `self` is returned for function chaining. /// pub fn import(self: *RuntimeEnv, file_path: file.Path) RuntimeError!*RuntimeEnv { - if (self.importer_overrides.lookup(file_path)) |importer| { + const file_name = file_path.to_bytes(); + + if (self.importer_overrides.lookup(file_name)) |importer| { if (try importer.invoke(self)) |object| { errdefer self.release(object); @@ -910,14 +912,9 @@ pub const RuntimeEnv = struct { } const callable = new_chunk: { - const file_name = file_path.to_bytes(); - - const file_data = - (try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse { - return self.raise(error.BadOperation, "failed to open or read `{name}`", .{ - .name = file_name, - }); - }; + const file_data = (try file.allocate_and_load(self.allocator, self.options.import_access, file_path)) orelse { + return self.raise(error.BadOperation, "failed to open or read `{name}`", .{.name = file_name}); + }; defer self.allocator.deallocate(file_data); @@ -933,7 +930,7 @@ pub const RuntimeEnv = struct { self.print_err(error_message); } - return self.raise(parse_error, "failed to parse `{name}`", .{.name = file_name}); + return self.raise(parse_error, "failed to parse `{path}`", .{.path = file_name}); }; } @@ -1035,7 +1032,7 @@ pub const RuntimeEnv = struct { self.release(previous_local); } - local.* = if (value) |object| object.internal().acquire() else null; + local.* = if (value) |object| self.acquire(object) else null; } /// @@ -1112,7 +1109,7 @@ pub const RuntimeEnv = struct { pub fn new_boxed(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv { const boxed = try RuntimeObj.allocate(self.allocator, .{ .ref_count = 1, - .payload = .{.boxed = if (value) |object| object.internal().acquire() else null}, + .payload = .{.boxed = if (value) |object| self.acquire(object) else null}, }); errdefer self.release(boxed); @@ -1343,7 +1340,7 @@ pub const RuntimeEnv = struct { /// /// A [RuntimeError] is returned if `self` is out of memory. /// - pub fn override_import(self: *RuntimeEnv, path: file.Path, importer: Importer) RuntimeError!void { + pub fn override_import(self: *RuntimeEnv, path: []const coral.io.Byte, importer: Importer) RuntimeError!void { _ = try self.importer_overrides.replace(path, importer); } @@ -1388,7 +1385,7 @@ pub const RuntimeEnv = struct { /// pub fn push(self: *RuntimeEnv, value: ?*RuntimeObj) RuntimeError!*RuntimeEnv { if (value) |object| { - const acquired = object.internal().acquire(); + const acquired = self.acquire(object); errdefer self.release(acquired); @@ -1542,7 +1539,7 @@ pub const RuntimeEnv = struct { .symbol => |symbol| self.new_string(coral.io.slice_sentineled(@as(coral.io.Byte, 0), symbol)), .string => acquire: { - try self.locals.push_one(value.internal().acquire()); + try self.locals.push_one(self.acquire(value)); break: acquire self; }, @@ -1550,7 +1547,11 @@ pub const RuntimeEnv = struct { .vector2 => |vector2| convert: { var string = [_:0]coral.io.Byte{0} ** 64; var buffer = coral.io.FixedBuffer{.bytes = &string}; - const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec2({x}, {y})", vector2).?; + + const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec2({x}, {y})", .{ + .x = vector2[0], + .y = vector2[1], + }).?; break: convert self.new_string(string[0 .. length]); }, @@ -1558,7 +1559,12 @@ pub const RuntimeEnv = struct { .vector3 => |vector3| convert: { var string = [_:0]coral.io.Byte{0} ** 96; var buffer = coral.io.FixedBuffer{.bytes = &string}; - const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec3({x}, {y}, {z})", vector3).?; + + const length = coral.utf8.print_formatted(buffer.as_writer(), "@vec3({x}, {y}, {z})", .{ + .x = vector3[0], + .y = vector3[1], + .z = vector3[2], + }).?; break: convert self.new_string(string[0 .. length]); }, @@ -1593,7 +1599,7 @@ pub const RuntimeEnv = struct { defer self.release(addable); return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2.vector_added(rhs_vector2)) + self.new_vector2(lhs_vector2 + rhs_vector2) else self.raise(error.TypeMismatch, "vector2 types are not addable with {typename}", .{ .typename = addable.typename(), @@ -1609,7 +1615,7 @@ pub const RuntimeEnv = struct { /// `self` is returned for function chaining. /// pub fn vector2_divide(self: *RuntimeEnv, rhs_vector2: Vector2) RuntimeError!*RuntimeEnv { - if (rhs_vector2.x == 0 or rhs_vector2.y == 0) { + if (rhs_vector2[0] == 0 or rhs_vector2[1] == 0) { return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); } @@ -1618,7 +1624,7 @@ pub const RuntimeEnv = struct { defer self.release(addable); return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2.vector_divided(rhs_vector2)) + self.new_vector2(lhs_vector2 / rhs_vector2) else self.raise(error.TypeMismatch, "vector2 types are not divisible with {typename}", .{ .typename = addable.typename(), @@ -1639,7 +1645,7 @@ pub const RuntimeEnv = struct { defer self.release(addable); return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2.vector_multiplied(rhs_vector2)) + self.new_vector2(lhs_vector2 * rhs_vector2) else self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ .typename = addable.typename(), @@ -1660,7 +1666,7 @@ pub const RuntimeEnv = struct { defer self.release(addable); return if (addable.is_vector2()) |lhs_vector2| - self.new_vector2(lhs_vector2.vector_subtracted(rhs_vector2)) + self.new_vector2(lhs_vector2 - rhs_vector2) else self.raise(error.TypeMismatch, "vector2 types are not multiplicable with {typename}", .{ .typename = addable.typename(), @@ -1680,7 +1686,7 @@ pub const RuntimeEnv = struct { defer self.release(addable); return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3.vector_added(rhs_vector3)) + self.new_vector3(lhs_vector3 + rhs_vector3) else self.raise(error.TypeMismatch, "vector3 types are not addable with {typename}", .{ .typename = addable.typename(), @@ -1696,7 +1702,7 @@ pub const RuntimeEnv = struct { /// `self` is returned for function chaining. /// pub fn vector3_divide(self: *RuntimeEnv, rhs_vector3: Vector3) RuntimeError!*RuntimeEnv { - if (rhs_vector3.x == 0 or rhs_vector3.y == 0 or rhs_vector3.z == 0) { + if (rhs_vector3[0] == 0 or rhs_vector3[1] == 0 or rhs_vector3[2] == 0) { return self.raise(error.TypeMismatch, "cannot divide by zero", .{}); } @@ -1705,7 +1711,7 @@ pub const RuntimeEnv = struct { defer self.release(addable); return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3.vector_divided(rhs_vector3)) + self.new_vector3(lhs_vector3 / rhs_vector3) else self.raise(error.TypeMismatch, "vector3 types are not divisible with {typename}", .{ .typename = addable.typename(), @@ -1726,7 +1732,7 @@ pub const RuntimeEnv = struct { defer self.release(addable); return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3.vector_multiplied(rhs_vector3)) + self.new_vector3(lhs_vector3 * rhs_vector3) else self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ .typename = addable.typename(), @@ -1747,7 +1753,7 @@ pub const RuntimeEnv = struct { defer self.release(addable); return if (addable.is_vector3()) |lhs_vector3| - self.new_vector3(lhs_vector3.vector_subtracted(rhs_vector3)) + self.new_vector3(lhs_vector3 - rhs_vector3) else self.raise(error.TypeMismatch, "vector3 types are not multiplicable with {typename}", .{ .typename = addable.typename(), @@ -1791,7 +1797,7 @@ pub const RuntimeEnv = struct { self.release(unboxed_object); } - unboxed_value.* = if (value) |object| object.internal().acquire() else null; + unboxed_value.* = if (value) |object| self.acquire(object) else null; }, else => return self.raise(error.TypeMismatch, "{typename} is not unboxable", .{ @@ -1865,12 +1871,6 @@ pub const RuntimeObj = opaque { } }, }, - - fn acquire(self: *Internal) *RuntimeObj { - self.ref_count += 1; - - return @ptrCast(self); - } }; fn allocate(allocator: coral.io.Allocator, data: Internal) coral.io.AllocationError!*RuntimeObj { @@ -1909,12 +1909,12 @@ pub const RuntimeObj = opaque { .boxed => |boxed| if (boxed) |object| self.equals(object) else false, .vector2 => |lhs_vector2| switch (other.internal().payload) { - .vector2 => |rhs_vector2| lhs_vector2.equals(rhs_vector2), + .vector2 => |rhs_vector2| lhs_vector2[0] == rhs_vector2[0] and lhs_vector2[1] == rhs_vector2[1], else => false, }, .vector3 => |lhs_vector3| switch (other.internal().payload) { - .vector3 => |rhs_vector3| lhs_vector3.equals(rhs_vector3), + .vector3 => |rhs_vector3| lhs_vector3[0] == rhs_vector3[0] and lhs_vector3[1] == rhs_vector3[1] and lhs_vector3[2] == rhs_vector3[2], else => false, }, @@ -1948,8 +1948,8 @@ pub const RuntimeObj = opaque { .float => |float| @bitCast(float), .fixed => |fixed| @intCast(@as(u32, @bitCast(fixed))), .symbol => |symbol| @intFromPtr(symbol), - .vector2 => |vector| @bitCast(vector.to_scalars()), - .vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector.to_scalars())), + .vector2 => |vector| @bitCast(vector), + .vector3 => |vector| coral.io.jenkins_hash(@typeInfo(usize).Int, coral.io.bytes_of(&vector)), .syscall => |syscall| @intFromPtr(syscall), .boxed => |boxed| @intFromPtr(boxed), .string => |string| coral.io.djb2_hash(@typeInfo(usize).Int, string.unpack()), @@ -2138,7 +2138,7 @@ pub const Typeinfo = struct { pub fn get_index(self: *const GetContext) *RuntimeObj { coral.debug.assert(self.env.locals.values.len > 0); - return self.env.locals.values[self.env.locals.values.len - 1].?.internal().acquire(); + return self.env.acquire(self.env.locals.values[self.env.locals.values.len - 1].?); } }; @@ -2161,7 +2161,7 @@ pub const Typeinfo = struct { pub fn get_index(self: *const SetContext) *RuntimeObj { coral.debug.assert(self.env.locals.values.len > 0); - return self.env.locals.values[self.env.locals.values.len - 1].?.internal().acquire(); + return self.env.acquire(self.env.locals.values[self.env.locals.values.len - 1].?); } /// @@ -2174,7 +2174,7 @@ pub const Typeinfo = struct { pub fn get_value(self: *const SetContext) ?*RuntimeObj { coral.debug.assert(self.env.locals.values.len > 1); - return if (self.env.locals.values[self.env.locals.values.len - 2]) |object| object.internal().acquire() else null; + return if (self.env.locals.values[self.env.locals.values.len - 2]) |object| self.env.acquire(object) else null; } }; @@ -2206,12 +2206,12 @@ pub const Typeinfo = struct { /// /// 2-component vector type. /// -pub const Vector2 = gfx.lina.Vector2; +pub const Vector2 = @Vector(2, f32); /// /// 3-component vector type. /// -pub const Vector3 = gfx.lina.Vector3; +pub const Vector3 = @Vector(3, f32); /// /// Higher-level wrapper for [RuntimeEnv.index_get] that makes it easier to index [Fixed] keys of objects. diff --git a/source/ona/kym/Chunk.zig b/source/ona/script/Chunk.zig similarity index 89% rename from source/ona/kym/Chunk.zig rename to source/ona/script/Chunk.zig index 69193ae..f8a2867 100644 --- a/source/ona/kym/Chunk.zig +++ b/source/ona/script/Chunk.zig @@ -2,19 +2,19 @@ const coral = @import("coral"); const file = @import("../file.zig"); -const kym = @import("../kym.zig"); +const script = @import("../script.zig"); const tokens = @import("./tokens.zig"); const tree = @import("./tree.zig"); -name: *kym.RuntimeObj, +name: *script.RuntimeObj, arity: u8, opcodes: OpcodeList, lines: LineList, cursor: usize, constants: ConstList, -bindings: []?*kym.RuntimeObj, +bindings: []?*script.RuntimeObj, const Builtin = enum { import, @@ -25,9 +25,9 @@ const Builtin = enum { const Compiler = struct { chunk: *Self, - env: *kym.RuntimeEnv, + env: *script.RuntimeEnv, - fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const tree.Expr) kym.RuntimeError!u8 { + fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const tree.Expr) script.RuntimeError!u8 { // TODO: Exceeding 255 arguments will make the VM crash. var maybe_argument = initial_argument; var argument_count = @as(u8, 0); @@ -42,7 +42,7 @@ const Compiler = struct { return argument_count; } - fn compile_expression(self: *const Compiler, environment: *const tree.Environment, expression: *const tree.Expr, name: ?[]const coral.io.Byte) kym.RuntimeError!void { + fn compile_expression(self: *const Compiler, environment: *const tree.Environment, expression: *const tree.Expr, name: ?[]const coral.io.Byte) script.RuntimeError!void { const number_format = coral.utf8.DecimalFormat{ .delimiter = "_", .positive_prefix = .none, @@ -57,13 +57,13 @@ const Compiler = struct { for (literal) |codepoint| { if (codepoint == '.') { return self.chunk.write(expression.line, .{ - .push_const = try self.declare_float(number_format.parse(literal, kym.Float) orelse unreachable), + .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, kym.Fixed) orelse unreachable), + .push_const = try self.declare_fixed(number_format.parse(literal, script.Fixed) orelse unreachable), }); }, @@ -181,11 +181,10 @@ const Compiler = struct { .declaration_get => |declaration_get| { if (get_local_index(environment, declaration_get.declaration)) |index| { + try self.chunk.write(expression.line, .{.push_local = 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; @@ -270,7 +269,7 @@ const Compiler = struct { } } - pub fn compile_environment(self: *const Compiler, environment: *const tree.Environment) kym.RuntimeError!void { + pub fn compile_environment(self: *const Compiler, environment: *const tree.Environment) script.RuntimeError!void { if (environment.statement) |statement| { const last_statement = try self.compile_statement(environment, statement); @@ -280,7 +279,7 @@ const Compiler = struct { } } - fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const tree.Stmt) kym.RuntimeError!*const tree.Stmt { + fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const tree.Stmt) script.RuntimeError!*const tree.Stmt { var current_statement = initial_statement; while (true) { @@ -346,7 +345,7 @@ const Compiler = struct { const constants_max = @as(usize, coral.math.max_int(@typeInfo(u16).Int)); - fn declare_chunk(self: *const Compiler, chunk: Self) kym.RuntimeError!u16 { + fn declare_chunk(self: *const Compiler, chunk: Self) script.RuntimeError!u16 { if (self.chunk.constants.values.len == coral.math.max_int(@typeInfo(u16).Int)) { return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{ .max = @as(usize, coral.math.max_int(@typeInfo(u16).Int)), @@ -362,7 +361,7 @@ const Compiler = struct { return @intCast(self.chunk.constants.values.len - 1); } - fn declare_fixed(self: *const Compiler, fixed: kym.Fixed) kym.RuntimeError!u16 { + fn declare_fixed(self: *const Compiler, fixed: script.Fixed) script.RuntimeError!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, @@ -378,7 +377,7 @@ const Compiler = struct { return @intCast(self.chunk.constants.values.len - 1); } - fn declare_float(self: *const Compiler, float: kym.Float) kym.RuntimeError!u16 { + fn declare_float(self: *const Compiler, float: script.Float) script.RuntimeError!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, @@ -394,7 +393,7 @@ const Compiler = struct { return @intCast(self.chunk.constants.values.len - 1); } - fn declare_string(self: *const Compiler, string: []const coral.io.Byte) kym.RuntimeError!u16 { + fn declare_string(self: *const Compiler, string: []const coral.io.Byte) script.RuntimeError!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, @@ -410,7 +409,7 @@ const Compiler = struct { return @intCast(self.chunk.constants.values.len - 1); } - fn declare_symbol(self: *const Compiler, symbol: []const coral.io.Byte) kym.RuntimeError!u16 { + fn declare_symbol(self: *const Compiler, symbol: []const coral.io.Byte) script.RuntimeError!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, @@ -426,7 +425,7 @@ const Compiler = struct { return @intCast(self.chunk.constants.values.len - 1); } - fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) kym.RuntimeError!?u8 { + fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) script.RuntimeError!?u8 { var binding_index = @as(u8, 0); while (binding_index < environment.capture_count) : (binding_index += 1) { @@ -467,7 +466,7 @@ const Compiler = struct { } }; -const ConstList = coral.list.Stack(*kym.RuntimeObj); +const ConstList = coral.list.Stack(*script.RuntimeObj); const LineList = coral.list.Stack(tokens.Line); @@ -516,7 +515,7 @@ const OpcodeList = coral.list.Stack(Opcode); const Self = @This(); -pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void { +pub fn deinit(self: *Self, env: *script.RuntimeEnv) void { while (self.constants.pop()) |constant| { env.release(constant); } @@ -538,7 +537,7 @@ pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void { self.bindings = &.{}; } -pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeObj { +pub fn dump(chunk: Self, env: *script.RuntimeEnv) script.RuntimeError!*script.RuntimeObj { var opcode_cursor = @as(u32, 0); var buffer = coral.list.ByteStack.init(env.allocator); @@ -629,7 +628,7 @@ pub fn dump(chunk: Self, env: *kym.RuntimeEnv) kym.RuntimeError!*kym.RuntimeObj return (try env.new_string(buffer.values)).pop().?; } -pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { +pub fn execute(self: *Self, env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj { self.cursor = 0; while (self.cursor < self.opcodes.values.len) : (self.cursor += 1) { @@ -693,7 +692,7 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime return env.raise(error.IllegalState, "cannot bind values to an already-bound chunk", .{}); } - chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*kym.RuntimeObj, null)); + chunk.bindings = try coral.io.allocate_many(env.allocator, bind, @as(?*script.RuntimeObj, null)); for (0 .. bind) |index| { const value = env.pop(); @@ -897,7 +896,7 @@ pub fn execute(self: *Self, env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.Runtime return env.pop(); } -fn get_binding(self: *Self, env: *kym.RuntimeEnv, index: usize) kym.RuntimeError!?*kym.RuntimeObj { +fn get_binding(self: *Self, env: *script.RuntimeEnv, index: usize) script.RuntimeError!?*script.RuntimeObj { if (index >= self.bindings.len) { return env.raise(error.IllegalState, "invalid binding", .{}); } @@ -905,7 +904,7 @@ fn get_binding(self: *Self, env: *kym.RuntimeEnv, index: usize) kym.RuntimeError return self.bindings[index]; } -fn get_constant(self: *const Self, env: *kym.RuntimeEnv, index: usize) kym.RuntimeError!*kym.RuntimeObj { +fn get_constant(self: *const Self, env: *script.RuntimeEnv, index: usize) script.RuntimeError!*script.RuntimeObj { if (index >= self.constants.values.len) { return env.raise(error.IllegalState, "invalid constant", .{}); } @@ -913,7 +912,7 @@ fn get_constant(self: *const Self, env: *kym.RuntimeEnv, index: usize) kym.Runti return self.constants.values[index]; } -pub fn init(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) kym.RuntimeError!Self { +pub fn init(env: *script.RuntimeEnv, name: []const coral.io.Byte, environment: *const tree.Environment) script.RuntimeError!Self { var chunk = Self{ .name = (try env.new_symbol(name)).pop().?, .opcodes = OpcodeList.init(env.allocator), @@ -934,17 +933,19 @@ pub fn init(env: *kym.RuntimeEnv, name: []const coral.io.Byte, environment: *con return chunk; } -fn syscall_import(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { - const arg = (try env.arg(0)).pop() orelse { - return env.raise(error.BadOperation, "`@import` requires one argument to be a valid import path", .{}); - }; +fn syscall_import(env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj { + const arg = try env.expect_object((try env.arg(0)).pop()); defer env.release(arg); - return (try env.import(file.Path.of(&.{try env.expect_string(arg)}))).pop(); + const file_path = file.Path.from_bytes(try env.expect_string(arg)) catch { + return env.raise(error.TypeMismatch, "`@import` requires argument `0` to be a valid import path", .{}); + }; + + return (try env.import(file_path)).pop(); } -fn syscall_print(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { +fn syscall_print(env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj { const string = (try (try env.arg(0)).to_string()).pop().?; defer env.release(string); @@ -954,7 +955,7 @@ fn syscall_print(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { return null; } -fn syscall_vec2(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { +fn syscall_vec2(env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj { const x = @as(f32, get_x: { const x = (try env.arg(0)).pop() orelse { return env.raise(error.BadOperation, "a first argument is required to create a vector", .{}); @@ -968,16 +969,13 @@ fn syscall_vec2(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { if ((try env.arg(1)).pop()) |y| { defer env.release(y); - return (try env.new_vector2(.{ - .y = @floatCast(try env.expect_float(y)), - .x = x, - })).pop(); + return (try env.new_vector2(.{x, @as(f32, @floatCast(try env.expect_float(y)))})).pop(); } - return (try env.new_vector2(kym.Vector2.from_scalar(x))).pop(); + return (try env.new_vector2(@splat(x))).pop(); } -fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { +fn syscall_vec3(env: *script.RuntimeEnv) script.RuntimeError!?*script.RuntimeObj { const x = @as(f32, get_x: { const x = (try env.arg(0)).pop() orelse { return env.raise(error.BadOperation, "a first argument is required to create a vector", .{}); @@ -991,27 +989,22 @@ fn syscall_vec3(env: *kym.RuntimeEnv) kym.RuntimeError!?*kym.RuntimeObj { if ((try env.arg(1)).pop()) |y| { defer env.release(y); - return (try env.new_vector3(.{ - .z = @as(f32, get_z: { - const z = (try env.arg(0)).pop() orelse { - return env.raise(error.BadOperation, - "a third argument is required to create a vector if a first and second exist", .{}); - }; + return (try env.new_vector3(.{x, @as(f32, @floatCast(try env.expect_float(y))), @as(f32, get_z: { + const z = (try env.arg(0)).pop() orelse { + return env.raise(error.BadOperation, + "a third argument is required to create a vector if a first and second exist", .{}); + }; - defer env.release(z); + defer env.release(z); - break: get_z @floatCast(try env.expect_float(z)); - }), - - .y = @floatCast(try env.expect_float(y)), - .x = x, - })).pop(); + break: get_z @floatCast(try env.expect_float(z)); + })})).pop(); } - return (try env.new_vector3(kym.Vector3.from_scalar(x))).pop(); + return (try env.new_vector3(@splat(x))).pop(); } -pub const typeinfo = &kym.Typeinfo{ +pub const typeinfo = &script.Typeinfo{ .size = @sizeOf(Self), .name = "lambda", .destruct = typeinfo_destruct, @@ -1019,15 +1012,15 @@ pub const typeinfo = &kym.Typeinfo{ .to_string = typeinfo_to_string, }; -fn typeinfo_call(context: kym.Typeinfo.CallContext) kym.RuntimeError!?*kym.RuntimeObj { +fn typeinfo_call(context: script.Typeinfo.CallContext) script.RuntimeError!?*script.RuntimeObj { return @as(*Self, @ptrCast(@alignCast(context.userdata))).execute(context.env); } -fn typeinfo_destruct(context: kym.Typeinfo.DestructContext) void { +fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void { @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); } -fn typeinfo_to_string(context: kym.Typeinfo.ToStringContext) kym.RuntimeError!*kym.RuntimeObj { +fn typeinfo_to_string(context: script.Typeinfo.ToStringContext) script.RuntimeError!*script.RuntimeObj { return (try (try context.env.push(@as(*Self, @ptrCast(@alignCast(context.userdata))).name)).to_string()).pop().?; } diff --git a/source/ona/kym/Table.zig b/source/ona/script/Table.zig similarity index 76% rename from source/ona/kym/Table.zig rename to source/ona/script/Table.zig index fad9327..63e9357 100644 --- a/source/ona/kym/Table.zig +++ b/source/ona/script/Table.zig @@ -1,21 +1,21 @@ const coral = @import("coral"); -const kym = @import("../kym.zig"); +const script = @import("../script.zig"); associative: RefTable, contiguous: RefList, -const RefList = coral.list.Stack(?*kym.RuntimeObj); +const RefList = coral.list.Stack(?*script.RuntimeObj); -const RefTable = coral.map.Table(*kym.RuntimeObj, *kym.RuntimeObj, struct { - pub const hash = kym.RuntimeObj.hash; +const RefTable = coral.map.Table(*script.RuntimeObj, *script.RuntimeObj, struct { + pub const hash = script.RuntimeObj.hash; - pub const equals = kym.RuntimeObj.equals; + pub const equals = script.RuntimeObj.equals; }); const Self = @This(); -pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void { +pub fn deinit(self: *Self, env: *script.RuntimeEnv) void { { var field_iterable = self.associative.as_iterable(); @@ -36,14 +36,14 @@ pub fn deinit(self: *Self, env: *kym.RuntimeEnv) void { self.contiguous.deinit(); } -pub fn init(env: *kym.RuntimeEnv) Self { +pub fn init(env: *script.RuntimeEnv) Self { return .{ .associative = RefTable.init(env.allocator, .{}), .contiguous = RefList.init(env.allocator), }; } -pub const typeinfo = &kym.Typeinfo{ +pub const typeinfo = &script.Typeinfo{ .size = @sizeOf(Self), .name = "table", .destruct = typeinfo_destruct, @@ -51,11 +51,11 @@ pub const typeinfo = &kym.Typeinfo{ .set = typeinfo_set, }; -fn typeinfo_destruct(context: kym.Typeinfo.DestructContext) void { +fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void { @as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env); } -fn typeinfo_get(context: kym.Typeinfo.GetContext) kym.RuntimeError!?*kym.RuntimeObj { +fn typeinfo_get(context: script.Typeinfo.GetContext) script.RuntimeError!?*script.RuntimeObj { const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); const index = context.get_index(); @@ -79,7 +79,7 @@ fn typeinfo_get(context: kym.Typeinfo.GetContext) kym.RuntimeError!?*kym.Runtime return null; } -fn typeinfo_set(context: kym.Typeinfo.SetContext) kym.RuntimeError!void { +fn typeinfo_set(context: script.Typeinfo.SetContext) script.RuntimeError!void { const table = @as(*Self, @ptrCast(@alignCast(context.userdata))); const index = context.get_index(); diff --git a/source/ona/kym/tokens.zig b/source/ona/script/tokens.zig similarity index 100% rename from source/ona/kym/tokens.zig rename to source/ona/script/tokens.zig diff --git a/source/ona/kym/tree.zig b/source/ona/script/tree.zig similarity index 100% rename from source/ona/kym/tree.zig rename to source/ona/script/tree.zig diff --git a/source/ona/kym/tree/Expr.zig b/source/ona/script/tree/Expr.zig similarity index 100% rename from source/ona/kym/tree/Expr.zig rename to source/ona/script/tree/Expr.zig diff --git a/source/ona/kym/tree/Stmt.zig b/source/ona/script/tree/Stmt.zig similarity index 100% rename from source/ona/kym/tree/Stmt.zig rename to source/ona/script/tree/Stmt.zig diff --git a/source/runner.zig b/source/runner.zig index 61dd7b5..9929e34 100644 --- a/source/runner.zig +++ b/source/runner.zig @@ -1,5 +1,185 @@ +const coral = @import("coral"); + const ona = @import("ona"); +pub const ScriptPlugin = struct { + env: ona.script.RuntimeEnv, + events: ?*ona.script.RuntimeObj, + + const EventsObject = struct { + updaters: RuntimeObjList, + loop: *ona.GraphicsLoop, + + fn get(context: ona.script.Typeinfo.GetContext) ona.script.RuntimeError!?*ona.script.RuntimeObj { + const index = context.get_index(); + + defer context.env.release(index); + + inline for ([_][]const u8{"on_update"}) |name| { + const symbol = (try context.env.new_symbol(name)).pop().?; + + defer context.env.release(symbol); + + if (index.equals(symbol)) { + return (try context.env.new_syscall(@field(EventsObject, name))).pop(); + } + } + + return null; + } + + fn on_update(env: *ona.script.RuntimeEnv) ona.script.RuntimeError!?*ona.script.RuntimeObj { + const caller = try env.expect_object(try env.get_caller()); + + defer env.release(caller); + + const updater = try env.expect_object((try env.arg(0)).pop()); + + errdefer env.release(updater); + + try @as(*EventsObject, @ptrCast(@alignCast(try env.expect_dynamic(caller, typeinfo)))).updaters.push_one(updater); + + return null; + } + + const typeinfo = &ona.script.Typeinfo{ + .name = "events", + .size = @sizeOf(EventsObject), + .get = get, + }; + + fn update(env: *ona.script.RuntimeEnv, _: *ona.GraphicsLoop) void { + return ((env.arg(0) catch return).call(0, null) catch return).discard(); + } + }; + + const RuntimeObjList = coral.list.Stack(*ona.script.RuntimeObj); + + pub fn deinit(self: *ScriptPlugin) void { + if (self.events) |object| { + self.env.release(object); + } + + self.env.deinit(); + } + + fn import_events(self: *ScriptPlugin, env: *ona.script.RuntimeEnv) ona.script.RuntimeError!?*ona.script.RuntimeObj { + return env.acquire(self.events.?); + } + + pub fn init() coral.io.AllocationError!ScriptPlugin { + var env = try ona.script.RuntimeEnv.init(ona.heap.allocator, 255, .{ + .print_out = ona.log_info, + .print_err = ona.log_fail, + }); + + errdefer env.deinit(); + + return .{ + .env = env, + .events = null, + }; + } + + fn load(self: *ScriptPlugin, loop: *ona.GraphicsLoop) void { + self.events = (self.env.new_dynamic(EventsObject.typeinfo, EventsObject{ + .updaters = RuntimeObjList.init(ona.heap.allocator), + .loop = loop, + }) catch { + return ona.log_fail("failed to instantiate events object"); + }).pop(); + + self.env.override_import("events", ona.script.Importer.bind(ScriptPlugin, self, import_events)) catch { + return ona.log_fail("failed to overide native import paths"); + }; + + self.reload(loop) catch {}; + } + + fn reload(self: *ScriptPlugin, loop: *ona.GraphicsLoop) ona.script.RuntimeError!void { + var title = comptime try coral.utf8.SmallString.from_units("Ona"); + var res_width = @as(u16, 640); + var res_height = @as(u16, 480); + var tick_rate = @as(f64, 60); + + if ((try self.env.import(comptime try ona.file.Path.from_bytes("app.ona"))).pop()) |manifest| { + defer self.env.release(manifest); + + if (try ona.script.get_field(&self.env, manifest, "res_width")) |object| { + defer self.env.release(object); + + const int = @typeInfo(@TypeOf(res_width)).Int; + + res_width = coral.math.checked_cast(int, try self.env.expect_fixed(object)) orelse { + return self.env.raise(error.TypeMismatch, "`res_width` property cannot be greater than `{max}`", .{ + .max = coral.math.max_int(int), + }); + }; + } + + if (try ona.script.get_field(&self.env, manifest, "res_height")) |object| { + defer self.env.release(object); + + const int = @typeInfo(@TypeOf(res_height)).Int; + + res_height = coral.math.checked_cast(int, try self.env.expect_fixed(object)) orelse { + return self.env.raise(error.TypeMismatch, "`res_height` property cannot be greater than `{max}`", .{ + .max = coral.math.max_int(int), + }); + }; + } + + if (try ona.script.get_field(&self.env, manifest, "tick_rate")) |object| { + defer self.env.release(object); + + tick_rate = try self.env.expect_float(object); + } + + if (try ona.script.get_field(&self.env, manifest, "title")) |object| { + defer self.env.release(object); + + title = coral.utf8.SmallString.from_units(try self.env.expect_string(object)) catch |from_error| { + return switch (from_error) { + error.InvalidUtf8 => self.env.raise(error.TypeMismatch, "`title` cannot contain invalid utf8", .{}), + error.TooBig => self.env.raise(error.TypeMismatch, "`title` is too long", .{}), + }; + }; + } + + loop.set_resolution(res_width, res_height); + loop.set_title(title); + loop.set_tick_rate(tick_rate); + } + } + + fn update(self: *ScriptPlugin, graphics_update: ona.GraphicsUpdate) void { + if (graphics_update.loop.is_key_pressed(ona.Key.f5)) { + self.reload(graphics_update.loop) catch {}; + } + + const eventsect = @as(*EventsObject, @ptrCast(@alignCast(self.env.expect_dynamic(self.events.?, EventsObject.typeinfo) catch { + return; + }))); + + const dt = (self.env.new_float(graphics_update.delta_time) catch return).pop().?; + + defer self.env.release(dt); + + for (eventsect.updaters.values) |updater| { + (((self.env.push(dt) catch return).push(updater) catch return).call(1, null) catch return).discard(); + } + } +}; + pub fn main() void { - ona.run_app(.{.sandboxed_path = &ona.file.Path.cwd}); + var script_plugin = ScriptPlugin.init() catch { + return ona.log_fail("failed to initialize script plugin"); + }; + + defer script_plugin.deinit(); + + ona.start_graphics(&.{ + .{.loader = ona.GraphicsLoader.bind(ScriptPlugin, &script_plugin, ScriptPlugin.load)}, + .{.updater = ona.GraphicsUpdater.bind(ScriptPlugin, &script_plugin, ScriptPlugin.update)}, + }); }