Compare commits
5 Commits
bb1d383ccb
...
d1c823df41
Author | SHA1 | Date |
---|---|---|
kayomn | d1c823df41 | |
kayomn | 455ede6d78 | |
kayomn | c2a95726ca | |
kayomn | 4e3263b352 | |
kayomn | 7e8691c050 |
|
@ -5,7 +5,7 @@
|
|||
"name": "Runner",
|
||||
"type": "gdb",
|
||||
"request": "launch",
|
||||
"target": "${workspaceRoot}/demos/effects.out",
|
||||
"target": "${workspaceRoot}/demos/canvas.out",
|
||||
"cwd": "${workspaceRoot}/demos/",
|
||||
"valuesFormatting": "prettyPrinters",
|
||||
"preLaunchTask": "Build All"
|
||||
|
|
14
build.zig
14
build.zig
|
@ -254,6 +254,20 @@ pub fn build(b: *std.Build) !void {
|
|||
|
||||
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_tests(b.step("tests", "Build and run tests"));
|
||||
}
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
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();
|
||||
|
||||
fn load(canvas: ona.Write(gui.Canvas)) !void {
|
||||
const item = try canvas.state.append("", .{
|
||||
.x = 0,
|
||||
.y = 0,
|
||||
.width = 256,
|
||||
.height = 256,
|
||||
});
|
||||
|
||||
try canvas.state.set_block(item, .{
|
||||
.has_color = gfx.colors.purple,
|
||||
});
|
||||
|
||||
try canvas.state.set_label(item, .{
|
||||
.has_text = "Hello, world",
|
||||
});
|
||||
|
||||
const item2 = try canvas.state.append("", .{
|
||||
.x = 128,
|
||||
.y = 128,
|
||||
.width = 256,
|
||||
.height = 256,
|
||||
});
|
||||
|
||||
try canvas.state.set_block(item2, .{
|
||||
.has_color = .{0, 1, 0, 1},
|
||||
});
|
||||
|
||||
canvas.state.set_parent(item2, item);
|
||||
}
|
||||
|
|
@ -56,23 +56,23 @@ fn load(display: ona.Write(gfx.Display), effects: ona.Write(Effects), assets: on
|
|||
};
|
||||
}
|
||||
|
||||
pub const main = ona.App.setup.
|
||||
with_module(gfx).
|
||||
with_state(Effects{}).
|
||||
with_system(.load, ona.system_fn(load), .{.label = "load effects"}).
|
||||
with_system(.update, ona.system_fn(update), .{.label = "update effects"}).
|
||||
with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build();
|
||||
pub const main = ona.App.game
|
||||
.with_module(gfx)
|
||||
.with_state(Effects{})
|
||||
.with_system(.load, ona.system_fn(load), .{.label = "load effects"})
|
||||
.with_system(.update, ona.system_fn(update), .{.label = "update effects"})
|
||||
.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;
|
||||
|
||||
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.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, effects: ona.Write(Effects), loop: ona.Read(ona.Loop), display: ona.Write(gfx.Display)) !void {
|
||||
try commands.set_target(.{
|
||||
.texture = effects.state.render_texture,
|
||||
.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,
|
||||
});
|
||||
|
||||
const display_width: f32 = @floatFromInt(display.state.width);
|
||||
const display_height: f32 = @floatFromInt(display.state.height);
|
||||
|
||||
const display_transform = gfx.transform_2d(.{
|
||||
.translation = .{display_width / 2, display_height / 2},
|
||||
});
|
||||
const width: f32 = @floatFromInt(display.state.width);
|
||||
const height: f32 = @floatFromInt(display.state.height);
|
||||
|
||||
try commands.draw_texture(.{
|
||||
.texture = effects.state.image_textures[effects.state.image_index],
|
||||
.transform = display_transform,
|
||||
.resolution = .{display.state.width, display.state.height},
|
||||
.size = .{width, height},
|
||||
});
|
||||
|
||||
try commands.set_effect(.{
|
||||
.effect = effects.state.crt_effect,
|
||||
|
||||
.properties = std.mem.asBytes(&CRT{
|
||||
.width = display_width,
|
||||
.height = display_height,
|
||||
.time = @floatCast(app.state.elapsed_time),
|
||||
.width = width,
|
||||
.height = height,
|
||||
.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(.{
|
||||
.texture = effects.state.render_texture,
|
||||
.transform = display_transform,
|
||||
.size = .{width, height},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
123
demos/inputs.zig
123
demos/inputs.zig
|
@ -6,80 +6,68 @@ const ona = @import("ona");
|
|||
|
||||
const std = @import("std");
|
||||
|
||||
const Spawned = struct {
|
||||
visual: struct {
|
||||
color: gfx.Color,
|
||||
transform: gfx.Transform2D,
|
||||
},
|
||||
|
||||
lifetime_seconds: f32,
|
||||
};
|
||||
|
||||
const Visuals = struct {
|
||||
spawned: ona.stack.Parallel(Spawned) = .{},
|
||||
spawned: SpawnMap = SpawnMap.init(ona.heap.allocator),
|
||||
random: std.Random.Xoroshiro128,
|
||||
mouse_position: @Vector(2, f32) = @splat(0),
|
||||
|
||||
const SpawnMap = ona.SlotMap(struct {
|
||||
color: gfx.Color,
|
||||
position: gfx.Vector2,
|
||||
rotation: f32,
|
||||
lifetime_seconds: f32,
|
||||
});
|
||||
};
|
||||
|
||||
fn cleanup(visuals: ona.Write(Visuals)) !void {
|
||||
visuals.state.spawned.deinit();
|
||||
visuals.state.spawned.deinit();
|
||||
}
|
||||
|
||||
fn load(visuals: ona.Write(Visuals)) !void {
|
||||
const initial_spawn_capacity = 1024;
|
||||
|
||||
try visuals.state.spawned.grow(initial_spawn_capacity);
|
||||
}
|
||||
|
||||
pub const main = ona.App.setup.
|
||||
with_module(gfx).
|
||||
with_module(hid).
|
||||
with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)}).
|
||||
with_system(.load, ona.system_fn(load), .{.label = "load visuals"}).
|
||||
with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"}).
|
||||
with_system(.render, ona.system_fn(render), .{.label = "render visuals"}).
|
||||
with_system(.exit, ona.system_fn(cleanup), .{.label = "clean up visuals"}).build();
|
||||
pub const main = ona.App.game
|
||||
.with_module(gfx)
|
||||
.with_module(hid)
|
||||
.with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)})
|
||||
.with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"})
|
||||
.with_system(.render, ona.system_fn(render), .{.label = "render visuals"})
|
||||
.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 {
|
||||
update_spawned(&visuals.state.spawned);
|
||||
const float_speed = 6;
|
||||
|
||||
for (0 .. visuals.state.spawned.next) |i| {
|
||||
const spawned = visuals.state.spawned.get(i) orelse {
|
||||
continue;
|
||||
};
|
||||
|
||||
spawned.lifetime_seconds -= 1.0 / 60.0;
|
||||
spawned.position -= .{0, float_speed};
|
||||
|
||||
if (spawned.lifetime_seconds <= 0) {
|
||||
std.debug.assert(visuals.state.spawned.remove(i) != null);
|
||||
}
|
||||
}
|
||||
|
||||
const random = visuals.state.random.random();
|
||||
const width: f32 = @floatFromInt(display.state.width);
|
||||
const height: f32 = @floatFromInt(display.state.height);
|
||||
const icon_scale = .{8, 8};
|
||||
|
||||
for (events.messages()) |event| {
|
||||
switch (event) {
|
||||
.key_down => {
|
||||
try visuals.state.spawned.push_grow(.{
|
||||
_ = try visuals.state.spawned.insert(.{
|
||||
.lifetime_seconds = 2.5 + (5 * random.float(f32)),
|
||||
|
||||
.visual = .{
|
||||
.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)},
|
||||
|
||||
.transform = gfx.transform_2d(.{
|
||||
.translation = .{width * random.float(f32), height},
|
||||
.rotation = std.math.pi * random.float(f32),
|
||||
.scale = icon_scale,
|
||||
}),
|
||||
},
|
||||
.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)},
|
||||
.position = .{width * random.float(f32), height},
|
||||
.rotation = std.math.pi * random.float(f32),
|
||||
});
|
||||
},
|
||||
|
||||
.mouse_down => {
|
||||
try visuals.state.spawned.push_grow(.{
|
||||
_ = try visuals.state.spawned.insert(.{
|
||||
.lifetime_seconds = 2.5 + (5 * random.float(f32)),
|
||||
|
||||
.visual = .{
|
||||
.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)},
|
||||
|
||||
.transform = gfx.transform_2d(.{
|
||||
.translation = visuals.state.mouse_position,
|
||||
.rotation = std.math.pi * random.float(f32),
|
||||
.scale = icon_scale,
|
||||
}),
|
||||
},
|
||||
.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)},
|
||||
.position = visuals.state.mouse_position,
|
||||
.rotation = std.math.pi * random.float(f32),
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -92,38 +80,13 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display:
|
|||
}
|
||||
}
|
||||
|
||||
fn update_spawned(spawned: *ona.stack.Parallel(Spawned)) void {
|
||||
const float_speed = 6;
|
||||
|
||||
for (spawned.values.slice(.visual)) |*visual| {
|
||||
visual.transform = visual.transform.translated(.{0, -float_speed});
|
||||
}
|
||||
|
||||
{
|
||||
var range = spawned.len();
|
||||
var index: usize = 0;
|
||||
|
||||
while (index < range) : (index += 1) {
|
||||
const lifetime_seconds = spawned.values.get(.lifetime_seconds, index).?;
|
||||
|
||||
lifetime_seconds.* -= 1.0 / 60.0;
|
||||
|
||||
if (lifetime_seconds.* <= 0) {
|
||||
range -= 1;
|
||||
|
||||
std.mem.swap(f32, lifetime_seconds, spawned.values.get(.lifetime_seconds, range).?);
|
||||
}
|
||||
}
|
||||
|
||||
std.debug.assert(spawned.pop_many(spawned.len() - range));
|
||||
}
|
||||
}
|
||||
|
||||
fn render(visuals: ona.Write(Visuals), commands: gfx.Commands) !void {
|
||||
for (visuals.state.spawned.values.slice(.visual)) |visual| {
|
||||
try commands.draw_rect(.{
|
||||
.transform = visual.transform,
|
||||
.color = visual.color,
|
||||
for (visuals.state.spawned.values()) |visual| {
|
||||
try commands.draw_texture(.{
|
||||
.anchor = @splat(0.5),
|
||||
.position = visual.position,
|
||||
.tint = visual.color,
|
||||
.size = @as(gfx.Vector2, @splat(64)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ pub const Effect = struct {
|
|||
},
|
||||
|
||||
.vertex_source = .{
|
||||
.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")),
|
||||
.ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.vert.spv")),
|
||||
},
|
||||
|
||||
.fragment_source = .{
|
||||
|
@ -197,12 +197,12 @@ pub const Effect = struct {
|
|||
};
|
||||
|
||||
attrs[2] = .{
|
||||
.format = .FLOAT2,
|
||||
.format = .FLOAT,
|
||||
.buffer_index = 1,
|
||||
};
|
||||
|
||||
attrs[3] = .{
|
||||
.format = .FLOAT2,
|
||||
.format = .FLOAT,
|
||||
.buffer_index = 1,
|
||||
};
|
||||
|
||||
|
@ -212,12 +212,12 @@ pub const Effect = struct {
|
|||
};
|
||||
|
||||
attrs[5] = .{
|
||||
.format = .UBYTE4N,
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = 1,
|
||||
};
|
||||
|
||||
attrs[6] = .{
|
||||
.format = .FLOAT,
|
||||
.format = .FLOAT2,
|
||||
.buffer_index = 1,
|
||||
};
|
||||
|
||||
|
@ -226,6 +226,11 @@ pub const Effect = struct {
|
|||
.buffer_index = 1,
|
||||
};
|
||||
|
||||
attrs[8] = .{
|
||||
.format = .FLOAT4,
|
||||
.buffer_index = 1,
|
||||
};
|
||||
|
||||
break: get attrs;
|
||||
},
|
||||
|
||||
|
@ -466,7 +471,7 @@ pub fn init() !Self {
|
|||
}
|
||||
|
||||
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(.{
|
||||
|
|
276
src/gfx/gfx.zig
276
src/gfx/gfx.zig
|
@ -27,40 +27,7 @@ pub const Assets = struct {
|
|||
load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc,
|
||||
};
|
||||
|
||||
pub const WorkQueue = ona.asyncio.BlockingQueue(1024, union (enum) {
|
||||
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,
|
||||
};
|
||||
});
|
||||
pub const WorkQueue = ona.asyncio.BlockingQueue(1024, Work);
|
||||
|
||||
fn deinit(self: *Assets) void {
|
||||
self.pending_work.enqueue(.shutdown);
|
||||
|
@ -72,12 +39,10 @@ pub const Assets = struct {
|
|||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn init() !Assets {
|
||||
fn init(width: u16, height: u16) !Assets {
|
||||
const window = create: {
|
||||
const position = ext.SDL_WINDOWPOS_CENTERED;
|
||||
const flags = ext.SDL_WINDOW_OPENGL;
|
||||
const width = 640;
|
||||
const height = 480;
|
||||
|
||||
break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
|
||||
return error.Unsupported;
|
||||
|
@ -138,42 +103,60 @@ pub const Assets = struct {
|
|||
}
|
||||
|
||||
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 Commands = struct {
|
||||
pending: *List,
|
||||
pub const Command = union (enum) {
|
||||
draw_font: DrawFont,
|
||||
draw_texture: DrawTexture,
|
||||
set_effect: SetEffect,
|
||||
set_scissor_rect: ?Rect,
|
||||
set_target: SetTarget,
|
||||
|
||||
pub const Command = union (enum) {
|
||||
draw_rect: DrawRectCommand,
|
||||
draw_texture: DrawTextureCommand,
|
||||
set_effect: SetEffectCommand,
|
||||
set_target: SetTargetCommand,
|
||||
pub const DrawFont = struct {
|
||||
position: Vector2 = @splat(0),
|
||||
size: Vector2,
|
||||
text: []const u8,
|
||||
font: Font,
|
||||
tint: Color,
|
||||
depth: f32,
|
||||
};
|
||||
|
||||
pub const DrawRectCommand = struct {
|
||||
color: Color,
|
||||
transform: Transform2D,
|
||||
pub const DrawTexture = struct {
|
||||
anchor: Vector2 = @splat(0),
|
||||
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 {
|
||||
texture: Texture,
|
||||
transform: Transform2D,
|
||||
resolution: ?@Vector(2, u16) = null,
|
||||
};
|
||||
|
||||
pub const SetEffectCommand = struct {
|
||||
pub const SetEffect = struct {
|
||||
effect: Effect,
|
||||
properties: []const u8,
|
||||
};
|
||||
|
||||
pub const SetTargetCommand = struct {
|
||||
pub const SetTarget = struct {
|
||||
texture: ?Texture = null,
|
||||
clear_color: ?Color,
|
||||
clear_depth: ?f32,
|
||||
clear_stencil: ?u8,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Commands = struct {
|
||||
pending: *List,
|
||||
|
||||
pub const List = struct {
|
||||
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 {
|
||||
return .{
|
||||
.swap_lists = .{
|
||||
|
@ -240,21 +225,30 @@ pub const Commands = struct {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn init(param: *Param) Commands {
|
||||
pub fn init(param: *Param) Self {
|
||||
return .{
|
||||
.pending = param.pending_list(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn draw_rect(self: Commands, command: DrawRectCommand) std.mem.Allocator.Error!void {
|
||||
try self.pending.stack.push_grow(.{.draw_rect = command});
|
||||
pub fn draw_font(self: Self, command: Command.DrawFont) std.mem.Allocator.Error!void {
|
||||
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});
|
||||
}
|
||||
|
||||
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(.{
|
||||
.set_effect = .{
|
||||
.properties = try self.pending.arena.allocator().dupe(u8, command.properties),
|
||||
|
@ -263,13 +257,17 @@ pub const Commands = struct {
|
|||
});
|
||||
}
|
||||
|
||||
pub fn unbind(param: *Param, _: ona.World.UnbindContext) void {
|
||||
param.deinit();
|
||||
pub fn set_scissor_rect(self: Self, has_rect: Rect) std.mem.Allocator.Error!void {
|
||||
try self.pending.stack.push_grow(.{.set_scissor_rect = has_rect});
|
||||
}
|
||||
|
||||
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});
|
||||
}
|
||||
|
||||
pub fn unbind(param: *Param, _: ona.World.UnbindContext) void {
|
||||
param.deinit();
|
||||
}
|
||||
};
|
||||
|
||||
pub const Descs = struct {
|
||||
|
@ -431,11 +429,37 @@ pub const Effect = enum (u32) {
|
|||
};
|
||||
};
|
||||
|
||||
pub const Rect = struct {
|
||||
pub const Font = enum (u32) {
|
||||
default,
|
||||
_,
|
||||
|
||||
pub const Desc = struct {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
pub const Rect = extern struct {
|
||||
left: f32,
|
||||
top: f32,
|
||||
right: f32,
|
||||
bottom: f32,
|
||||
|
||||
pub fn area(self: Rect) f32 {
|
||||
const width, const height = self.size();
|
||||
|
||||
return width * height;
|
||||
}
|
||||
|
||||
pub fn size(self: Rect) Vector2 {
|
||||
return .{self.right - self.left, self.bottom - self.top};
|
||||
}
|
||||
|
||||
pub const zero = Rect{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = 0,
|
||||
.bottom = 0,
|
||||
};
|
||||
};
|
||||
|
||||
pub const Texture = enum (u32) {
|
||||
|
@ -474,58 +498,41 @@ pub const Texture = enum (u32) {
|
|||
};
|
||||
};
|
||||
|
||||
pub const Transform2D = extern struct {
|
||||
xbasis: Vector = .{1, 0},
|
||||
ybasis: Vector = .{0, 1},
|
||||
origin: Vector = @splat(0),
|
||||
pub const Vector2 = @Vector(2, f32);
|
||||
|
||||
pub const Simplified = struct {
|
||||
translation: Vector = @splat(0),
|
||||
rotation: f32 = 0,
|
||||
scale: Vector = @splat(1),
|
||||
skew: f32 = 0,
|
||||
const Work = union (enum) {
|
||||
load_effect: LoadEffect,
|
||||
load_texture: LoadTexture,
|
||||
render_frame: RenderFrame,
|
||||
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,
|
||||
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture),
|
||||
};
|
||||
|
||||
pub fn scaled(self: Transform2D, scale: Vector) Transform2D {
|
||||
var transform = self;
|
||||
const scale_x, const scale_y = scale;
|
||||
const RenderFrame = struct {
|
||||
clear_color: Color,
|
||||
width: u16,
|
||||
height: u16,
|
||||
finished: *std.Thread.ResetEvent,
|
||||
has_commands: ?*ona.Params(Commands).Node,
|
||||
};
|
||||
|
||||
transform.xbasis *= @splat(scale_x);
|
||||
transform.ybasis *= @splat(scale_y);
|
||||
const UnloadEffect = struct {
|
||||
handle: Effect,
|
||||
};
|
||||
|
||||
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 {
|
||||
var transform = self;
|
||||
|
||||
transform.origin += translation;
|
||||
|
||||
return transform;
|
||||
}
|
||||
const UnloadTexture = struct {
|
||||
handle: Texture,
|
||||
};
|
||||
};
|
||||
|
||||
pub const colors = struct {
|
||||
|
@ -546,6 +553,8 @@ pub const colors = struct {
|
|||
|
||||
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 {
|
||||
return .{r, g, b, 1};
|
||||
}
|
||||
|
@ -553,13 +562,13 @@ pub const colors = struct {
|
|||
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);
|
||||
|
||||
while (ext.SDL_PollEvent(&event) != 0) {
|
||||
switch (event.type) {
|
||||
ext.SDL_QUIT => {
|
||||
app.state.quit();
|
||||
loop.state.quit();
|
||||
},
|
||||
|
||||
ext.SDL_KEYUP => {
|
||||
|
@ -606,13 +615,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) {
|
||||
return error.Unsupported;
|
||||
}
|
||||
|
||||
const display = try world.set_get_state(Display{});
|
||||
|
||||
const assets = create: {
|
||||
var assets = try Assets.init();
|
||||
var assets = try Assets.init(display.width, display.height);
|
||||
|
||||
errdefer {
|
||||
assets.deinit();
|
||||
|
@ -632,10 +643,9 @@ pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} ||
|
|||
assets.window,
|
||||
});
|
||||
|
||||
try world.set_state(Display{});
|
||||
try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll gfx"});
|
||||
try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop gfx"});
|
||||
try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"});
|
||||
try world.on_event(.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(.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"});
|
||||
}
|
||||
|
||||
pub fn stop(assets: ona.Write(Assets)) void {
|
||||
|
@ -648,12 +658,10 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void {
|
|||
assets.frame_rendered.wait();
|
||||
assets.frame_rendered.reset();
|
||||
|
||||
{
|
||||
var has_command_param = exclusive.world.get_params(Commands).has_head;
|
||||
var has_command_param = exclusive.world.get_params(Commands).has_head;
|
||||
|
||||
while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
|
||||
command_param.param.rotate();
|
||||
}
|
||||
while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
|
||||
command_param.param.rotate();
|
||||
}
|
||||
|
||||
var display_width, var display_height = [_]c_int{0, 0};
|
||||
|
@ -664,10 +672,10 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void {
|
|||
ext.SDL_SetWindowSize(assets.window, display.width, display.height);
|
||||
}
|
||||
|
||||
if (exclusive.world.get_params(Commands).has_head) |command_param| {
|
||||
if (exclusive.world.get_params(Commands).has_head) |has_commands| {
|
||||
assets.pending_work.enqueue(.{
|
||||
.render_frame = .{
|
||||
.has_command_params = command_param,
|
||||
.has_commands = has_commands,
|
||||
.width = display.width,
|
||||
.height = display.height,
|
||||
.clear_color = display.clear_color,
|
||||
|
@ -678,21 +686,3 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void {
|
|||
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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,15 +23,23 @@ const Frame = struct {
|
|||
current_source_texture: gfx.Texture = .default,
|
||||
current_target_texture: ?gfx.Texture = null,
|
||||
current_effect: gfx.Effect = .default,
|
||||
transform_matrix: Matrix = identity_matrix,
|
||||
width: u16 = 0,
|
||||
height: u16 = 0,
|
||||
|
||||
const DrawTexture = extern struct {
|
||||
transform: gfx.Transform2D,
|
||||
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
|
||||
depth: f32 = 0,
|
||||
texture_offset: @Vector(2, f32) = @splat(0),
|
||||
texture_size: @Vector(2, f32) = @splat(1),
|
||||
rotation: f32,
|
||||
depth: f32,
|
||||
position: gfx.Vector2,
|
||||
size: gfx.Vector2,
|
||||
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;
|
||||
|
@ -75,35 +83,13 @@ const Frame = struct {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn draw_rect(self: *Frame, resources: *Resources, command: gfx.Commands.DrawRectCommand) !void {
|
||||
if (self.current_source_texture != .default) {
|
||||
self.flush(resources);
|
||||
}
|
||||
|
||||
self.current_source_texture = .default;
|
||||
|
||||
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_font(self: *Frame, resources: *Resources, command: gfx.Command.DrawFont) !void {
|
||||
_ = self;
|
||||
_ = resources;
|
||||
_ = command;
|
||||
}
|
||||
|
||||
pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Commands.DrawTextureCommand) !void {
|
||||
pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Command.DrawTexture) !void {
|
||||
if (self.current_source_texture != command.texture) {
|
||||
self.flush(resources);
|
||||
}
|
||||
|
@ -127,10 +113,13 @@ const Frame = struct {
|
|||
const texture = resources.get_texture(command.texture).?;
|
||||
|
||||
_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{
|
||||
.transform = command.transform.scaled(@floatFromInt(command.resolution orelse .{
|
||||
texture.width,
|
||||
texture.height,
|
||||
})),
|
||||
.size = command.size orelse .{@floatFromInt(texture.width), @floatFromInt(texture.height)},
|
||||
.anchor = command.anchor,
|
||||
.rotation = command.rotation,
|
||||
.depth = command.depth,
|
||||
.source_clip = command.source,
|
||||
.tint = command.tint,
|
||||
.position = command.position,
|
||||
}));
|
||||
|
||||
self.drawn_count += 1;
|
||||
|
@ -178,19 +167,27 @@ const Frame = struct {
|
|||
if (self.current_target_texture) |target_texture| {
|
||||
const texture = resources.get_texture(target_texture).?;
|
||||
|
||||
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = @floatFromInt(texture.width),
|
||||
.bottom = @floatFromInt(texture.height),
|
||||
})));
|
||||
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{
|
||||
.projection_matrix = orthographic_projection(-1.0, 1.0, .{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = @floatFromInt(texture.width),
|
||||
.bottom = @floatFromInt(texture.height),
|
||||
}),
|
||||
|
||||
.transform_matrix = self.transform_matrix,
|
||||
}));
|
||||
} else {
|
||||
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = @floatFromInt(self.width),
|
||||
.bottom = @floatFromInt(self.height),
|
||||
})));
|
||||
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{
|
||||
.projection_matrix = orthographic_projection(-1.0, 1.0, .{
|
||||
.left = 0,
|
||||
.top = 0,
|
||||
.right = @floatFromInt(self.width),
|
||||
.bottom = @floatFromInt(self.height),
|
||||
}),
|
||||
|
||||
.transform_matrix = self.transform_matrix,
|
||||
}));
|
||||
}
|
||||
|
||||
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) {
|
||||
self.flush(resources);
|
||||
}
|
||||
|
@ -227,7 +224,25 @@ const Frame = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Commands.SetTargetCommand) void {
|
||||
pub fn set_scissor_rect(self: *Frame, resources: *Resources, has_rect: ?gfx.Rect) void {
|
||||
self.flush(resources);
|
||||
|
||||
if (has_rect) |rect| {
|
||||
const width, const height = rect.size();
|
||||
|
||||
sokol.gfx.applyScissorRect(
|
||||
@intFromFloat(rect.left),
|
||||
@intFromFloat(rect.top),
|
||||
@intFromFloat(width),
|
||||
@intFromFloat(height),
|
||||
true,
|
||||
);
|
||||
} else {
|
||||
sokol.gfx.applyScissorRect(0, 0, self.width, self.height, true);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Command.SetTarget) void {
|
||||
sokol.gfx.endPass();
|
||||
|
||||
var pass = sokol.gfx.Pass{
|
||||
|
@ -305,18 +320,23 @@ const Frame = struct {
|
|||
}
|
||||
};
|
||||
|
||||
fn Matrix(comptime n: usize, comptime Element: type) type {
|
||||
return [n]@Vector(n, Element);
|
||||
}
|
||||
const Matrix = [4]@Vector(4, f32);
|
||||
|
||||
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 = .{
|
||||
.mesh = 0,
|
||||
.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 height = viewport.bottom - viewport.top;
|
||||
|
||||
|
@ -397,17 +417,18 @@ 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);
|
||||
|
||||
var has_command_params = render_frame.has_command_params;
|
||||
var has_commands = render_frame.has_commands;
|
||||
|
||||
while (has_command_params) |command_params| : (has_command_params = command_params.has_next) {
|
||||
for (command_params.param.submitted_commands()) |command| {
|
||||
while (has_commands) |commands| : (has_commands = commands.has_next) {
|
||||
for (commands.param.submitted_commands()) |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),
|
||||
.set_effect => |set_effect| frame.set_effect(&resources, set_effect),
|
||||
.set_scissor_rect => |has_rect| frame.set_scissor_rect(&resources, has_rect),
|
||||
.set_target => |set_target| frame.set_target(&resources, set_target),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -232,9 +232,7 @@ pub const Stage = struct {
|
|||
else => error.UnsupportedSPIRV,
|
||||
},
|
||||
|
||||
.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse {
|
||||
return error.UnsupportedSPIRV;
|
||||
},
|
||||
.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse 0,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,234 @@
|
|||
const gfx = @import("gfx");
|
||||
|
||||
const ona = @import("ona");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const Box = struct {
|
||||
x: f32,
|
||||
y: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
|
||||
pub fn rect(self: Box) gfx.Rect {
|
||||
return .{
|
||||
.left = self.x,
|
||||
.top = self.y,
|
||||
.right = self.x + self.width,
|
||||
.bottom = self.y + self.height,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Canvas = struct {
|
||||
nodes: NodeMap = NodeMap.init(ona.heap.allocator),
|
||||
drawable_blocks: DrawableBlockMap = DrawableBlockMap.init(ona.heap.allocator),
|
||||
drawable_labels: DrawableLabelMap = DrawableLabelMap.init(ona.heap.allocator),
|
||||
|
||||
fn Drawable(comptime State: type) type {
|
||||
return struct {
|
||||
clip: gfx.Rect,
|
||||
box: Box,
|
||||
depth: f32,
|
||||
state: State,
|
||||
};
|
||||
}
|
||||
|
||||
const DrawableBlockMap = ona.SlotMap(Drawable(struct {
|
||||
background: gfx.Texture = .default,
|
||||
color: gfx.Color = gfx.colors.white,
|
||||
}));
|
||||
|
||||
const DrawableLabelMap = ona.SlotMap(Drawable(struct {
|
||||
text: ona.stack.Sequential(u8) = .{},
|
||||
color: gfx.Color = gfx.colors.black,
|
||||
font: gfx.Font = .default,
|
||||
}));
|
||||
|
||||
const NodeMap = ona.SlotMap(struct {
|
||||
name: []u8,
|
||||
box: Box,
|
||||
parent_index: u32 = unused_index,
|
||||
block_index: u32 = unused_index,
|
||||
label_index: u32 = unused_index,
|
||||
|
||||
const Self = @This();
|
||||
|
||||
fn clip(self: Self, nodes: NodeMap) gfx.Rect {
|
||||
if (self.parent_index == unused_index) {
|
||||
return self.box.rect();
|
||||
}
|
||||
|
||||
return nodes.get(self.parent_index).?.box.rect();
|
||||
}
|
||||
|
||||
fn depth(self: Self, nodes: NodeMap) f32 {
|
||||
var parent_index = self.parent_index;
|
||||
var value: f32 = 0;
|
||||
|
||||
while (parent_index != unused_index) : (parent_index = nodes.get(parent_index).?.parent_index) {
|
||||
value += 1;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
return @enumFromInt(try self.nodes.insert(.{
|
||||
.name = duped_name,
|
||||
.box = box,
|
||||
}));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
pub fn set_block(self: *Canvas, item: Item, set: SetBlock) std.mem.Allocator.Error!void {
|
||||
const node = self.nodes.get(@intFromEnum(item)).?;
|
||||
|
||||
if (node.block_index == unused_index) {
|
||||
node.block_index = @intCast(try self.drawable_blocks.insert(.{
|
||||
.clip = node.clip(self.nodes),
|
||||
.depth = node.depth(self.nodes),
|
||||
.box = node.box,
|
||||
.state = .{},
|
||||
}));
|
||||
}
|
||||
|
||||
const block = self.drawable_blocks.get(node.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 node = self.nodes.get(@intFromEnum(item)).?;
|
||||
|
||||
if (node.label_index == unused_index) {
|
||||
node.label_index = @intCast(try self.drawable_labels.insert(.{
|
||||
.clip = node.clip(self.nodes),
|
||||
.depth = node.depth(self.nodes),
|
||||
.box = node.box,
|
||||
.state = .{},
|
||||
}));
|
||||
}
|
||||
|
||||
const label = self.drawable_labels.get(node.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 node = self.nodes.get(@intFromEnum(item)).?;
|
||||
|
||||
node.parent_index = if (has_parent) |parent| @intFromEnum(parent) else unused_index;
|
||||
|
||||
const depth = node.depth(self.nodes);
|
||||
const clip = node.clip(self.nodes);
|
||||
|
||||
if (node.block_index != unused_index) {
|
||||
const block = self.drawable_blocks.get(node.block_index).?;
|
||||
|
||||
block.depth = depth;
|
||||
block.clip = clip;
|
||||
}
|
||||
|
||||
if (node.label_index != unused_index) {
|
||||
const label = self.drawable_labels.get(node.label_index).?;
|
||||
|
||||
label.depth = depth;
|
||||
label.clip = clip;
|
||||
}
|
||||
}
|
||||
|
||||
const unused_index = std.math.maxInt(u32);
|
||||
};
|
||||
|
||||
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, canvas: ona.Read(Canvas)) !void {
|
||||
// TODO: Investigate if scissor rects are necessary for clipping content that overflows.
|
||||
for (canvas.state.drawable_blocks.values()) |block| {
|
||||
try commands.set_scissor_rect(block.clip);
|
||||
|
||||
try commands.draw_texture(.{
|
||||
.size = .{block.box.width, block.box.height},
|
||||
.position = .{block.box.x, block.box.y},
|
||||
.depth = block.depth,
|
||||
.texture = block.state.background,
|
||||
.tint = block.state.color,
|
||||
});
|
||||
}
|
||||
|
||||
for (canvas.state.drawable_labels.values()) |label| {
|
||||
try commands.set_scissor_rect(label.clip);
|
||||
|
||||
try commands.draw_font(.{
|
||||
.size = .{label.box.width, label.box.height},
|
||||
.depth = label.depth,
|
||||
.text = label.state.text.values,
|
||||
.position = .{label.box.x, label.box.y},
|
||||
.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"});
|
||||
}
|
|
@ -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.on_event(events.pre_update, ona.system_fn(update), .{
|
||||
try world.on_event(.pre_update, ona.system_fn(update), .{
|
||||
.label = "update actions",
|
||||
});
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ const std = @import("std");
|
|||
|
||||
thread_pool: ?*std.Thread.Pool = null,
|
||||
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 {
|
||||
node: ona.dag.Node,
|
||||
|
@ -110,8 +110,6 @@ const Graph = ona.dag.Graph(struct {
|
|||
resource_accesses: ona.stack.Sequential(StateAccess),
|
||||
});
|
||||
|
||||
pub const Event = enum (usize) { _ };
|
||||
|
||||
const ParallelNodeBundles = ona.stack.Sequential(NodeBundle);
|
||||
|
||||
const NodeBundle = ona.stack.Sequential(ona.dag.Node);
|
||||
|
@ -367,7 +365,7 @@ const Schedule = struct {
|
|||
|
||||
errdefer {
|
||||
bundle.deinit();
|
||||
std.debug.assert(schedule.parallel_work_bundles.pop());
|
||||
std.debug.assert(schedule.parallel_work_bundles.pop() != null);
|
||||
}
|
||||
|
||||
try populate_bundle(bundle, &schedule.graph, node);
|
||||
|
@ -442,22 +440,16 @@ const Schedule = struct {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn init(label: []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);
|
||||
|
||||
pub fn init(comptime label: [:0]const u8) std.mem.Allocator.Error!Schedule {
|
||||
return .{
|
||||
.graph = Graph.init(ona.heap.allocator),
|
||||
.label = duped_label,
|
||||
.arena = arena,
|
||||
.system_id_nodes = .{.allocator = ona.heap.allocator},
|
||||
.read_write_resource_id_nodes = .{.allocator = ona.heap.allocator},
|
||||
.read_only_resource_id_nodes = .{.allocator = ona.heap.allocator},
|
||||
.parallel_work_bundles = .{.allocator = ona.heap.allocator},
|
||||
.blocking_work = .{.allocator = ona.heap.allocator},
|
||||
.arena = std.heap.ArenaAllocator.init(ona.heap.allocator),
|
||||
.label = label,
|
||||
.system_id_nodes = .{},
|
||||
.read_write_resource_id_nodes = .{},
|
||||
.read_only_resource_id_nodes = .{},
|
||||
.parallel_work_bundles = .{},
|
||||
.blocking_work = .{},
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -544,21 +536,12 @@ pub const UnbindContext = struct {
|
|||
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 {
|
||||
for (self.event_systems.values) |*schedule| {
|
||||
schedule.deinit(self);
|
||||
var event_schedules = self.event_schedules.entries();
|
||||
|
||||
while (event_schedules.next()) |schedule| {
|
||||
schedule.value.deinit(self);
|
||||
ona.heap.allocator.destroy(schedule.value);
|
||||
}
|
||||
|
||||
for (&self.thread_restricted_resources) |*resources| {
|
||||
|
@ -570,7 +553,7 @@ pub fn deinit(self: *Self) void {
|
|||
ona.heap.allocator.destroy(thread_pool);
|
||||
}
|
||||
|
||||
self.event_systems.deinit();
|
||||
self.event_schedules.deinit();
|
||||
|
||||
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 {
|
||||
var world = Self{
|
||||
.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) {
|
||||
|
@ -611,12 +594,34 @@ pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
|
|||
return world;
|
||||
}
|
||||
|
||||
pub fn on_event(self: *Self, event: Event, action: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
|
||||
try self.event_systems.values[@intFromEnum(event)].insert(action, order);
|
||||
pub fn on_event(self: *Self, comptime event: anytype, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
|
||||
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);
|
||||
}
|
||||
|
||||
schedule.* = try Schedule.init(event_name);
|
||||
|
||||
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, event: Event) anyerror!void {
|
||||
try self.event_systems.values[@intFromEnum(event)].run(self);
|
||||
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 {
|
||||
|
|
|
@ -30,13 +30,13 @@ pub fn Graph(comptime Payload: type) type {
|
|||
}
|
||||
|
||||
pub fn clear_edges(self: *Self) void {
|
||||
for (self.table.values.slice(.edges)) |*edges| {
|
||||
for (self.table.values.get(.edges)) |*edges| {
|
||||
edges.clear();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
for (self.table.values.slice(.edges)) |*edges| {
|
||||
for (self.table.values.get(.edges)) |*edges| {
|
||||
edges.deinit();
|
||||
}
|
||||
|
||||
|
@ -50,15 +50,17 @@ pub fn Graph(comptime Payload: type) type {
|
|||
return null;
|
||||
}
|
||||
|
||||
return self.table.values.get(.edges, @intFromEnum(node)).?.values;
|
||||
return self.table.values.get(.edges)[@intFromEnum(node)].values;
|
||||
}
|
||||
|
||||
pub fn exists(self: Self, node: Node) bool {
|
||||
if (self.table.values.get(.is_occupied, @intFromEnum(node))) |is_occupied| {
|
||||
return is_occupied.*;
|
||||
const node_index = @intFromEnum(node);
|
||||
|
||||
if (node_index >= self.table.values.len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
return self.table.values.get(.is_occupied)[node_index];
|
||||
}
|
||||
|
||||
pub fn get(self: Self, node: Node) ?*Payload {
|
||||
|
@ -66,7 +68,7 @@ pub fn Graph(comptime Payload: type) type {
|
|||
return null;
|
||||
}
|
||||
|
||||
return self.table.values.get(.payload, @intFromEnum(node)).?;
|
||||
return &self.table.values.get(.payload)[@intFromEnum(node)];
|
||||
}
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Self {
|
||||
|
@ -76,13 +78,11 @@ pub fn Graph(comptime Payload: type) type {
|
|||
}
|
||||
|
||||
pub fn insert_edge(self: *Self, dependant_node: Node, edge_node: Node) std.mem.Allocator.Error!bool {
|
||||
if (!self.exists(edge_node)) {
|
||||
if (!self.exists(edge_node) or !self.exists(dependant_node)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const edges = self.table.values.get(.edges, @intFromEnum(dependant_node)) orelse {
|
||||
return false;
|
||||
};
|
||||
const edges = &self.table.values.get(.edges)[@intFromEnum(dependant_node)];
|
||||
|
||||
if (std.mem.indexOfScalar(Node, edges.values, edge_node) == null) {
|
||||
try edges.push_grow(edge_node);
|
||||
|
@ -97,7 +97,7 @@ pub fn Graph(comptime Payload: type) type {
|
|||
|
||||
pub fn nodes(self: *const Self) Nodes {
|
||||
return .{
|
||||
.occupied_table = self.table.values.slice(.is_occupied),
|
||||
.occupied_table = self.table.values.get(.is_occupied),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -106,7 +106,7 @@ pub fn Graph(comptime Payload: type) type {
|
|||
return false;
|
||||
}
|
||||
|
||||
std.debug.assert(self.table.values.set(.is_visited, @intFromEnum(node), true));
|
||||
self.table.values.get(.is_visited)[@intFromEnum(node)] = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -118,14 +118,14 @@ pub fn Graph(comptime Payload: type) type {
|
|||
|
||||
const node_index = @intFromEnum(node);
|
||||
|
||||
self.table.values.get(.is_occupied, node_index).?.* = false;
|
||||
self.table.values.get(.is_occupied)[node_index] = false;
|
||||
self.node_count -= 1;
|
||||
|
||||
return self.table.values.get(.payload, node_index).?.*;
|
||||
return self.table.values.get(.payload)[node_index];
|
||||
}
|
||||
|
||||
pub fn reset_visited(self: *Self) void {
|
||||
@memset(self.table.values.slice(.is_visited), false);
|
||||
@memset(self.table.values.get(.is_visited), false);
|
||||
}
|
||||
|
||||
pub fn visited(self: Self, node: Node) ?bool {
|
||||
|
@ -133,7 +133,7 @@ pub fn Graph(comptime Payload: type) type {
|
|||
return null;
|
||||
}
|
||||
|
||||
return self.table.values.get(.is_visited, @intFromEnum(node)).?.*;
|
||||
return self.table.values.get(.is_visited)[@intFromEnum(node)];
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(
|
|||
const max_int = std.math.maxInt(usize);
|
||||
|
||||
return struct {
|
||||
allocator: std.mem.Allocator,
|
||||
allocator: std.mem.Allocator = ona.heap.allocator,
|
||||
entry_map: []?Entry = &.{},
|
||||
len: usize = 0,
|
||||
|
||||
|
|
453
src/ona/ona.zig
453
src/ona/ona.zig
|
@ -29,185 +29,146 @@ pub const World = @import("./World.zig");
|
|||
pub const lina = @import("./lina.zig");
|
||||
|
||||
pub const App = struct {
|
||||
events: *const Events,
|
||||
step: fn (*World) anyerror!void = noop,
|
||||
|
||||
fn noop(_: *World) !void {}
|
||||
|
||||
pub fn build(self: App) fn () anyerror!void {
|
||||
const Start = struct {
|
||||
fn main() anyerror!void {
|
||||
defer {
|
||||
heap.trace_leaks();
|
||||
}
|
||||
|
||||
parse_args: for (std.os.argv[1 ..]) |arg| {
|
||||
const arg_span = std.mem.span(arg);
|
||||
const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len;
|
||||
const arg_name = arg_span[0 .. arg_split_index];
|
||||
|
||||
for (LaunchFlag.values) |value| {
|
||||
const name = @tagName(value);
|
||||
|
||||
if (!std.mem.eql(u8, arg_name, name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LaunchFlag.args[@intFromEnum(value)] =
|
||||
if (arg_split_index == arg_span.len)
|
||||
name
|
||||
else
|
||||
arg_span[arg_split_index ..];
|
||||
|
||||
continue: parse_args;
|
||||
}
|
||||
}
|
||||
|
||||
const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
|
||||
@panic(switch (cpu_count_error) {
|
||||
error.PermissionDenied => "permission denied retrieving CPU count",
|
||||
error.SystemResources => "system resources are preventing retrieval of the CPU count",
|
||||
error.Unexpected => "unexpected failure retrieving CPU count",
|
||||
});
|
||||
}, 0, std.math.maxInt(u32))));
|
||||
|
||||
var world = try World.init(scalars.fractional(cpu_count, @as(f32, 0.75)) orelse 0);
|
||||
|
||||
defer world.deinit();
|
||||
|
||||
try self.step(&world);
|
||||
}
|
||||
};
|
||||
|
||||
return Start.main;
|
||||
}
|
||||
|
||||
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")) {
|
||||
@compileError("`Module` argument must have a setup fn declaration");
|
||||
}
|
||||
|
||||
const WithState = struct {
|
||||
fn step(world: *World) !void {
|
||||
try Module.setup(world);
|
||||
try self.step(world);
|
||||
}
|
||||
};
|
||||
|
||||
return .{
|
||||
.step = WithState.step,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn with_state(self: App, comptime state: anytype) App {
|
||||
const WithState = struct {
|
||||
fn step(world: *World) !void {
|
||||
try world.set_state(state);
|
||||
try self.step(world);
|
||||
}
|
||||
};
|
||||
|
||||
return .{
|
||||
.step = WithState.step,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn with_system(self: App, comptime event: anytype, comptime info: *const SystemInfo, comptime order: SystemOrder) App {
|
||||
const WithState = struct {
|
||||
fn step(world: *World) !void {
|
||||
try world.on_event(event, info, order);
|
||||
try self.step(world);
|
||||
}
|
||||
};
|
||||
|
||||
return .{
|
||||
.step = WithState.step,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const Loop = struct {
|
||||
target_frame_time: f64,
|
||||
elapsed_time: f64,
|
||||
is_running: bool,
|
||||
|
||||
pub const Events = struct {
|
||||
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 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 {
|
||||
fn main() anyerror!void {
|
||||
defer {
|
||||
heap.trace_leaks();
|
||||
}
|
||||
|
||||
parse_args: for (std.os.argv[1 ..]) |arg| {
|
||||
const arg_span = std.mem.span(arg);
|
||||
const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len;
|
||||
const arg_name = arg_span[0 .. arg_split_index];
|
||||
|
||||
for (LaunchFlag.values) |value| {
|
||||
const name = @tagName(value);
|
||||
|
||||
if (!std.mem.eql(u8, arg_name, name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
LaunchFlag.args[@intFromEnum(value)] =
|
||||
if (arg_split_index == arg_span.len)
|
||||
name
|
||||
else
|
||||
arg_span[arg_split_index ..];
|
||||
|
||||
continue: parse_args;
|
||||
}
|
||||
}
|
||||
|
||||
const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
|
||||
@panic(switch (cpu_count_error) {
|
||||
error.PermissionDenied => "permission denied retrieving CPU count",
|
||||
error.SystemResources => "system resources are preventing retrieval of the CPU count",
|
||||
error.Unexpected => "unexpected failure retrieving CPU count",
|
||||
});
|
||||
}, 0, std.math.maxInt(u32))));
|
||||
|
||||
var world = try World.init(scalars.fractional(cpu_count, @as(f32, 0.75)) orelse 0);
|
||||
|
||||
defer world.deinit();
|
||||
|
||||
const events = App.Events{
|
||||
.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;
|
||||
}
|
||||
|
||||
pub fn with_module(self: Setup, comptime Module: type) Setup {
|
||||
if (!@hasDecl(Module, "setup")) {
|
||||
@compileError("`Module` argument must have a setup fn declaration");
|
||||
}
|
||||
|
||||
const WithState = struct {
|
||||
fn step(world: *World, events: App.Events) !void {
|
||||
try Module.setup(world, events);
|
||||
try self.step(world, events);
|
||||
}
|
||||
};
|
||||
|
||||
return .{
|
||||
.step = WithState.step,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn with_state(self: Setup, comptime state: anytype) Setup {
|
||||
const WithState = struct {
|
||||
fn step(world: *World, events: App.Events) !void {
|
||||
try world.set_state(state);
|
||||
try self.step(world, events);
|
||||
}
|
||||
};
|
||||
|
||||
return .{
|
||||
.step = WithState.step,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn with_system(self: Setup, comptime event: Event, comptime info: *const SystemInfo, comptime order: SystemOrder) Setup {
|
||||
const WithState = struct {
|
||||
fn step(world: *World, events: App.Events) !void {
|
||||
const app_event = switch (event) {
|
||||
.load => events.load,
|
||||
.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);
|
||||
}
|
||||
};
|
||||
|
||||
return .{
|
||||
.step = WithState.step,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
pub const setup = Setup{};
|
||||
|
||||
pub fn quit(self: *App) void {
|
||||
pub fn quit(self: *Loop) void {
|
||||
self.is_running = false;
|
||||
}
|
||||
};
|
||||
|
@ -355,7 +316,10 @@ pub const LaunchFlag = enum {
|
|||
|
||||
pub fn Params(comptime Value: type) type {
|
||||
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 {
|
||||
|
@ -519,15 +483,11 @@ pub fn Receive(comptime Message: type) type {
|
|||
.channel = (try context.register_readable_state_access(TypedChannel)) orelse set: {
|
||||
try context.world.set_state(TypedChannel.init(heap.allocator));
|
||||
|
||||
const app = context.world.get_state(App) orelse {
|
||||
@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), .{
|
||||
try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{
|
||||
.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),
|
||||
});
|
||||
|
||||
|
@ -565,15 +525,11 @@ pub fn Send(comptime Message: type) type {
|
|||
.channel = (try context.register_writable_state_access(TypedChannel)) orelse set: {
|
||||
try context.world.set_state(TypedChannel.init(heap.allocator));
|
||||
|
||||
const app = context.world.get_state(App) orelse {
|
||||
@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), .{
|
||||
try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{
|
||||
.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),
|
||||
});
|
||||
|
||||
|
@ -679,6 +635,127 @@ pub fn Shared(comptime Value: type, comptime info: ShareInfo) type {
|
|||
};
|
||||
}
|
||||
|
||||
pub fn SlotMap(comptime Value: type) type {
|
||||
const Index = @Type(.{.Int = .{
|
||||
.bits = @bitSizeOf(usize) - 1,
|
||||
.signedness = .unsigned,
|
||||
}});
|
||||
|
||||
const Slot = packed struct {
|
||||
index: Index,
|
||||
is_occupied: bool,
|
||||
};
|
||||
|
||||
comptime std.debug.assert(@sizeOf(Slot) == @sizeOf(usize));
|
||||
|
||||
return struct {
|
||||
slots: stack.Sequential(Slot),
|
||||
entries: stack.Parallel(Entry),
|
||||
next: Index = 0,
|
||||
|
||||
const Entry = struct {
|
||||
value: Value,
|
||||
erased: usize,
|
||||
};
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
self.slots.deinit();
|
||||
self.entries.deinit();
|
||||
}
|
||||
|
||||
pub fn get(self: Self, index: usize) ?*Value {
|
||||
if (index >= self.slots.len()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const slot = self.slots.values[index];
|
||||
|
||||
if (!slot.is_occupied) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return &self.entries.values.get(.value)[slot.index];
|
||||
}
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Self {
|
||||
return .{
|
||||
.slots = .{.allocator = allocator},
|
||||
.entries = .{.allocator = allocator},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
|
||||
const index = self.next;
|
||||
const slots_len: Index = @intCast(self.slots.len());
|
||||
|
||||
if (self.next == slots_len) {
|
||||
try self.slots.push_grow(.{
|
||||
.index = slots_len + 1,
|
||||
.is_occupied = false,
|
||||
});
|
||||
}
|
||||
|
||||
const slot = &self.slots.values[index];
|
||||
|
||||
std.debug.assert(!slot.is_occupied);
|
||||
|
||||
self.next = slot.index;
|
||||
|
||||
slot.* = .{
|
||||
.index = index,
|
||||
.is_occupied = true,
|
||||
};
|
||||
|
||||
try self.entries.push_grow(.{
|
||||
.value = value,
|
||||
.erased = index,
|
||||
});
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
pub fn remove(self: *Self, index: usize) ?Value {
|
||||
if (index >= self.slots.len()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const slot = self.slots.values[index];
|
||||
|
||||
if (!slot.is_occupied) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const value = self.entries.values.get(.value)[slot.index];
|
||||
|
||||
std.debug.assert(self.entries.values.write(slot.index, .{
|
||||
.value = self.entries.get(.value).?.*,
|
||||
.erased = self.entries.get(.erased).?.*,
|
||||
}));
|
||||
|
||||
std.debug.assert(self.entries.pop());
|
||||
|
||||
if (!self.entries.is_empty()) {
|
||||
self.slots.values[self.entries.values.get(.erased)[slot.index]] = slot;
|
||||
}
|
||||
|
||||
self.slots.values[index] = .{
|
||||
.index = self.next,
|
||||
.is_occupied = false,
|
||||
};
|
||||
|
||||
self.next = @intCast(index);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
pub fn values(self: Self) []Value {
|
||||
return self.entries.values.get(.value);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub const SystemInfo = struct {
|
||||
execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void,
|
||||
parameters: [max_parameters]*const Parameter = undefined,
|
||||
|
|
|
@ -35,15 +35,15 @@ pub fn Parallel(comptime Type: type) type {
|
|||
|
||||
const all_fields = std.enums.values(Field);
|
||||
|
||||
pub fn get(self: Self, comptime field: Field) []align (alignment) Element(field) {
|
||||
return self.ptr(field)[0 .. self.len];
|
||||
}
|
||||
|
||||
pub fn ptr(self: Self, comptime field: Field) [*]align (alignment) Element(field) {
|
||||
return @as([*]align (alignment) Element(field), @ptrCast(self.ptrs[@intFromEnum(field)]));
|
||||
}
|
||||
|
||||
pub fn slice(self: Self, comptime field: Field) []align (alignment) Element(field) {
|
||||
return self.ptr(field)[0 .. self.len];
|
||||
}
|
||||
|
||||
pub fn slice_all(self: Self, off: usize, len: usize) ?Self {
|
||||
pub fn slice(self: Self, off: usize, len: usize) ?Self {
|
||||
if (len > self.len or off > len) {
|
||||
return null;
|
||||
}
|
||||
|
@ -57,31 +57,13 @@ pub fn Parallel(comptime Type: type) type {
|
|||
return sliced;
|
||||
}
|
||||
|
||||
pub fn get(self: Self, comptime field: Field, index: usize) ?*Element(field) {
|
||||
if (index >= self.len) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return &self.ptr(field)[index];
|
||||
}
|
||||
|
||||
pub fn set(self: Self, comptime field: Field, index: usize, value: Element(field)) bool {
|
||||
if (index >= self.len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.slice(field)[index] = value;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn set_all(self: Self, index: usize, value: Type) bool {
|
||||
pub fn write(self: Self, index: usize, value: Type) bool {
|
||||
if (index >= self.len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
inline for (0 .. fields.len) |i| {
|
||||
self.slice(all_fields[i])[index] = @field(value, fields[i].name);
|
||||
self.get(all_fields[i])[index] = @field(value, fields[i].name);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -123,12 +105,12 @@ pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: u
|
|||
|
||||
pub fn parallel_copy(comptime Element: type, target: Parallel(Element), origin: Parallel(Element)) void {
|
||||
inline for (comptime std.enums.values(Parallel(Element).Field)) |field| {
|
||||
@memcpy(target.slice(field), origin.slice(field));
|
||||
@memcpy(target.get(field), origin.get(field));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parallel_free(comptime Element: type, allocator: std.mem.Allocator, buffers: Parallel(Element)) void {
|
||||
inline for (comptime std.enums.values(Parallel(Element).Field)) |field| {
|
||||
allocator.free(buffers.slice(field));
|
||||
allocator.free(buffers.get(field));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,11 +25,12 @@ pub fn Sequential(comptime Value: type) type {
|
|||
}
|
||||
|
||||
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
|
||||
if (additional == 0) {
|
||||
const grown_capacity = self.values.len + additional;
|
||||
|
||||
if (grown_capacity <= self.cap) {
|
||||
return;
|
||||
}
|
||||
|
||||
const grown_capacity = self.cap + additional;
|
||||
const buffer = try self.allocator.alloc(Value, grown_capacity);
|
||||
|
||||
errdefer self.allocator.deallocate(buffer);
|
||||
|
@ -59,14 +60,20 @@ pub fn Sequential(comptime Value: type) type {
|
|||
return self.values.len;
|
||||
}
|
||||
|
||||
pub fn pop(self: *Self) bool {
|
||||
pub fn pop(self: *Self) ?Value {
|
||||
if (self.values.len == 0) {
|
||||
return false;
|
||||
return null;
|
||||
}
|
||||
|
||||
self.values = self.values[0 .. self.values.len - 1];
|
||||
const tail_index = self.values.len - 1;
|
||||
|
||||
return true;
|
||||
defer self.values = self.values[0 .. tail_index];
|
||||
|
||||
return self.values[tail_index];
|
||||
}
|
||||
|
||||
pub fn pop_all(self: *Self) void {
|
||||
self.values = self.values[0 .. 0];
|
||||
}
|
||||
|
||||
pub fn pop_many(self: *Self, n: usize) bool {
|
||||
|
@ -112,9 +119,7 @@ pub fn Sequential(comptime Value: type) type {
|
|||
}
|
||||
|
||||
pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
|
||||
if (self.values.len == self.cap) {
|
||||
try self.grow(@max(1, self.cap));
|
||||
}
|
||||
try self.grow(@max(1, self.values.len));
|
||||
|
||||
const offset_index = self.values.len;
|
||||
|
||||
|
@ -227,46 +232,63 @@ pub fn Parallel(comptime Value: type) type {
|
|||
return null;
|
||||
}
|
||||
|
||||
return &self.slices.field_slice(field)[self.len() - 1];
|
||||
return @alignCast(&self.values.get(field)[self.len() - 1]);
|
||||
}
|
||||
|
||||
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
|
||||
const grown_capacity = self.cap + additional;
|
||||
const grown_capacity = self.values.len + additional;
|
||||
|
||||
if (grown_capacity <= self.cap) {
|
||||
return;
|
||||
}
|
||||
|
||||
const buffer = try ona.slices.parallel_alloc(Value, self.allocator, grown_capacity);
|
||||
|
||||
if (self.cap != 0) {
|
||||
ona.slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values);
|
||||
ona.slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?);
|
||||
ona.slices.parallel_copy(Value, buffer.slice(0, self.values.len).?, self.values);
|
||||
ona.slices.parallel_free(Value, self.allocator, self.values.slice(0, self.cap).?);
|
||||
}
|
||||
|
||||
self.cap = grown_capacity;
|
||||
self.values = buffer.slice_all(0, self.values.len).?;
|
||||
self.values = buffer.slice(0, self.values.len).?;
|
||||
}
|
||||
|
||||
pub fn is_empty(self: Self) bool {
|
||||
return self.values.len == 0;
|
||||
}
|
||||
|
||||
pub fn len(self: Self) usize {
|
||||
return self.values.len;
|
||||
}
|
||||
|
||||
pub fn pop(self: *Self) bool {
|
||||
if (self.values.len == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.values = self.values.slice(0, self.values.len - 1).?;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn pop_many(self: *Self, n: usize) bool {
|
||||
const new_length = ona.scalars.sub(self.values.len, n) orelse {
|
||||
return false;
|
||||
};
|
||||
|
||||
self.values = self.values.slice_all(0, new_length).?;
|
||||
self.values = self.values.slice(0, new_length).?;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
pub fn push_grow(self: *Self, value: Value) std.mem.Allocator.Error!void {
|
||||
if (self.len() == self.cap) {
|
||||
try self.grow(@max(1, self.cap));
|
||||
}
|
||||
try self.grow(@max(1, self.values.len));
|
||||
|
||||
const tail_index = self.values.len;
|
||||
|
||||
self.values.len += 1;
|
||||
|
||||
std.debug.assert(self.values.set_all(tail_index, value));
|
||||
std.debug.assert(self.values.write(tail_index, value));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue