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 { 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)}, }); }