Add GUI module

This commit is contained in:
kayomn 2024-07-29 23:50:40 +01:00
parent bb1d383ccb
commit 7e8691c050
18 changed files with 859 additions and 599 deletions

2
.vscode/launch.json vendored
View File

@ -5,7 +5,7 @@
"name": "Runner", "name": "Runner",
"type": "gdb", "type": "gdb",
"request": "launch", "request": "launch",
"target": "${workspaceRoot}/demos/effects.out", "target": "${workspaceRoot}/demos/canvas.out",
"cwd": "${workspaceRoot}/demos/", "cwd": "${workspaceRoot}/demos/",
"valuesFormatting": "prettyPrinters", "valuesFormatting": "prettyPrinters",
"preLaunchTask": "Build All" "preLaunchTask": "Build All"

View File

@ -254,6 +254,20 @@ pub fn build(b: *std.Build) !void {
gfx_module.link_libc = true; gfx_module.link_libc = true;
_ = try project.add_module(b, "gui", .{
.imports = &.{
.{
.name = "ona",
.module = ona_module,
},
.{
.name = "gfx",
.module = gfx_module,
},
},
});
project.find_demos(b.step("demos", "Build demos")); project.find_demos(b.step("demos", "Build demos"));
project.find_tests(b.step("tests", "Build and run tests")); project.find_tests(b.step("tests", "Build and run tests"));
} }

31
demos/canvas.zig Normal file
View File

@ -0,0 +1,31 @@
const gfx = @import("gfx");
const gui = @import("gui");
const hid = @import("hid");
const ona = @import("ona");
pub const main = ona.App.game
.with_module(gfx)
.with_module(hid)
.with_module(gui)
.with_system(.load, ona.system_fn(load), .{ .label = "Hello Ona" }).build();
pub fn load(canvas: ona.Write(gui.Canvas)) !void {
const item = try canvas.state.append("", .{
.left = 0,
.top = 0,
.width = 256,
.height = 256,
});
canvas.state.set_block(item, .{
.has_background = .default,
.has_color = gfx.colors.purple,
});
try canvas.state.set_label(item, .{
.has_text = "Hello, world",
});
}

View File

@ -56,23 +56,23 @@ fn load(display: ona.Write(gfx.Display), effects: ona.Write(Effects), assets: on
}; };
} }
pub const main = ona.App.setup. pub const main = ona.App.game
with_module(gfx). .with_module(gfx)
with_state(Effects{}). .with_state(Effects{})
with_system(.load, ona.system_fn(load), .{.label = "load effects"}). .with_system(.load, ona.system_fn(load), .{.label = "load effects"})
with_system(.update, ona.system_fn(update), .{.label = "update effects"}). .with_system(.update, ona.system_fn(update), .{.label = "update effects"})
with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build(); .with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build();
fn update(effects: ona.Write(Effects), app: ona.Read(ona.App)) void { fn update(effects: ona.Write(Effects), loop: ona.Read(ona.Loop)) void {
const update_seconds = 5; const update_seconds = 5;
if ((app.state.elapsed_time - effects.state.last_time) > update_seconds) { if ((loop.state.elapsed_time - effects.state.last_time) > update_seconds) {
effects.state.image_index = (effects.state.image_index + 1) % effects.state.image_textures.len; effects.state.image_index = (effects.state.image_index + 1) % effects.state.image_textures.len;
effects.state.last_time = app.state.elapsed_time; effects.state.last_time = loop.state.elapsed_time;
} }
} }
fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona.App), display: ona.Write(gfx.Display)) !void { fn render(commands: gfx.Commands(.background), effects: ona.Write(Effects), loop: ona.Read(ona.Loop), display: ona.Write(gfx.Display)) !void {
try commands.set_target(.{ try commands.set_target(.{
.texture = effects.state.render_texture, .texture = effects.state.render_texture,
.clear_color = gfx.colors.black, .clear_color = gfx.colors.black,
@ -80,26 +80,21 @@ fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona
.clear_stencil = 0, .clear_stencil = 0,
}); });
const display_width: f32 = @floatFromInt(display.state.width); const width: f32 = @floatFromInt(display.state.width);
const display_height: f32 = @floatFromInt(display.state.height); const height: f32 = @floatFromInt(display.state.height);
const display_transform = gfx.transform_2d(.{
.translation = .{display_width / 2, display_height / 2},
});
try commands.draw_texture(.{ try commands.draw_texture(.{
.texture = effects.state.image_textures[effects.state.image_index], .texture = effects.state.image_textures[effects.state.image_index],
.transform = display_transform, .size = .{width, height},
.resolution = .{display.state.width, display.state.height},
}); });
try commands.set_effect(.{ try commands.set_effect(.{
.effect = effects.state.crt_effect, .effect = effects.state.crt_effect,
.properties = std.mem.asBytes(&CRT{ .properties = std.mem.asBytes(&CRT{
.width = display_width, .width = width,
.height = display_height, .height = height,
.time = @floatCast(app.state.elapsed_time), .time = @floatCast(loop.state.elapsed_time),
}), }),
}); });
@ -111,7 +106,7 @@ fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona
try commands.draw_texture(.{ try commands.draw_texture(.{
.texture = effects.state.render_texture, .texture = effects.state.render_texture,
.transform = display_transform, .size = .{width, height},
}); });
} }

View File

@ -9,7 +9,8 @@ const std = @import("std");
const Spawned = struct { const Spawned = struct {
visual: struct { visual: struct {
color: gfx.Color, color: gfx.Color,
transform: gfx.Transform2D, position: gfx.Vector2,
rotation: f32,
}, },
lifetime_seconds: f32, lifetime_seconds: f32,
@ -23,7 +24,6 @@ const Visuals = struct {
fn cleanup(visuals: ona.Write(Visuals)) !void { fn cleanup(visuals: ona.Write(Visuals)) !void {
visuals.state.spawned.deinit(); visuals.state.spawned.deinit();
visuals.state.spawned.deinit();
} }
fn load(visuals: ona.Write(Visuals)) !void { fn load(visuals: ona.Write(Visuals)) !void {
@ -32,14 +32,14 @@ fn load(visuals: ona.Write(Visuals)) !void {
try visuals.state.spawned.grow(initial_spawn_capacity); try visuals.state.spawned.grow(initial_spawn_capacity);
} }
pub const main = ona.App.setup. pub const main = ona.App.game
with_module(gfx). .with_module(gfx)
with_module(hid). .with_module(hid)
with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)}). .with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)})
with_system(.load, ona.system_fn(load), .{.label = "load visuals"}). .with_system(.load, ona.system_fn(load), .{.label = "load visuals"})
with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"}). .with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"})
with_system(.render, ona.system_fn(render), .{.label = "render visuals"}). .with_system(.render, ona.system_fn(render), .{.label = "render visuals"})
with_system(.exit, ona.system_fn(cleanup), .{.label = "clean up visuals"}).build(); .with_system(.exit, ona.system_fn(cleanup), .{.label = "clean up visuals"}).build();
fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: ona.Read(gfx.Display)) !void { fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: ona.Read(gfx.Display)) !void {
update_spawned(&visuals.state.spawned); update_spawned(&visuals.state.spawned);
@ -47,7 +47,6 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display:
const random = visuals.state.random.random(); const random = visuals.state.random.random();
const width: f32 = @floatFromInt(display.state.width); const width: f32 = @floatFromInt(display.state.width);
const height: f32 = @floatFromInt(display.state.height); const height: f32 = @floatFromInt(display.state.height);
const icon_scale = .{8, 8};
for (events.messages()) |event| { for (events.messages()) |event| {
switch (event) { switch (event) {
@ -57,12 +56,8 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display:
.visual = .{ .visual = .{
.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, .color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)},
.position = .{width * random.float(f32), height},
.transform = gfx.transform_2d(.{
.translation = .{width * random.float(f32), height},
.rotation = std.math.pi * random.float(f32), .rotation = std.math.pi * random.float(f32),
.scale = icon_scale,
}),
}, },
}); });
}, },
@ -73,12 +68,8 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display:
.visual = .{ .visual = .{
.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, .color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)},
.position = visuals.state.mouse_position,
.transform = gfx.transform_2d(.{
.translation = visuals.state.mouse_position,
.rotation = std.math.pi * random.float(f32), .rotation = std.math.pi * random.float(f32),
.scale = icon_scale,
}),
}, },
}); });
}, },
@ -96,7 +87,7 @@ fn update_spawned(spawned: *ona.stack.Parallel(Spawned)) void {
const float_speed = 6; const float_speed = 6;
for (spawned.values.slice(.visual)) |*visual| { for (spawned.values.slice(.visual)) |*visual| {
visual.transform = visual.transform.translated(.{0, -float_speed}); visual.position -= .{0, float_speed};
} }
{ {
@ -119,11 +110,13 @@ fn update_spawned(spawned: *ona.stack.Parallel(Spawned)) void {
} }
} }
fn render(visuals: ona.Write(Visuals), commands: gfx.Commands) !void { fn render(visuals: ona.Write(Visuals), commands: gfx.Commands(.foreground)) !void {
for (visuals.state.spawned.values.slice(.visual)) |visual| { for (visuals.state.spawned.values.slice(.visual)) |visual| {
try commands.draw_rect(.{ try commands.draw_texture(.{
.transform = visual.transform, .anchor = @splat(0.5),
.color = visual.color, .position = visual.position,
.tint = visual.color,
.size = @as(gfx.Vector2, @splat(64)),
}); });
} }
} }

View File

@ -38,7 +38,7 @@ pub const Effect = struct {
}, },
.vertex_source = .{ .vertex_source = .{
.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), .ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.vert.spv")),
}, },
.fragment_source = .{ .fragment_source = .{
@ -197,12 +197,12 @@ pub const Effect = struct {
}; };
attrs[2] = .{ attrs[2] = .{
.format = .FLOAT2, .format = .FLOAT,
.buffer_index = 1, .buffer_index = 1,
}; };
attrs[3] = .{ attrs[3] = .{
.format = .FLOAT2, .format = .FLOAT,
.buffer_index = 1, .buffer_index = 1,
}; };
@ -212,12 +212,12 @@ pub const Effect = struct {
}; };
attrs[5] = .{ attrs[5] = .{
.format = .UBYTE4N, .format = .FLOAT2,
.buffer_index = 1, .buffer_index = 1,
}; };
attrs[6] = .{ attrs[6] = .{
.format = .FLOAT, .format = .FLOAT2,
.buffer_index = 1, .buffer_index = 1,
}; };
@ -226,6 +226,11 @@ pub const Effect = struct {
.buffer_index = 1, .buffer_index = 1,
}; };
attrs[8] = .{
.format = .FLOAT4,
.buffer_index = 1,
};
break: get attrs; break: get attrs;
}, },
@ -466,7 +471,7 @@ pub fn init() !Self {
} }
assert.is_default_handle(try pools.create_effect(.{ assert.is_default_handle(try pools.create_effect(.{
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), .fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.frag.spv")),
})); }));
assert.is_default_handle(try pools.create_texture(try descs.solid_texture(.{ assert.is_default_handle(try pools.create_texture(try descs.solid_texture(.{

View File

@ -27,40 +27,7 @@ pub const Assets = struct {
load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc, load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc,
}; };
pub const WorkQueue = ona.asyncio.BlockingQueue(1024, union (enum) { pub const WorkQueue = ona.asyncio.BlockingQueue(1024, Work);
load_effect: LoadEffectWork,
load_texture: LoadTextureWork,
render_frame: RenderFrameWork,
shutdown,
unload_effect: UnloadEffectWork,
unload_texture: UnloadTextureWork,
const LoadEffectWork = struct {
desc: Effect.Desc,
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect),
};
const LoadTextureWork = struct {
desc: Texture.Desc,
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture),
};
const RenderFrameWork = struct {
clear_color: Color,
width: u16,
height: u16,
finished: *std.Thread.ResetEvent,
has_command_params: ?*ona.Params(Commands).Node,
};
const UnloadEffectWork = struct {
handle: Effect,
};
const UnloadTextureWork = struct {
handle: Texture,
};
});
fn deinit(self: *Assets) void { fn deinit(self: *Assets) void {
self.pending_work.enqueue(.shutdown); self.pending_work.enqueue(.shutdown);
@ -72,12 +39,10 @@ pub const Assets = struct {
self.* = undefined; self.* = undefined;
} }
fn init() !Assets { fn init(width: u16, height: u16) !Assets {
const window = create: { const window = create: {
const position = ext.SDL_WINDOWPOS_CENTERED; const position = ext.SDL_WINDOWPOS_CENTERED;
const flags = ext.SDL_WINDOW_OPENGL; const flags = ext.SDL_WINDOW_OPENGL;
const width = 640;
const height = 480;
break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse { break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
return error.Unsupported; return error.Unsupported;
@ -138,42 +103,60 @@ pub const Assets = struct {
} }
pub const thread_restriction = .main; pub const thread_restriction = .main;
pub fn unload_effect(self: *Assets, handle: Effect) void {
self.pending_work.enqueue(.{.unload_effect = handle});
}
pub fn unload_texture(self: *Assets, handle: Texture) void {
self.pending_work.enqueue(.{.unload_texture = handle});
}
}; };
pub const Color = @Vector(4, f32); pub const Color = @Vector(4, f32);
pub const Commands = struct {
pending: *List,
pub const Command = union (enum) { pub const Command = union (enum) {
draw_rect: DrawRectCommand, draw_font: DrawFont,
draw_texture: DrawTextureCommand, draw_texture: DrawTexture,
set_effect: SetEffectCommand, set_effect: SetEffect,
set_target: SetTargetCommand, set_target: SetTarget,
pub const DrawFont = struct {
position: Vector2 = @splat(0),
size: Vector2,
text: []const u8,
font: Font,
tint: Color,
depth: f32,
}; };
pub const DrawRectCommand = struct { pub const DrawTexture = struct {
color: Color, anchor: Vector2 = @splat(0),
transform: Transform2D, size: ?Vector2 = null,
source: Rect = .{.left = 0, .top = 0, .right = 1, .bottom = 1},
position: Vector2 = @splat(0),
tint: Color = colors.white,
texture: Texture = .default,
rotation: f32 = 0,
depth: f32 = 0,
}; };
pub const DrawTextureCommand = struct { pub const SetEffect = struct {
texture: Texture,
transform: Transform2D,
resolution: ?@Vector(2, u16) = null,
};
pub const SetEffectCommand = struct {
effect: Effect, effect: Effect,
properties: []const u8, properties: []const u8,
}; };
pub const SetTargetCommand = struct { pub const SetTarget = struct {
texture: ?Texture = null, texture: ?Texture = null,
clear_color: ?Color, clear_color: ?Color,
clear_depth: ?f32, clear_depth: ?f32,
clear_stencil: ?u8, clear_stencil: ?u8,
}; };
};
pub fn Commands(comptime layer: Layer) type {
return struct {
pending: *List,
pub const List = struct { pub const List = struct {
arena: std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
@ -231,6 +214,8 @@ pub const Commands = struct {
} }
}; };
const Self = @This();
pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param { pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param {
return .{ return .{
.swap_lists = .{ .swap_lists = .{
@ -240,21 +225,30 @@ pub const Commands = struct {
}; };
} }
pub fn init(param: *Param) Commands { pub fn init(param: *Param) Self {
return .{ return .{
.pending = param.pending_list(), .pending = param.pending_list(),
}; };
} }
pub fn draw_rect(self: Commands, command: DrawRectCommand) std.mem.Allocator.Error!void { pub fn draw_font(self: Self, command: Command.DrawFont) std.mem.Allocator.Error!void {
try self.pending.stack.push_grow(.{.draw_rect = command}); try self.pending.stack.push_grow(.{
.draw_font = .{
.text = try self.pending.arena.allocator().dupe(u8, command.text),
.position = command.position,
.size = command.size,
.font = command.font,
.tint = command.tint,
.depth = command.depth,
},
});
} }
pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void { pub fn draw_texture(self: Self, command: Command.DrawTexture) std.mem.Allocator.Error!void {
try self.pending.stack.push_grow(.{.draw_texture = command}); try self.pending.stack.push_grow(.{.draw_texture = command});
} }
pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void { pub fn set_effect(self: Self, command: Command.SetEffect) std.mem.Allocator.Error!void {
try self.pending.stack.push_grow(.{ try self.pending.stack.push_grow(.{
.set_effect = .{ .set_effect = .{
.properties = try self.pending.arena.allocator().dupe(u8, command.properties), .properties = try self.pending.arena.allocator().dupe(u8, command.properties),
@ -263,14 +257,17 @@ pub const Commands = struct {
}); });
} }
pub const _ = layer;
pub fn unbind(param: *Param, _: ona.World.UnbindContext) void { pub fn unbind(param: *Param, _: ona.World.UnbindContext) void {
param.deinit(); param.deinit();
} }
pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void { pub fn set_target(self: Self, command: Command.SetTarget) std.mem.Allocator.Error!void {
try self.pending.stack.push_grow(.{.set_target = command}); try self.pending.stack.push_grow(.{.set_target = command});
} }
}; };
}
pub const Descs = struct { pub const Descs = struct {
arena: std.heap.ArenaAllocator, arena: std.heap.ArenaAllocator,
@ -431,7 +428,24 @@ pub const Effect = enum (u32) {
}; };
}; };
pub const Rect = struct { pub const Font = enum (u32) {
default,
_,
pub const Desc = struct {
};
};
pub const Layer = enum {
background,
foreground,
overlay,
const values = std.enums.values(Layer);
};
pub const Rect = extern struct {
left: f32, left: f32,
top: f32, top: f32,
right: f32, right: f32,
@ -474,58 +488,47 @@ pub const Texture = enum (u32) {
}; };
}; };
pub const Transform2D = extern struct { pub const Vector2 = @Vector(2, f32);
xbasis: Vector = .{1, 0},
ybasis: Vector = .{0, 1},
origin: Vector = @splat(0),
pub const Simplified = struct { const Work = union (enum) {
translation: Vector = @splat(0), load_effect: LoadEffect,
rotation: f32 = 0, load_texture: LoadTexture,
scale: Vector = @splat(1), render_frame: RenderFrame,
skew: f32 = 0, shutdown,
unload_effect: UnloadEffect,
unload_texture: UnloadTexture,
const LoadEffect = struct {
desc: Effect.Desc,
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect),
}; };
pub const Vector = @Vector(2, f32); const LoadTexture = struct {
desc: Texture.Desc,
pub fn scaled(self: Transform2D, scale: Vector) Transform2D { loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture),
var transform = self;
const scale_x, const scale_y = scale;
transform.xbasis *= @splat(scale_x);
transform.ybasis *= @splat(scale_y);
return transform;
}
pub fn transformed(self: Transform2D, other: Transform2D) Transform2D {
const xbasis_x, const xbasis_y = other.xbasis;
const ybasis_x, const ybasis_y = other.ybasis;
const origin_x, const origin_y = other.origin;
return .{
.xbasis =
(self.xbasis * @as(Vector, @splat(xbasis_x))) +
(self.ybasis * @as(Vector, @splat(xbasis_y))),
.ybasis =
(self.xbasis * @as(Vector, @splat(ybasis_x))) +
(self.ybasis * @as(Vector, @splat(ybasis_y))),
.origin =
(self.xbasis * @as(Vector, @splat(origin_x))) +
(self.ybasis * @as(Vector, @splat(origin_y))) +
self.origin,
}; };
}
pub fn translated(self: Transform2D, translation: Vector) Transform2D { const RenderFrame = struct {
var transform = self; clear_color: Color,
width: u16,
height: u16,
finished: *std.Thread.ResetEvent,
commands_set: CommandsSet,
transform.origin += translation; const CommandsSet = struct {
?*ona.Params(Commands(.background)).Node,
?*ona.Params(Commands(.foreground)).Node,
?*ona.Params(Commands(.overlay)).Node,
};
};
return transform; const UnloadEffect = struct {
} handle: Effect,
};
const UnloadTexture = struct {
handle: Texture,
};
}; };
pub const colors = struct { pub const colors = struct {
@ -546,6 +549,8 @@ pub const colors = struct {
pub const purple = rgb(0.5, 0, 0.5); pub const purple = rgb(0.5, 0, 0.5);
pub const red = rgb(1, 0, 0);
pub fn rgb(r: f32, g: f32, b: f32) Color { pub fn rgb(r: f32, g: f32, b: f32) Color {
return .{r, g, b, 1}; return .{r, g, b, 1};
} }
@ -553,13 +558,13 @@ pub const colors = struct {
pub const white = greyscale(1); pub const white = greyscale(1);
}; };
pub fn poll(app: ona.Write(ona.App), events: ona.Send(hid.Event)) !void { pub fn poll(loop: ona.Write(ona.Loop), events: ona.Send(hid.Event)) !void {
var event = @as(ext.SDL_Event, undefined); var event = @as(ext.SDL_Event, undefined);
while (ext.SDL_PollEvent(&event) != 0) { while (ext.SDL_PollEvent(&event) != 0) {
switch (event.type) { switch (event.type) {
ext.SDL_QUIT => { ext.SDL_QUIT => {
app.state.quit(); loop.state.quit();
}, },
ext.SDL_KEYUP => { ext.SDL_KEYUP => {
@ -606,13 +611,15 @@ pub fn poll(app: ona.Write(ona.App), events: ona.Send(hid.Event)) !void {
} }
} }
pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { pub fn setup(world: *ona.World) !void {
if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) { if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) {
return error.Unsupported; return error.Unsupported;
} }
const display = try world.set_get_state(Display{});
const assets = create: { const assets = create: {
var assets = try Assets.init(); var assets = try Assets.init(display.width, display.height);
errdefer { errdefer {
assets.deinit(); assets.deinit();
@ -632,10 +639,9 @@ pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} ||
assets.window, assets.window,
}); });
try world.set_state(Display{}); try world.on_event(.pre_update, ona.system_fn(poll), .{.label = "poll gfx"});
try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll gfx"}); try world.on_event(.exit, ona.system_fn(stop), .{.label = "stop gfx"});
try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop gfx"}); try world.on_event(.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"});
try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"});
} }
pub fn stop(assets: ona.Write(Assets)) void { pub fn stop(assets: ona.Write(Assets)) void {
@ -648,8 +654,8 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void {
assets.frame_rendered.wait(); assets.frame_rendered.wait();
assets.frame_rendered.reset(); assets.frame_rendered.reset();
{ inline for (Layer.values) |layer| {
var has_command_param = exclusive.world.get_params(Commands).has_head; var has_command_param = exclusive.world.get_params(Commands(layer)).has_head;
while (has_command_param) |command_param| : (has_command_param = command_param.has_next) { while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
command_param.param.rotate(); command_param.param.rotate();
@ -664,35 +670,27 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void {
ext.SDL_SetWindowSize(assets.window, display.width, display.height); ext.SDL_SetWindowSize(assets.window, display.width, display.height);
} }
if (exclusive.world.get_params(Commands).has_head) |command_param| { var commands_filled: usize = 0;
var commands_set = Work.RenderFrame.CommandsSet{null, null, null};
inline for (0 .. Layer.values.len, Layer.values) |i, layer| {
if (exclusive.world.get_params(Commands(layer)).has_head) |command_param| {
commands_set[i] = command_param;
commands_filled += 1;
}
}
if (commands_filled == 0) {
assets.frame_rendered.set();
} else {
assets.pending_work.enqueue(.{ assets.pending_work.enqueue(.{
.render_frame = .{ .render_frame = .{
.has_command_params = command_param, .commands_set = commands_set,
.width = display.width, .width = display.width,
.height = display.height, .height = display.height,
.clear_color = display.clear_color, .clear_color = display.clear_color,
.finished = &assets.frame_rendered, .finished = &assets.frame_rendered,
}, },
}); });
} else {
assets.frame_rendered.set();
} }
} }
pub fn transform_2d(simplified: Transform2D.Simplified) Transform2D {
const rotation_skew = simplified.rotation + simplified.skew;
return .{
.xbasis = simplified.scale * Transform2D.Vector{
std.math.cos(simplified.rotation),
std.math.sin(simplified.rotation),
},
.ybasis = simplified.scale * Transform2D.Vector{
-std.math.sin(rotation_skew),
std.math.cos(rotation_skew),
},
.origin = simplified.translation,
};
}

View File

@ -23,15 +23,23 @@ const Frame = struct {
current_source_texture: gfx.Texture = .default, current_source_texture: gfx.Texture = .default,
current_target_texture: ?gfx.Texture = null, current_target_texture: ?gfx.Texture = null,
current_effect: gfx.Effect = .default, current_effect: gfx.Effect = .default,
transform_matrix: Matrix = identity_matrix,
width: u16 = 0, width: u16 = 0,
height: u16 = 0, height: u16 = 0,
const DrawTexture = extern struct { const DrawTexture = extern struct {
transform: gfx.Transform2D, rotation: f32,
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), depth: f32,
depth: f32 = 0, position: gfx.Vector2,
texture_offset: @Vector(2, f32) = @splat(0), size: gfx.Vector2,
texture_size: @Vector(2, f32) = @splat(1), anchor: gfx.Vector2,
source_clip: gfx.Rect,
tint: gfx.Color,
};
const View = extern struct {
projection_matrix: Matrix,
transform_matrix: Matrix,
}; };
const batches_per_buffer = 512; const batches_per_buffer = 512;
@ -75,35 +83,13 @@ const Frame = struct {
}; };
} }
pub fn draw_rect(self: *Frame, resources: *Resources, command: gfx.Commands.DrawRectCommand) !void { pub fn draw_font(self: *Frame, resources: *Resources, command: gfx.Command.DrawFont) !void {
if (self.current_source_texture != .default) { _ = self;
self.flush(resources); _ = resources;
_ = command;
} }
self.current_source_texture = .default; pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Command.DrawTexture) !void {
const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0;
const buffer_count = self.drawn_count / batches_per_buffer;
if (has_filled_current_buffer and buffer_count == self.texture_batch_buffers.len()) {
const instance_buffer = sokol.gfx.makeBuffer(.{
.size = @sizeOf(DrawTexture) * batches_per_buffer,
.usage = .STREAM,
});
errdefer sokol.gfx.destroyBuffer(instance_buffer);
try self.texture_batch_buffers.push_grow(instance_buffer);
}
_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{
.transform = command.transform,
}));
self.drawn_count += 1;
}
pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Commands.DrawTextureCommand) !void {
if (self.current_source_texture != command.texture) { if (self.current_source_texture != command.texture) {
self.flush(resources); self.flush(resources);
} }
@ -127,10 +113,13 @@ const Frame = struct {
const texture = resources.get_texture(command.texture).?; const texture = resources.get_texture(command.texture).?;
_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{ _ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{
.transform = command.transform.scaled(@floatFromInt(command.resolution orelse .{ .size = command.size orelse .{@floatFromInt(texture.width), @floatFromInt(texture.height)},
texture.width, .anchor = command.anchor,
texture.height, .rotation = command.rotation,
})), .depth = command.depth,
.source_clip = command.source,
.tint = command.tint,
.position = command.position,
})); }));
self.drawn_count += 1; self.drawn_count += 1;
@ -178,19 +167,27 @@ const Frame = struct {
if (self.current_target_texture) |target_texture| { if (self.current_target_texture) |target_texture| {
const texture = resources.get_texture(target_texture).?; const texture = resources.get_texture(target_texture).?;
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{ sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{
.projection_matrix = orthographic_projection(-1.0, 1.0, .{
.left = 0, .left = 0,
.top = 0, .top = 0,
.right = @floatFromInt(texture.width), .right = @floatFromInt(texture.width),
.bottom = @floatFromInt(texture.height), .bottom = @floatFromInt(texture.height),
}))); }),
.transform_matrix = self.transform_matrix,
}));
} else { } else {
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{ sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{
.projection_matrix = orthographic_projection(-1.0, 1.0, .{
.left = 0, .left = 0,
.top = 0, .top = 0,
.right = @floatFromInt(self.width), .right = @floatFromInt(self.width),
.bottom = @floatFromInt(self.height), .bottom = @floatFromInt(self.height),
}))); }),
.transform_matrix = self.transform_matrix,
}));
} }
if (effect.properties.len != 0) { if (effect.properties.len != 0) {
@ -215,7 +212,7 @@ const Frame = struct {
} }
} }
pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Commands.SetEffectCommand) void { pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Command.SetEffect) void {
if (command.effect != self.current_effect) { if (command.effect != self.current_effect) {
self.flush(resources); self.flush(resources);
} }
@ -227,7 +224,7 @@ const Frame = struct {
} }
} }
pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Commands.SetTargetCommand) void { pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Command.SetTarget) void {
sokol.gfx.endPass(); sokol.gfx.endPass();
var pass = sokol.gfx.Pass{ var pass = sokol.gfx.Pass{
@ -305,18 +302,23 @@ const Frame = struct {
} }
}; };
fn Matrix(comptime n: usize, comptime Element: type) type { const Matrix = [4]@Vector(4, f32);
return [n]@Vector(n, Element);
}
var default_sampler: sokol.gfx.Sampler = undefined; var default_sampler: sokol.gfx.Sampler = undefined;
const identity_matrix = Matrix{
.{1, 0, 0, 0},
.{0, 1, 0, 0},
.{0, 0, 1, 0},
.{0, 0, 0, 1},
};
const vertex_indices = .{ const vertex_indices = .{
.mesh = 0, .mesh = 0,
.instance = 1, .instance = 1,
}; };
fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix(4, f32) { fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix {
const width = viewport.right - viewport.left; const width = viewport.right - viewport.left;
const height = viewport.bottom - viewport.top; const height = viewport.bottom - viewport.top;
@ -397,15 +399,16 @@ pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window
} }
}, },
.render_frame => |render_frame| { .render_frame => |*render_frame| {
frame.start(render_frame.width, render_frame.height); frame.start(render_frame.width, render_frame.height);
var has_command_params = render_frame.has_command_params; inline for (&render_frame.commands_set) |has_commands| {
var has_commands_currently = has_commands;
while (has_command_params) |command_params| : (has_command_params = command_params.has_next) { while (has_commands_currently) |commands| : (has_commands_currently = commands.has_next) {
for (command_params.param.submitted_commands()) |command| { for (commands.param.submitted_commands()) |command| {
try switch (command) { try switch (command) {
.draw_rect => |draw_rect| frame.draw_rect(&resources, draw_rect), .draw_font => |draw_font| frame.draw_font(&resources, draw_font),
.draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture), .draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture),
.set_effect => |set_effect| frame.set_effect(&resources, set_effect), .set_effect => |set_effect| frame.set_effect(&resources, set_effect),
.set_target => |set_target| frame.set_target(&resources, set_target), .set_target => |set_target| frame.set_target(&resources, set_target),
@ -422,6 +425,7 @@ pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window
}); });
} }
} }
}
frame.finish(&resources); frame.finish(&resources);
ext.SDL_GL_SwapWindow(window); ext.SDL_GL_SwapWindow(window);

View File

@ -1,29 +0,0 @@
#version 430
layout (location = 0) in vec2 mesh_xy;
layout (location = 1) in vec2 mesh_uv;
layout (location = 2) in vec2 instance_xbasis;
layout (location = 3) in vec2 instance_ybasis;
layout (location = 4) in vec2 instance_origin;
layout (location = 5) in vec4 instance_tint;
layout (location = 6) in float instance_depth;
layout (location = 7) in vec4 instance_clip;
layout (location = 0) out vec4 color;
layout (location = 1) out vec2 uv;
layout (binding = 0) uniform View {
mat4 projection_matrix;
};
void main() {
const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis;
const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy;
const vec2 rect_size = instance_clip.zw - instance_clip.xy;
const vec4 screen_position = vec4(projected_position, instance_depth, 1.0);
gl_Position = screen_position;
color = instance_tint;
uv = instance_clip.xy + (mesh_uv * rect_size);
}

View File

@ -0,0 +1,42 @@
#version 430
layout (location = 0) in vec2 batch_xy;
layout (location = 1) in vec2 batch_uv;
layout (location = 2) in float instance_rotation;
layout (location = 3) in float instance_depth;
layout (location = 4) in vec2 instance_position;
layout (location = 5) in vec2 instance_scale;
layout (location = 6) in vec2 instance_anchor;
layout (location = 7) in vec4 instance_clip;
layout (location = 8) in vec4 instance_tint;
layout (location = 0) out vec4 color;
layout (location = 1) out vec2 uv;
layout (binding = 0) uniform View {
mat4 projection_matrix;
mat4 transform_matrix;
};
void main() {
const vec2 normalized_position = (batch_xy + 0.5) - instance_anchor;
const float cos_theta = cos(instance_rotation);
const float sin_theta = sin(instance_rotation);
const mat2 rotation_matrix = mat2(
cos_theta, -sin_theta,
sin_theta, cos_theta
);
const vec2 rotated_position = rotation_matrix * (normalized_position * instance_scale);
const vec2 world_position = instance_position + rotated_position;
const vec4 clip_space_position = projection_matrix * transform_matrix * vec4(world_position, 0.0, 1.0);
gl_Position = vec4(clip_space_position.xy, instance_depth, 1.0);
const vec2 clip_size = instance_clip.zw - instance_clip.xy;
uv = instance_clip.xy + (batch_uv * clip_size);
color = instance_tint;
}

View File

@ -232,9 +232,7 @@ pub const Stage = struct {
else => error.UnsupportedSPIRV, else => error.UnsupportedSPIRV,
}, },
.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse { .len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse 0,
return error.UnsupportedSPIRV;
},
}; };
} }

234
src/gui/gui.zig Normal file
View File

@ -0,0 +1,234 @@
const gfx = @import("gfx");
const ona = @import("ona");
const std = @import("std");
pub const Box = struct {
left: f32,
top: f32,
width: f32,
height: f32,
};
pub const Canvas = struct {
nodes: ona.stack.Parallel(Node) = .{},
drawable_blocks: ona.stack.Sequential(DrawableBlock) = .{},
drawable_labels: ona.stack.Sequential(DrawableLabel) = .{},
fn Drawable(comptime State: type) type {
return struct {
state: State,
depth: f32,
};
}
const DrawableBlock = Drawable(struct {
background: gfx.Texture = .default,
color: gfx.Color = gfx.colors.white,
});
const DrawableLabel = Drawable(struct {
text: ona.stack.Sequential(u8) = .{},
color: gfx.Color = gfx.colors.black,
font: gfx.Font = .default,
});
const Node = struct {
name: []u8,
box: Box,
parent_index: u32 = unused_index,
block_index: u32 = unused_index,
label_index: u32 = unused_index,
style_index: u32 = unused_index,
const unused_index = std.math.maxInt(u32);
};
pub fn append(self: *Canvas, name: []const u8, box: Box) std.mem.Allocator.Error!Item {
const duped_name = try ona.heap.allocator.dupe(u8, name);
errdefer {
ona.heap.allocator.free(duped_name);
}
const index = self.nodes.len();
try self.nodes.push_grow(.{
.name = duped_name,
.box = box,
});
errdefer {
std.debug.assert(self.nodes.pop());
}
const node_count = self.nodes.len();
try self.drawable_blocks.grow(ona.scalars.sub(node_count, self.drawable_blocks.cap) orelse 0);
try self.drawable_labels.grow(ona.scalars.sub(node_count, self.drawable_labels.cap) orelse 0);
return @enumFromInt(index);
}
fn calculate_depth(self: Canvas, item: Item) f32 {
const parent_indices = self.nodes.values.slice(.parent_index);
var depth: f32 = 0;
var index = @intFromEnum(item);
while (true) : (depth += 1) {
const parent_index = parent_indices[index];
if (parent_index == Node.unused_index) {
break;
}
index = parent_index;
}
return depth;
}
fn deinit(self: *Canvas) void {
self.nodes.deinit();
self.drawable_blocks.deinit();
for (self.drawable_labels.values) |*drawable_label| {
drawable_label.state.text.deinit();
}
self.drawable_labels.deinit();
self.* = undefined;
}
const label_depth_offset = 0.5;
pub fn set_block(self: *Canvas, item: Item, set: SetBlock) void {
const block_index = self.nodes.values.get(.block_index, @intFromEnum(item)).?;
if (block_index.* == Node.unused_index) {
block_index.* = @intCast(self.drawable_blocks.len());
std.debug.assert(self.drawable_blocks.push(.{
.depth = self.calculate_depth(item),
.state = .{},
}));
}
const block = &self.drawable_blocks.values[block_index.*];
if (set.has_background) |background| {
block.state.background = background;
}
if (set.has_color) |color| {
block.state.color = color;
}
}
pub fn set_label(self: *Canvas, item: Item, set: SetLabel) std.mem.Allocator.Error!void {
const label_index = self.nodes.values.get(.label_index, @intFromEnum(item)).?;
if (label_index.* == Node.unused_index) {
label_index.* = @intCast(self.drawable_labels.len());
std.debug.assert(self.drawable_labels.push(.{
.depth = self.calculate_depth(item) + label_depth_offset,
.state = .{},
}));
}
const label = &self.drawable_labels.values[label_index.*];
if (set.has_text) |text| {
label.state.text.pop_all();
try label.state.text.grow(ona.scalars.sub(text.len, label.state.text.cap) orelse 0);
std.debug.assert(label.state.text.push_all(text));
}
if (set.has_font) |font| {
label.state.font = font;
}
if (set.has_color) |color| {
label.state.color = color;
}
}
pub fn set_parent(self: *Canvas, item: Item, has_parent: ?Item) void {
const item_index = @intFromEnum(item);
const parent_index = self.nodes.values.get(.parent_index, item_index).?;
parent_index.* = if (has_parent) |parent| @intFromEnum(parent) else Node.unused_index;
const label_index = self.nodes.values.get(.parent_index, item_index).?.*;
const block_index = self.nodes.values.get(.parent_index, item_index).?.*;
const label_index_exists = label_index != Node.unused_index;
const block_index_exists = block_index != Node.unused_index;
if (label_index_exists or block_index_exists) {
const depth = self.calculate_depth(item);
if (label_index_exists) {
self.drawable_labels[label_index].depth = depth + label_depth_offset;
}
if (block_index_exists) {
self.drawable_labels[label_index].depth = depth;
}
}
}
};
pub const Item = enum (u32) {
_,
};
pub const SetBlock = struct {
has_background: ?gfx.Texture = null,
has_color: ?gfx.Color = null,
};
pub const SetLabel = struct {
has_text: ?[]const u8 = null,
has_color: ?gfx.Color = null,
has_font: ?gfx.Font = null,
};
pub fn exit(canvas: ona.Write(Canvas)) !void {
canvas.state.deinit();
}
pub fn render(commands: gfx.Commands(.overlay), canvas: ona.Read(Canvas)) !void {
const boxes = canvas.state.nodes.values.slice(.box);
for (canvas.state.drawable_blocks.values, boxes) |block, box| {
try commands.draw_texture(.{
.depth = block.depth,
.position = .{box.left, box.top},
.size = .{box.width, box.height},
.texture = block.state.background,
.tint = block.state.color,
});
}
for (canvas.state.drawable_labels.values, boxes) |label, box| {
try commands.draw_font(.{
.depth = label.depth,
.text = label.state.text.values,
.position = .{box.left, box.top},
.size = .{box.width, box.height},
.tint = label.state.color,
.font = label.state.font,
});
}
}
pub fn setup(world: *ona.World) !void {
try world.set_state(Canvas{});
try world.on_event(.render, ona.system_fn(render), .{.label = "render gui"});
try world.on_event(.exit, ona.system_fn(exit), .{.label = "cleanup gui"});
}

View File

@ -293,10 +293,10 @@ test "mapping values" {
} }
} }
pub fn setup(world: *ona.World, events: ona.App.Events) std.mem.Allocator.Error!void { pub fn setup(world: *ona.World) std.mem.Allocator.Error!void {
try world.set_state(Mapping{}); try world.set_state(Mapping{});
try world.on_event(events.pre_update, ona.system_fn(update), .{ try world.on_event(.pre_update, ona.system_fn(update), .{
.label = "update actions", .label = "update actions",
}); });
} }

View File

@ -6,7 +6,7 @@ const std = @import("std");
thread_pool: ?*std.Thread.Pool = null, thread_pool: ?*std.Thread.Pool = null,
thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable, thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable,
event_systems: ona.stack.Sequential(Schedule), event_schedules: ona.map.Hashed([]const u8, *Schedule, ona.map.string_traits),
pub const BindContext = struct { pub const BindContext = struct {
node: ona.dag.Node, node: ona.dag.Node,
@ -110,8 +110,6 @@ const Graph = ona.dag.Graph(struct {
resource_accesses: ona.stack.Sequential(StateAccess), resource_accesses: ona.stack.Sequential(StateAccess),
}); });
pub const Event = enum (usize) { _ };
const ParallelNodeBundles = ona.stack.Sequential(NodeBundle); const ParallelNodeBundles = ona.stack.Sequential(NodeBundle);
const NodeBundle = ona.stack.Sequential(ona.dag.Node); const NodeBundle = ona.stack.Sequential(ona.dag.Node);
@ -442,22 +440,16 @@ const Schedule = struct {
} }
} }
pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule { pub fn init(comptime label: [:0]const u8) std.mem.Allocator.Error!Schedule {
var arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
errdefer arena.deinit();
const duped_label = try arena.allocator().dupeZ(u8, label);
return .{ return .{
.graph = Graph.init(ona.heap.allocator), .graph = Graph.init(ona.heap.allocator),
.label = duped_label, .arena = std.heap.ArenaAllocator.init(ona.heap.allocator),
.arena = arena, .label = label,
.system_id_nodes = .{.allocator = ona.heap.allocator}, .system_id_nodes = .{},
.read_write_resource_id_nodes = .{.allocator = ona.heap.allocator}, .read_write_resource_id_nodes = .{},
.read_only_resource_id_nodes = .{.allocator = ona.heap.allocator}, .read_only_resource_id_nodes = .{},
.parallel_work_bundles = .{.allocator = ona.heap.allocator}, .parallel_work_bundles = .{},
.blocking_work = .{.allocator = ona.heap.allocator}, .blocking_work = .{},
}; };
} }
@ -544,21 +536,12 @@ pub const UnbindContext = struct {
world: *Self, world: *Self,
}; };
pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event {
var systems = try Schedule.init(label);
errdefer systems.deinit(self);
const index = self.event_systems.len();
try self.event_systems.push_grow(systems);
return @enumFromInt(index);
}
pub fn deinit(self: *Self) void { pub fn deinit(self: *Self) void {
for (self.event_systems.values) |*schedule| { var event_schedules = self.event_schedules.entries();
schedule.deinit(self);
while (event_schedules.next()) |schedule| {
schedule.value.deinit(self);
ona.heap.allocator.destroy(schedule.value);
} }
for (&self.thread_restricted_resources) |*resources| { for (&self.thread_restricted_resources) |*resources| {
@ -570,7 +553,7 @@ pub fn deinit(self: *Self) void {
ona.heap.allocator.destroy(thread_pool); ona.heap.allocator.destroy(thread_pool);
} }
self.event_systems.deinit(); self.event_schedules.deinit();
self.* = undefined; self.* = undefined;
} }
@ -594,7 +577,7 @@ pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@Type
pub fn init(thread_count: u32) std.Thread.SpawnError!Self { pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
var world = Self{ var world = Self{
.thread_restricted_resources = .{StateTable.init(), StateTable.init()}, .thread_restricted_resources = .{StateTable.init(), StateTable.init()},
.event_systems = .{.allocator = ona.heap.allocator}, .event_schedules = .{.allocator = ona.heap.allocator},
}; };
if (thread_count != 0 and !builtin.single_threaded) { if (thread_count != 0 and !builtin.single_threaded) {
@ -611,12 +594,34 @@ pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
return world; return world;
} }
pub fn on_event(self: *Self, event: Event, action: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void { pub fn on_event(self: *Self, comptime event: anytype, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
try self.event_systems.values[@intFromEnum(event)].insert(action, order); const event_name = @tagName(event);
if (self.event_schedules.get(event_name)) |schedule| {
try schedule.*.insert(info, order);
} else {
const schedule = try ona.heap.allocator.create(Schedule);
errdefer {
ona.heap.allocator.destroy(schedule);
} }
pub fn run_event(self: *Self, event: Event) anyerror!void { schedule.* = try Schedule.init(event_name);
try self.event_systems.values[@intFromEnum(event)].run(self);
errdefer {
schedule.deinit(self);
}
try schedule.insert(info, order);
std.debug.assert(try self.event_schedules.emplace(event_name, schedule));
}
}
pub fn run_event(self: *Self, comptime event: anytype) anyerror!void {
if (self.event_schedules.get(@tagName(event))) |schedule| {
try schedule.*.run(self);
}
} }
pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void { pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void {

View File

@ -7,7 +7,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(
const max_int = std.math.maxInt(usize); const max_int = std.math.maxInt(usize);
return struct { return struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator = ona.heap.allocator,
entry_map: []?Entry = &.{}, entry_map: []?Entry = &.{},
len: usize = 0, len: usize = 0,

View File

@ -29,37 +29,11 @@ pub const World = @import("./World.zig");
pub const lina = @import("./lina.zig"); pub const lina = @import("./lina.zig");
pub const App = struct { pub const App = struct {
events: *const Events, step: fn (*World) anyerror!void = noop,
target_frame_time: f64,
elapsed_time: f64,
is_running: bool,
pub const Events = struct { fn noop(_: *World) !void {}
load: World.Event,
pre_update: World.Event,
update: World.Event,
post_update: World.Event,
render: World.Event,
finish: World.Event,
exit: World.Event,
};
pub const Setup = struct { pub fn build(self: App) fn () anyerror!void {
pub const Event = enum {
load,
pre_update,
update,
post_update,
render,
finish,
exit,
};
step: fn (*World, Events) anyerror!void = noop,
fn noop(_: *World, _: Events) !void {}
pub fn build(self: Setup) fn () anyerror!void {
const Start = struct { const Start = struct {
fn main() anyerror!void { fn main() anyerror!void {
defer { defer {
@ -100,66 +74,60 @@ pub const App = struct {
defer world.deinit(); defer world.deinit();
const events = App.Events{ try self.step(&world);
.load = try world.create_event("load"),
.pre_update = try world.create_event("pre-update"),
.update = try world.create_event("update"),
.post_update = try world.create_event("post-update"),
.render = try world.create_event("render"),
.finish = try world.create_event("finish"),
.exit = try world.create_event("exit"),
};
const app = try world.set_get_state(App{
.events = &events,
.target_frame_time = 1.0 / 60.0,
.elapsed_time = 0,
.is_running = true,
});
try self.step(&world, events);
try world.run_event(events.load);
const ticks_initial = std.time.milliTimestamp();
var ticks_previous = ticks_initial;
var accumulated_time = @as(f64, 0);
while (app.is_running) {
const ticks_current = std.time.milliTimestamp();
const milliseconds_per_second = 1000.0;
const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second;
app.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second;
ticks_previous = ticks_current;
accumulated_time += delta_time;
try world.run_event(events.pre_update);
while (accumulated_time >= app.target_frame_time) : (accumulated_time -= app.target_frame_time) {
try world.run_event(events.update);
}
try world.run_event(events.post_update);
try world.run_event(events.render);
try world.run_event(events.finish);
}
try world.run_event(events.exit);
} }
}; };
return Start.main; return Start.main;
} }
pub fn with_module(self: Setup, comptime Module: type) Setup { pub const game = App{.step = run_game};
fn run_game(world: *World) !void {
const loop = try world.set_get_state(Loop{
.target_frame_time = 1.0 / 60.0,
.elapsed_time = 0,
.is_running = true,
});
try world.run_event(.load);
const ticks_initial = std.time.milliTimestamp();
var ticks_previous = ticks_initial;
var accumulated_time = @as(f64, 0);
while (loop.is_running) {
const ticks_current = std.time.milliTimestamp();
const milliseconds_per_second = 1000.0;
const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second;
loop.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second;
ticks_previous = ticks_current;
accumulated_time += delta_time;
try world.run_event(.pre_update);
while (accumulated_time >= loop.target_frame_time) : (accumulated_time -= loop.target_frame_time) {
try world.run_event(.update);
}
try world.run_event(.post_update);
try world.run_event(.render);
try world.run_event(.finish);
}
try world.run_event(.exit);
}
pub fn with_module(self: App, comptime Module: type) App {
if (!@hasDecl(Module, "setup")) { if (!@hasDecl(Module, "setup")) {
@compileError("`Module` argument must have a setup fn declaration"); @compileError("`Module` argument must have a setup fn declaration");
} }
const WithState = struct { const WithState = struct {
fn step(world: *World, events: App.Events) !void { fn step(world: *World) !void {
try Module.setup(world, events); try Module.setup(world);
try self.step(world, events); try self.step(world);
} }
}; };
@ -168,11 +136,11 @@ pub const App = struct {
}; };
} }
pub fn with_state(self: Setup, comptime state: anytype) Setup { pub fn with_state(self: App, comptime state: anytype) App {
const WithState = struct { const WithState = struct {
fn step(world: *World, events: App.Events) !void { fn step(world: *World) !void {
try world.set_state(state); try world.set_state(state);
try self.step(world, events); try self.step(world);
} }
}; };
@ -181,21 +149,11 @@ pub const App = struct {
}; };
} }
pub fn with_system(self: Setup, comptime event: Event, comptime info: *const SystemInfo, comptime order: SystemOrder) Setup { pub fn with_system(self: App, comptime event: anytype, comptime info: *const SystemInfo, comptime order: SystemOrder) App {
const WithState = struct { const WithState = struct {
fn step(world: *World, events: App.Events) !void { fn step(world: *World) !void {
const app_event = switch (event) { try world.on_event(event, info, order);
.load => events.load, try self.step(world);
.pre_update => events.pre_update,
.update => events.update,
.post_update => events.post_update,
.render => events.render,
.finish => events.finish,
.exit => events.exit,
};
try world.on_event(app_event, info, order);
try self.step(world, events);
} }
}; };
@ -205,9 +163,12 @@ pub const App = struct {
} }
}; };
pub const setup = Setup{}; pub const Loop = struct {
target_frame_time: f64,
elapsed_time: f64,
is_running: bool,
pub fn quit(self: *App) void { pub fn quit(self: *Loop) void {
self.is_running = false; self.is_running = false;
} }
}; };
@ -355,7 +316,10 @@ pub const LaunchFlag = enum {
pub fn Params(comptime Value: type) type { pub fn Params(comptime Value: type) type {
if (!@hasDecl(Value, "Param")) { if (!@hasDecl(Value, "Param")) {
@compileError("System parameters must have a Params type declaration"); @compileError(@typeName(Value) ++
\\ is missing a public `Params` type declaration.
\\This is required for a type to be used as a system parameter
);
} }
return struct { return struct {
@ -519,15 +483,11 @@ pub fn Receive(comptime Message: type) type {
.channel = (try context.register_readable_state_access(TypedChannel)) orelse set: { .channel = (try context.register_readable_state_access(TypedChannel)) orelse set: {
try context.world.set_state(TypedChannel.init(heap.allocator)); try context.world.set_state(TypedChannel.init(heap.allocator));
const app = context.world.get_state(App) orelse { try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{
@panic("Send system parameters depend on a " ++ @typeName(App) ++ " state to work");
};
try context.world.on_event(app.events.post_update, system_fn(TypedChannel.swap), .{
.label = "swap channel of " ++ @typeName(Message), .label = "swap channel of " ++ @typeName(Message),
}); });
try context.world.on_event(app.events.exit, system_fn(TypedChannel.cleanup), .{ try context.world.on_event(.exit, system_fn(TypedChannel.cleanup), .{
.label = "clean up channel of " ++ @typeName(Message), .label = "clean up channel of " ++ @typeName(Message),
}); });
@ -565,15 +525,11 @@ pub fn Send(comptime Message: type) type {
.channel = (try context.register_writable_state_access(TypedChannel)) orelse set: { .channel = (try context.register_writable_state_access(TypedChannel)) orelse set: {
try context.world.set_state(TypedChannel.init(heap.allocator)); try context.world.set_state(TypedChannel.init(heap.allocator));
const app = context.world.get_state(App) orelse { try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{
@panic("Send system parameters depend on a " ++ @typeName(App) ++ " state to work");
};
try context.world.on_event(app.events.post_update, system_fn(TypedChannel.swap), .{
.label = "swap channel of " ++ @typeName(Message), .label = "swap channel of " ++ @typeName(Message),
}); });
try context.world.on_event(app.events.exit, system_fn(TypedChannel.cleanup), .{ try context.world.on_event(.exit, system_fn(TypedChannel.cleanup), .{
.label = "clean up channel of " ++ @typeName(Message), .label = "clean up channel of " ++ @typeName(Message),
}); });

View File

@ -69,6 +69,10 @@ pub fn Sequential(comptime Value: type) type {
return true; return true;
} }
pub fn pop_all(self: *Self) void {
self.values = self.values[0 .. 0];
}
pub fn pop_many(self: *Self, n: usize) bool { pub fn pop_many(self: *Self, n: usize) bool {
const new_length = ona.scalars.sub(self.values.len, n) orelse { const new_length = ona.scalars.sub(self.values.len, n) orelse {
return false; return false;
@ -247,6 +251,16 @@ pub fn Parallel(comptime Value: type) type {
return self.values.len; return self.values.len;
} }
pub fn pop(self: *Self) bool {
if (self.values.len == 0) {
return false;
}
self.values = self.values.slice_all(0, self.values.len - 1).?;
return true;
}
pub fn pop_many(self: *Self, n: usize) bool { pub fn pop_many(self: *Self, n: usize) bool {
const new_length = ona.scalars.sub(self.values.len, n) orelse { const new_length = ona.scalars.sub(self.values.len, n) orelse {
return false; return false;