Compare commits

...

4 Commits

Author SHA1 Message Date
d5d5b69f54 Add partial work on new VM interface
Some checks failed
continuous-integration/drone/push Build is failing
2023-05-09 22:57:19 +00:00
24a4590293 Expose canvas item API on Ona app 2023-05-09 21:12:17 +00:00
d034c526e4 Replace graphics file with canvas API 2023-05-09 21:05:14 +00:00
38dadb6d05 Fix compile errors in slot map remove function 2023-05-09 20:59:26 +00:00
7 changed files with 314 additions and 241 deletions

View File

@ -4,6 +4,7 @@ pub const Value = union(enum) {
newline: void,
string: []const u8,
unsigned: u128,
signed: i128,
};
pub fn print(writer: io.Writer, values: []const Value) io.WriteError!usize {

View File

@ -6,6 +6,10 @@ const math = @import("./math.zig");
const stack = @import("./stack.zig");
///
/// Retruns a set of dense slots that may store `Element`s indexable by a [Slot], where `key` defines how many bits the
/// [Slot] used is made from.
///
pub fn Dense(comptime key: Key, comptime Element: type) type {
const KeySlot = Slot(key);
const Index = math.Unsigned(key.index_bits);
@ -19,20 +23,12 @@ pub fn Dense(comptime key: Key, comptime Element: type) type {
const Self = @This();
pub fn fetch(self: Self, slot: KeySlot) ?*Element {
if (slot.index >= self.values.len) {
return null;
}
const redirect = &self.slots[slot.index];
if (slot.salt != redirect.salt) {
return null;
}
return &self.values[redirect.index];
}
///
/// Clears all elements from the slots in `self`.
///
/// *Note* that clearing the slots is not the same as deinitializing them, as it does not deallocate any memory
/// that has already been allocated to the slots structure.
///
pub fn clear(self: *Self) void {
self.next_free = 0;
self.values = self.values[0 .. 0];
@ -49,18 +45,66 @@ pub fn Dense(comptime key: Key, comptime Element: type) type {
}
}
///
/// Frees all memory allocated by `allocator` to self.
///
/// *Note*: if `self` already contains allocated memory then `allocator` must reference the same [io.Allocator]
/// that was used to create the already-allocated memory.
///
pub fn deinit(self: *Self, allocator: io.Allocator) void {
io.deallocate(allocator, self.values.ptr);
io.deallocate(allocator, self.slots);
io.deallocate(allocator, self.erase);
self.values = &.{};
self.slots = null;
self.erase = null;
}
///
/// Attempts to fetch the element identified referenced by `slot` from `self`, returning it or `null` if `slot`
/// does not reference a valid element.
///
pub fn fetch(self: Self, slot: KeySlot) ?*Element {
if (slot.index >= self.values.len) {
return null;
}
const redirect = &self.slots[slot.index];
if (slot.salt != redirect.salt) {
return null;
}
return &self.values[redirect.index];
}
///
/// Attempts to transactionally grow `self` by `growth_amount` using `allocator`, returning a
/// [io.AllocationError] if it failed.
///
/// Should growing fail, `self` is left in an unmodified state.
///
/// *Note*: if `self` already contains allocated memory then `allocator` must reference the same [io.Allocator]
/// that was used to create the already-allocated memory.
///
pub fn grow(self: *Self, allocator: io.Allocator, growth_amount: usize) io.AllocationError!void {
const grown_capacity = self.capacity + growth_amount;
const values = try io.allocate_many(Element, grown_capacity, allocator);
self.values = try io.reallocate(allocator, self.values, grown_capacity);
self.slots = (try io.reallocate(allocator, self.slots.?[0 .. self.values.len], grown_capacity)).ptr;
self.erase = (try io.reallocate(allocator, self.erase.?[0 .. self.values.len], grown_capacity)).ptr;
errdefer io.deallocate(allocator, values);
const slots = try io.allocate_many(KeySlot, grown_capacity, allocator);
errdefer io.deallocate(allocator, slots);
const erase = try io.allocate_many(Index, grown_capacity, allocator);
errdefer io.deallocate(allocator, slots);
self.values = values;
self.slots = slots.ptr;
self.erase = erase.ptr;
self.capacity = grown_capacity;
// Add new values to the freelist
@ -76,6 +120,13 @@ pub fn Dense(comptime key: Key, comptime Element: type) type {
}
}
///
/// Attempts to insert `value` into `self`, growing the internal buffer with `allocator` if it is full and
/// returning a `Slot` of `key` referencing the inserted element or a [io.AllocationError] if it failed.
///
/// *Note*: if `self` already contains allocated memory then `allocator` must reference the same [io.Allocator]
/// that was used to create the already-allocated memory.
///
pub fn insert(self: *Self, allocator: io.Allocator, value: Element) io.AllocationError!KeySlot {
if (self.values.len == self.capacity) {
try self.grow(allocator, math.max(usize, 1, self.capacity));
@ -97,8 +148,12 @@ pub fn Dense(comptime key: Key, comptime Element: type) type {
};
}
///
/// Attempts to remove the element referenced by `slot` from `self`, returning `true` if it was successful or
/// `false` if `slot` does not reference a valid slot.
///
pub fn remove(self: *Self, slot: KeySlot) bool {
const redirect = &self.slots[slot.index];
const redirect = &self.slots.?[slot.index];
if (slot.salt != redirect.salt) {
return false;
@ -109,17 +164,17 @@ pub fn Dense(comptime key: Key, comptime Element: type) type {
self.values = self.values[0 .. (self.values.len - 1)];
if (self.values.len > 0) {
const free_data = &self.data[free_index];
const free_erase = &self.erase[free_index];
const last_data = &self.data[self.values.len];
const last_erase = &self.erase[self.values.len];
const free_value = &self.values[free_index];
const free_erase = &self.erase.?[free_index];
const last_value = &self.values[self.values.len];
const last_erase = &self.erase.?[self.values.len];
free_data.* = last_data.*;
free_value.* = last_value.*;
free_erase.* = last_erase.*;
self.slots[free_erase.*].index = free_index;
self.slots.?[free_erase.*].index = free_index;
}
redirect.salt = math.max(redirect.salt +% 1, 1);
redirect.salt = math.max(Index, redirect.salt +% 1, 1);
redirect.index = self.next_free;
self.next_free = slot.index;
@ -128,11 +183,17 @@ pub fn Dense(comptime key: Key, comptime Element: type) type {
};
}
///
/// Describes the memory layout of an element-slot mapping.
///
pub const Key = struct {
index_bits: usize,
salt_bits: usize,
};
///
/// References a slot in a slot mapping.
///
pub fn Slot(comptime key: Key) type {
return extern struct {
index: math.Unsigned(key.index_bits),
@ -140,6 +201,9 @@ pub fn Slot(comptime key: Key) type {
};
}
///
/// [Key] that uses the same number of bits as a [usize].
///
pub const addressable_key = Key{
.index_bits = (@bitSizeOf(usize) / 2),
.salt_bits = (@bitSizeOf(usize) / 2),

View File

@ -22,8 +22,8 @@ pub const Environment = struct {
};
pub const NewOptions = struct {
userdata_size: usize = 0,
userinfo: usize = 0,
userdata: []const u8 = &.{},
identity: ?*const anyopaque = null,
behavior: *const Object.Behavior = &.{},
};
@ -35,19 +35,30 @@ pub const Environment = struct {
pub fn call(self: *Environment, object: *Object, arguments: []const Value) RuntimeError!Value {
var global_object = Object{
.ref_count = 0,
.userinfo = 0,
.identity = &self.globals,
.userdata = &.{},
.fields = self.globals,
.behavior = &.{},
};
return object.behavior.caller(.{
.environment = self,
.arguments = arguments,
.object = &global_object,
.env = self,
.args = arguments,
.caller = &global_object,
.callee = object,
});
}
pub fn check(self: Environment, condition: bool, failure_message: []const u8) RuntimeError!void {
if (condition) return;
// TODO: Emit failure message.
_ = self;
_ = failure_message;
return error.CheckFailure;
}
pub fn deinit(self: *Environment) void {
self.stack.deinit(self.allocator);
self.calls.deinit(self.allocator);
@ -57,7 +68,7 @@ pub const Environment = struct {
try self.globals.assign(self.allocator, global_name, value);
}
pub fn init(allocator: coral.io.Allocator, init_options: InitOptions) !Environment {
pub fn init(allocator: coral.io.Allocator, options: InitOptions) !Environment {
var environment = Environment{
.allocator = allocator,
.globals = .{},
@ -70,8 +81,8 @@ pub const Environment = struct {
environment.calls.deinit(allocator);
}
try environment.stack.grow(allocator, init_options.stack_max);
try environment.calls.grow(allocator, init_options.calls_max);
try environment.stack.grow(allocator, options.stack_max);
try environment.calls.grow(allocator, options.calls_max);
return environment;
}
@ -81,14 +92,16 @@ pub const Environment = struct {
errdefer coral.io.deallocate(self.allocator, object);
const userdata = try coral.io.allocate_many(u8, options.userdata_size, self.allocator);
const userdata = try coral.io.allocate_many(u8, options.userdata.len, self.allocator);
errdefer coral.io.deallocate(self.allocator, userdata);
coral.io.copy(userdata, options.userdata);
object.* = .{
.userdata = userdata,
.ref_count = 1,
.userinfo = options.userinfo,
.identity = options.identity orelse userdata.ptr,
.behavior = options.behavior,
.fields = .{},
};
@ -101,7 +114,7 @@ pub const Environment = struct {
return self.new(.none, .{});
}
pub fn new_script(self: *Environment, options: NewScriptOptions) RuntimeError!*Object {
pub fn new_script(self: *Environment, options: NewScriptOptions) coral.io.AllocationError!*Object {
// TODO: Implement.
_ = self;
_ = options;
@ -110,30 +123,21 @@ pub const Environment = struct {
}
pub fn new_string(self: *Environment, string_data: []const u8) coral.io.AllocationError!*Object {
const string_behavior = &.{};
if (string_data.len == 0) {
// Empty string.
return self.new(.{.behavior = string_behavior});
}
const string_object = try self.new(.{
.userdata_size = string_data.len,
.behavior = string_behavior
const object = try self.new(.{
.userdata = string_data,
.behavior = &.{},
});
errdefer self.release(string_object);
errdefer self.release(object);
coral.io.copy(string_object.userdata, string_data);
return string_object;
return object;
}
pub fn new_string_from_float(self: *Environment, float: Float) coral.io.AllocationError!*Object {
pub fn new_string_from(self: *Environment, formats: []const coral.format.Value) coral.io.AllocationError!*Object {
// TODO: Implement.
_ = float;
coral.format.print(coral.io.null_writer, formats);
return self.new_string("0.0");
return self.new_string("");
}
pub fn new_string_from_integer(self: *Environment, integer: Integer) coral.io.AllocationError!*Object {
@ -143,13 +147,6 @@ pub const Environment = struct {
return self.new_string("0");
}
pub fn new_string_from_object(self: *Environment, object: *Object) coral.io.AllocationError!*Object {
// TODO: Implement.
_ = object;
return self.new_string("");
}
pub fn new_table(self: *Environment) coral.io.AllocationError!*Object {
// TODO: Implement.
return self.new(.none, .{});
@ -174,6 +171,23 @@ pub const Environment = struct {
}
}
pub fn user_cast(self: *Environment, object: *Object, expected_identity: *const anyopaque, comptime CastTarget: type) RuntimeError!*CastTarget {
// TODO: Emit failure message.
_ = self;
if (object.identity != expected_identity) {
// Identity does not match what was expected.
return error.InvalidOperation;
}
if (object.userdata.len != @sizeOf(CastTarget)) {
// Userdata size does not match target type.
return error.InvalidOperation;
}
return @ptrCast(*CastTarget, @alignCast(@alignOf(CastTarget), object.userdata));
}
pub fn virtual_call(self: *Environment, object: *Object, index: Value, arguments: []const Value) RuntimeError!Value {
const value = try self.virtual_get(object, index);
@ -217,39 +231,43 @@ pub const Integer = i32;
pub const Object = struct {
ref_count: usize,
userinfo: usize,
identity: *const anyopaque,
userdata: []u8,
behavior: *const Behavior,
fields: ValueTable,
pub const Behavior = struct {
caller: *const Caller = default_call,
deinitialize: *const Deinitializer = default_deinitialize,
getter: *const Getter = default_get,
setter: *const Setter = default_set,
destructor: *const Destructor = default_destruct,
fn default_call(_: CallContext) RuntimeError!Value {
return error.InvalidOperation;
}
fn default_destruct(_: DestructContext) void {
fn default_deinitialize(_: DeinitializeContext) void {
// Nothing to deinitialize by default.
}
fn default_get(context: GetContext) RuntimeError!Value {
const index = try switch (context.index) {
.object => |object| context.environment.new_string_from_object(object),
.integer => |integer| context.environment.new_string_from_integer(integer),
.float => |float| context.environment.new_string_from_float(float),
switch (context.index) {
.object => |index| {
if (!index.is_string()) return error.InvalidOperation;
return context.obj.fields.lookup(index.userdata) orelse .nil;
},
.integer => |integer| {
const index = context.env.new_string_from(&.{.{.signed = integer}});
defer context.env.release(index);
return context.obj.fields.lookup(index.userdata) orelse .nil;
},
else => return error.InvalidOperation,
};
defer context.environment.release(index);
// A string is just a serious of bytes (i.e. userdata with no userinfo).
coral.debug.assert(index.userinfo == 0);
return context.object.fields.lookup(index.userdata) orelse .nil;
}
}
fn default_set(_: SetContext) RuntimeError!void {
@ -258,41 +276,32 @@ pub const Object = struct {
};
pub const CallContext = struct {
environment: *Environment,
object: *Object,
arguments: []const Value,
pub fn check(self: CallContext, condition: bool, failure_message: []const u8) RuntimeError!void {
if (condition) return;
// TODO: Emit failure message.
_ = self;
_ = failure_message;
return error.CheckFailure;
}
env: *Environment,
caller: *Object,
callee: *Object,
args: []const Value,
};
pub const Caller = fn (context: CallContext) RuntimeError!Value;
pub const DestructContext = struct {
environment: *Environment,
object: *Object,
pub const DeinitializeContext = struct {
env: *Environment,
obj: *Object,
};
pub const Destructor = fn (context: DestructContext) void;
pub const Deinitializer = fn (context: DeinitializeContext) void;
pub const GetContext = struct {
environment: *Environment,
object: *const Object,
env: *Environment,
obj: *const Object,
index: Value,
};
pub const Getter = fn (context: GetContext) RuntimeError!Value;
pub const SetContext = struct {
environment: *Environment,
object: *Object,
env: *Environment,
obj: *Object,
index: Value,
value: Value,
};
@ -302,6 +311,11 @@ pub const Object = struct {
pub fn as_value(self: *Object) Value {
return .{.object = self};
}
pub fn is_string(self: Object) bool {
// Userdata represents a string (of bytes) if it's identity is it's userdata.
return self.identity == @ptrCast(*const anyopaque, self.userdata.ptr);
}
};
pub const RuntimeError = coral.io.AllocationError || error {

23
source/ona/canvas.zig Executable file
View File

@ -0,0 +1,23 @@
const coral = @import("coral");
pub const Item = struct {
transform: Transform,
options: union (enum) {
sprite: struct {
},
},
};
pub const Transform = extern struct {
x: coral.math.Vector2,
y: coral.math.Vector2,
origin: coral.math.Vector2,
pub const identity = Transform{
.x = .{1, 0},
.y = .{0, 1},
.origin = .{0, 0},
};
};

View File

@ -1,68 +0,0 @@
const coral = @import("coral");
const ext = @import("./ext.zig");
pub const Canvas = struct {
allocator: coral.io.Allocator,
items: coral.slots.Dense(coral.slots.addressable_key, Item),
const Item = union (enum) {
sprite: struct {
transform: Transform2D,
},
};
pub fn create_sprite(self: *Canvas, transform: Transform2D) coral.io.AllocationError!Sprite {
const slot = try self.items.insert(self.allocator, .{.sprite = .{
.transform = transform,
}});
errdefer coral.debug.assert(self.items.remove(slot));
return Sprite{
.slot = slot,
};
}
pub fn deinit(self: *Canvas) void {
self.items.free(self.allocator);
}
pub fn destroy_sprite(self: *Canvas, sprite: Sprite) void {
self.items.remove(sprite) catch unreachable;
}
pub fn init(allocator: coral.io.Allocator) Canvas {
return .{
.allocator = allocator,
.items = .{},
};
}
};
pub const Label = struct {
index: u32,
version: u32,
};
pub const Sprite = struct {
slot: coral.slots.Slot(coral.slots.addressable_key),
};
pub const Transform2D = extern struct {
x: coral.math.Vector2,
y: coral.math.Vector2,
origin: coral.math.Vector2,
pub fn from_trs(translation_x: f32, translation_y: f32, rotation: f32, scale_x: f32, scale_y: f32) Transform2D {
return identity.scaled(scale_x, scale_y).multiply(
identity.rotated(rotation).multiply(
identity.translated(translation_x, translation_y)));
}
pub const identity = Transform2D{
.x = coral.math.Vector2{1, 0},
.y = coral.math.Vector2{0, 1},
.origin = coral.math.Vector2{0, 0},
};
};

View File

@ -1,53 +1,66 @@
pub const canvas = @import("./canvas.zig");
const coral = @import("coral");
const ext = @import("./ext.zig");
pub const files = @import("./files.zig");
pub const gfx = @import("./gfx.zig");
pub const App = opaque {
pub fn Starter(comptime errors: type) type {
return fn (app_state: *App) errors!void;
}
const State = struct {
last_event: ext.SDL_Event,
base_file_sandbox: files.FileSandbox,
window: *ext.SDL_Window,
renderer: *ext.SDL_Renderer,
canvas: gfx.Canvas,
last_tick: u64,
tick_rate: u64 = 16,
canvas_items: coral.slots.Dense(coral.slots.addressable_key, canvas.Item),
fn cast(self: *App) *State {
return @ptrCast(*State, @alignCast(@alignOf(State), self));
}
};
pub fn canvas(self: *App) *gfx.Canvas {
return &State.cast(self).canvas;
}
pub fn data_fs(self: *App) files.FileAccessor {
return files.FileAccessor.bind(files.FileSandbox, &State.cast(self).base_file_sandbox);
}
pub fn poll(self: *App) bool {
pub fn loop(self: *App) void {
const state = State.cast(self);
while (ext.SDL_PollEvent(&state.last_event) != 0) switch (state.last_event.type) {
ext.SDL_QUIT => return false,
else => {},
};
while (true) {
const current_tick = ext.SDL_GetTicks64();
var event = @as(ext.SDL_Event, undefined);
return true;
}
while (ext.SDL_PollEvent(&event) != 0) {
switch (event.type) {
ext.SDL_QUIT => return,
else => {},
}
}
pub fn present(self: *App) void {
for (self.canvas().items.values) |item| {
switch (item) {
.sprite => {
// TODO: Implement.
},
if ((current_tick - state.last_tick) >= state.tick_rate) {
// TODO: Perform game updates.
if (ext.SDL_SetRenderDrawColor(state.renderer, 255, 255, 255, 255) != 0) {
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
}
if (ext.SDL_RenderClear(state.renderer) != 0) {
return log_error(&.{.{.string = coral.io.slice_sentineled(u8, 0, ext.SDL_GetError())}});
}
for (state.canvas_items.values) |canvas_item| {
switch (canvas_item.options) {
.sprite => {},
}
}
ext.SDL_RenderPresent(state.renderer);
state.last_tick = current_tick;
}
}
}
@ -80,10 +93,10 @@ pub const App = opaque {
defer ext.SDL_DestroyRenderer(renderer);
return start(@ptrCast(*App, &App.State{
.last_event = undefined,
.window = window,
.renderer = renderer,
.canvas = gfx.Canvas.init(allocator),
.last_tick = 0,
.canvas_items = .{},
.base_file_sandbox = .{
.prefix = coral.io.slice_sentineled(u8, 0, base_prefix),
@ -133,6 +146,24 @@ var heap = struct {
}
}{};
pub const CanvasItem = struct {
app: *App,
id: coral.slots.Slot(coral.slots.addressable_key),
pub fn deinit(self: *CanvasItem) void {
coral.debug.assert(App.State.cast(self.app).canvas_items.remove(self.id));
}
pub fn init(app: *App, canvas_item: canvas.Item) coral.io.AllocationError!CanvasItem {
const slot = try App.State.cast(app).canvas_items.insert(allocator, canvas_item);
return CanvasItem{
.app = app,
.id = slot,
};
}
};
pub fn log_debug(values: []const coral.format.Value) void {
var message_memory = [_]u8{0} ** 4096;
var message_buffer = coral.buffer.Fixed{.data = &message_memory};

View File

@ -4,72 +4,82 @@ const kym = @import("kym");
const ona = @import("ona");
const transform2d_typename = @typeName(ona.gfx.Transform2D);
const transform_typeid = @typeName(ona.canvas.Transform);
fn bind_canvas(environment: *kym.vm.Environment, canvas: *ona.gfx.Canvas) !void {
const object = try environment.new(.{});
fn new_canvas_item(environment: *kym.vm.Environment, canvas_item: ona.CanvasItem) !*kym.vm.Object {
const typeid = @typeName(ona.CanvasItem);
defer environment.release(object);
const sprite_creator = try environment.new(.{
.userinfo = @ptrToInt(canvas),
.behavior = &.{
.caller = struct {
fn call(context: kym.vm.Object.CallContext) kym.vm.RuntimeError!kym.vm.Value {
try context.check(context.arguments.len == 2, "2 arguments expected");
const transform = context.arguments[0].object;
try context.check(transform.userinfo == @ptrToInt(transform2d_typename), "`transform2d` expected");
_ = try @intToPtr(*ona.gfx.Canvas, context.object.userinfo).create_sprite(
coral.io.bytes_to(ona.gfx.Transform2D, transform.userdata).?);
return .nil;
}
}.call,
},
var object = try environment.new(.{
.userdata = coral.io.bytes_of(&canvas_item),
.identity = typeid,
});
defer environment.release(sprite_creator);
errdefer environment.release(object);
try environment.raw_set(object, "create_sprite", sprite_creator.as_value());
try environment.global_set("canvas", object.as_value());
return object;
}
// fn bind_transform_2d(environment: *kym.Environment) !*kym.Object {
// const allocator = environment.allocator();
// const transform_2d = try allocator.allocate_one(ona.gfx.Transform2D);
fn new_transform(environment: *kym.Environment, transform: ona.canvas.Transform) !*kym.vm.Object {
var object = try environment.new(.{
.userdata = coral.io.bytes_of(&transform),
.identity = transform_typeid,
});
// errdefer allocator.deallocate(transform_2d);
errdefer environment.release(object);
// const object = try environment.new(@ptrToInt(transform_2d), .{
// .destructor = struct {
// fn destruct() void {
// allocator.deallocate(transform_2d);
// }
// }.destruct,
// });
// defer environment.release(object);
// try environment.set_field(environment.globals(), object);
// }
return object;
}
pub fn main() anyerror!void {
return ona.App.run(anyerror, start);
}
fn start(app: *ona.App) anyerror!void {
var kym_env = try kym.vm.Environment.init(ona.allocator, .{
var kym_environment = try kym.vm.Environment.init(ona.allocator, .{
.stack_max = 256,
.calls_max = 256,
});
defer kym_env.deinit();
defer kym_environment.deinit();
try bind_canvas(&kym_env, app.canvas());
const Ona = struct {
app: *ona.App,
const typeid = @typeName(ona.App);
};
var ona_lib = try kym_environment.new(.{
.userdata = coral.io.bytes_of(&Ona{
.app = app,
}),
.identity = Ona.typeid,
});
defer kym_environment.release(ona_lib);
try kym_environment.global_set("ona", ona_lib.as_value());
{
var sprite_creator = try kym_environment.new(.{.behavior = &.{.caller = struct {
fn call(context: kym.vm.Object.CallContext) kym.vm.RuntimeError!kym.vm.Value {
try context.env.check(context.args.len == 2, "2 arguments expected");
var item = try ona.CanvasItem.init((try context.env.user_cast(context.caller, Ona.typeid, Ona)).app, .{
.transform = (try context.env.user_cast(context.args[0].object, transform_typeid, ona.canvas.Transform)).*,
.options = .{.sprite = .{}},
});
errdefer item.deinit();
return (try new_canvas_item(context.env, item)).as_value();
}
}.call}});
defer kym_environment.release(sprite_creator);
try kym_environment.raw_set(ona_lib, "create_sprite", sprite_creator.as_value());
}
{
const index_path = "index.kym";
@ -92,18 +102,16 @@ fn start(app: *ona.App) anyerror!void {
}
{
const index_script = try kym_env.new_script(.{
const index_script = try kym_environment.new_script(.{
.name = index_path,
.data = index_buffer.data
});
defer kym_env.release(index_script);
defer kym_environment.release(index_script);
_ = try kym_env.call(index_script, &.{});
_ = try kym_environment.call(index_script, &.{});
}
}
while (app.poll()) {
app.present();
}
app.loop();
}