module: event handlers are defined ahead of time
Signed-off-by: Stephen Gutekanst <stephen@hexops.com>
This commit is contained in:
parent
0fc3bf6545
commit
3bfafe102d
12 changed files with 1099 additions and 967 deletions
|
|
@ -20,6 +20,11 @@ pub const components = struct {
|
||||||
pub const follower = void;
|
pub const follower = void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .init, .handler = init },
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
// Each module must have a globally unique name declared, it is impossible to use two modules with
|
// Each module must have a globally unique name declared, it is impossible to use two modules with
|
||||||
// the same name in a program. To avoid name conflicts, we follow naming conventions:
|
// the same name in a program. To avoid name conflicts, we follow naming conventions:
|
||||||
//
|
//
|
||||||
|
|
@ -33,7 +38,8 @@ pub const components = struct {
|
||||||
pub const name = .game;
|
pub const name = .game;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
pub fn init(
|
// TODO(engine): remove need for returning an error here
|
||||||
|
fn init(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
renderer: *Renderer.Mod,
|
renderer: *Renderer.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
|
|
@ -56,7 +62,8 @@ pub fn init(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(
|
// TODO(engine): remove need for returning an error here
|
||||||
|
fn tick(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
renderer: *Renderer.Mod,
|
renderer: *Renderer.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,19 @@ pub const components = struct {
|
||||||
pub const scale = f32;
|
pub const scale = f32;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .init, .handler = init },
|
||||||
|
.{ .global = .deinit, .handler = deinit },
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: this shouldn't be a packed struct, it should be extern.
|
// TODO: this shouldn't be a packed struct, it should be extern.
|
||||||
const UniformBufferObject = packed struct {
|
const UniformBufferObject = packed struct {
|
||||||
offset: Vec3.Vector,
|
offset: Vec3.Vector,
|
||||||
scale: f32,
|
scale: f32,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(
|
fn init(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
renderer: *Mod,
|
renderer: *Mod,
|
||||||
) !void {
|
) !void {
|
||||||
|
|
@ -97,7 +103,7 @@ pub fn init(
|
||||||
shader_module.release();
|
shader_module.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(
|
fn deinit(
|
||||||
renderer: *Mod,
|
renderer: *Mod,
|
||||||
) !void {
|
) !void {
|
||||||
renderer.state.pipeline.release();
|
renderer.state.pipeline.release();
|
||||||
|
|
@ -106,7 +112,7 @@ pub fn deinit(
|
||||||
renderer.state.uniform_buffer.release();
|
renderer.state.uniform_buffer.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(
|
fn tick(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
renderer: *Mod,
|
renderer: *Mod,
|
||||||
) !void {
|
) !void {
|
||||||
|
|
|
||||||
|
|
@ -40,12 +40,17 @@ const d0 = 0.000001;
|
||||||
pub const name = .game;
|
pub const name = .game;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .init, .handler = init },
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
pub const Pipeline = enum(u32) {
|
pub const Pipeline = enum(u32) {
|
||||||
default,
|
default,
|
||||||
text,
|
text,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(
|
fn init(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
sprite_mod: *Sprite.Mod,
|
sprite_mod: *Sprite.Mod,
|
||||||
text_mod: *Text.Mod,
|
text_mod: *Text.Mod,
|
||||||
|
|
@ -91,7 +96,7 @@ pub fn init(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(
|
fn tick(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
sprite_mod: *Sprite.Mod,
|
sprite_mod: *Sprite.Mod,
|
||||||
text_mod: *Text.Mod,
|
text_mod: *Text.Mod,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,12 @@ const assets = @import("assets");
|
||||||
pub const name = .game_text;
|
pub const name = .game_text;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .deinit, .handler = deinit },
|
||||||
|
.{ .local = .init, .handler = init },
|
||||||
|
.{ .local = .prepare, .handler = prepare },
|
||||||
|
};
|
||||||
|
|
||||||
const RegionMap = std.AutoArrayHashMapUnmanaged(u21, mach.gfx.Atlas.Region);
|
const RegionMap = std.AutoArrayHashMapUnmanaged(u21, mach.gfx.Atlas.Region);
|
||||||
|
|
||||||
texture_atlas: mach.gfx.Atlas,
|
texture_atlas: mach.gfx.Atlas,
|
||||||
|
|
@ -16,7 +22,7 @@ ft: ft.Library,
|
||||||
face: ft.Face,
|
face: ft.Face,
|
||||||
regions: RegionMap = .{},
|
regions: RegionMap = .{},
|
||||||
|
|
||||||
pub fn deinit(
|
fn deinit(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
text_mod: *Mod,
|
text_mod: *Mod,
|
||||||
) !void {
|
) !void {
|
||||||
|
|
@ -27,97 +33,95 @@ pub fn deinit(
|
||||||
text_mod.state.regions.deinit(engine.allocator);
|
text_mod.state.regions.deinit(engine.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const local = struct {
|
fn init(
|
||||||
pub fn init(
|
engine: *mach.Engine.Mod,
|
||||||
engine: *mach.Engine.Mod,
|
text_mod: *Mod,
|
||||||
text_mod: *Mod,
|
) !void {
|
||||||
) !void {
|
const device = engine.state.device;
|
||||||
const device = engine.state.device;
|
|
||||||
|
|
||||||
// rgba32_pixels
|
// rgba32_pixels
|
||||||
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
|
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
|
||||||
|
|
||||||
// Create a GPU texture
|
// Create a GPU texture
|
||||||
const texture = device.createTexture(&.{
|
const texture = device.createTexture(&.{
|
||||||
.size = img_size,
|
.size = img_size,
|
||||||
.format = .rgba8_unorm,
|
.format = .rgba8_unorm,
|
||||||
.usage = .{
|
.usage = .{
|
||||||
.texture_binding = true,
|
.texture_binding = true,
|
||||||
.copy_dst = true,
|
.copy_dst = true,
|
||||||
.render_attachment = true,
|
.render_attachment = true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
var s = &text_mod.state;
|
var s = &text_mod.state;
|
||||||
s.texture = texture;
|
s.texture = texture;
|
||||||
s.texture_atlas = try mach.gfx.Atlas.init(
|
s.texture_atlas = try mach.gfx.Atlas.init(
|
||||||
engine.allocator,
|
engine.allocator,
|
||||||
img_size.width,
|
img_size.width,
|
||||||
.rgba,
|
.rgba,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: state fields' default values do not work
|
// TODO: state fields' default values do not work
|
||||||
s.regions = .{};
|
s.regions = .{};
|
||||||
|
|
||||||
s.ft = try ft.Library.init();
|
s.ft = try ft.Library.init();
|
||||||
s.face = try s.ft.createFaceMemory(assets.roboto_medium_ttf, 0);
|
s.face = try s.ft.createFaceMemory(assets.roboto_medium_ttf, 0);
|
||||||
|
|
||||||
text_mod.send(.prepare, .{&[_]u21{ '?', '!', 'a', 'b', '#', '@', '%', '$', '&', '^', '*', '+', '=', '<', '>', '/', ':', ';', 'Q', '~' }});
|
text_mod.send(.prepare, .{&[_]u21{ '?', '!', 'a', 'b', '#', '@', '%', '$', '&', '^', '*', '+', '=', '<', '>', '/', ':', ';', 'Q', '~' }});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare(
|
fn prepare(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
text_mod: *Mod,
|
text_mod: *Mod,
|
||||||
codepoints: []const u21,
|
codepoints: []const u21,
|
||||||
) !void {
|
) !void {
|
||||||
const device = engine.state.device;
|
const device = engine.state.device;
|
||||||
const queue = device.getQueue();
|
const queue = device.getQueue();
|
||||||
var s = &text_mod.state;
|
var s = &text_mod.state;
|
||||||
|
|
||||||
for (codepoints) |codepoint| {
|
for (codepoints) |codepoint| {
|
||||||
const font_size = 48 * 1;
|
const font_size = 48 * 1;
|
||||||
try s.face.setCharSize(font_size * 64, 0, 50, 0);
|
try s.face.setCharSize(font_size * 64, 0, 50, 0);
|
||||||
try s.face.loadChar(codepoint, .{ .render = true });
|
try s.face.loadChar(codepoint, .{ .render = true });
|
||||||
const glyph = s.face.glyph();
|
const glyph = s.face.glyph();
|
||||||
const metrics = glyph.metrics();
|
const metrics = glyph.metrics();
|
||||||
|
|
||||||
const glyph_bitmap = glyph.bitmap();
|
const glyph_bitmap = glyph.bitmap();
|
||||||
const glyph_width = glyph_bitmap.width();
|
const glyph_width = glyph_bitmap.width();
|
||||||
const glyph_height = glyph_bitmap.rows();
|
const glyph_height = glyph_bitmap.rows();
|
||||||
|
|
||||||
// Add 1 pixel padding to texture to avoid bleeding over other textures
|
// Add 1 pixel padding to texture to avoid bleeding over other textures
|
||||||
const margin = 1;
|
const margin = 1;
|
||||||
const glyph_data = try engine.allocator.alloc([4]u8, (glyph_width + (margin * 2)) * (glyph_height + (margin * 2)));
|
const glyph_data = try engine.allocator.alloc([4]u8, (glyph_width + (margin * 2)) * (glyph_height + (margin * 2)));
|
||||||
defer engine.allocator.free(glyph_data);
|
defer engine.allocator.free(glyph_data);
|
||||||
const glyph_buffer = glyph_bitmap.buffer().?;
|
const glyph_buffer = glyph_bitmap.buffer().?;
|
||||||
for (glyph_data, 0..) |*data, i| {
|
for (glyph_data, 0..) |*data, i| {
|
||||||
const x = i % (glyph_width + (margin * 2));
|
const x = i % (glyph_width + (margin * 2));
|
||||||
const y = i / (glyph_width + (margin * 2));
|
const y = i / (glyph_width + (margin * 2));
|
||||||
if (x < margin or x > (glyph_width + margin) or y < margin or y > (glyph_height + margin)) {
|
if (x < margin or x > (glyph_width + margin) or y < margin or y > (glyph_height + margin)) {
|
||||||
data.* = [4]u8{ 0, 0, 0, 0 };
|
data.* = [4]u8{ 0, 0, 0, 0 };
|
||||||
} else {
|
} else {
|
||||||
const alpha = glyph_buffer[((y - margin) * glyph_width + (x - margin)) % glyph_buffer.len];
|
const alpha = glyph_buffer[((y - margin) * glyph_width + (x - margin)) % glyph_buffer.len];
|
||||||
data.* = [4]u8{ 0, 0, 0, alpha };
|
data.* = [4]u8{ 0, 0, 0, alpha };
|
||||||
}
|
|
||||||
}
|
}
|
||||||
var glyph_atlas_region = try s.texture_atlas.reserve(engine.allocator, glyph_width + (margin * 2), glyph_height + (margin * 2));
|
|
||||||
s.texture_atlas.set(glyph_atlas_region, @as([*]const u8, @ptrCast(glyph_data.ptr))[0 .. glyph_data.len * 4]);
|
|
||||||
|
|
||||||
glyph_atlas_region.x += margin;
|
|
||||||
glyph_atlas_region.y += margin;
|
|
||||||
glyph_atlas_region.width -= margin * 2;
|
|
||||||
glyph_atlas_region.height -= margin * 2;
|
|
||||||
|
|
||||||
try s.regions.put(engine.allocator, codepoint, glyph_atlas_region);
|
|
||||||
_ = metrics;
|
|
||||||
}
|
}
|
||||||
|
var glyph_atlas_region = try s.texture_atlas.reserve(engine.allocator, glyph_width + (margin * 2), glyph_height + (margin * 2));
|
||||||
|
s.texture_atlas.set(glyph_atlas_region, @as([*]const u8, @ptrCast(glyph_data.ptr))[0 .. glyph_data.len * 4]);
|
||||||
|
|
||||||
// rgba32_pixels
|
glyph_atlas_region.x += margin;
|
||||||
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
|
glyph_atlas_region.y += margin;
|
||||||
const data_layout = gpu.Texture.DataLayout{
|
glyph_atlas_region.width -= margin * 2;
|
||||||
.bytes_per_row = @as(u32, @intCast(img_size.width * 4)),
|
glyph_atlas_region.height -= margin * 2;
|
||||||
.rows_per_image = @as(u32, @intCast(img_size.height)),
|
|
||||||
};
|
try s.regions.put(engine.allocator, codepoint, glyph_atlas_region);
|
||||||
queue.writeTexture(&.{ .texture = s.texture }, &data_layout, &img_size, s.texture_atlas.data);
|
_ = metrics;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
// rgba32_pixels
|
||||||
|
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
|
||||||
|
const data_layout = gpu.Texture.DataLayout{
|
||||||
|
.bytes_per_row = @as(u32, @intCast(img_size.width * 4)),
|
||||||
|
.rows_per_image = @as(u32, @intCast(img_size.height)),
|
||||||
|
};
|
||||||
|
queue.writeTexture(&.{ .texture = s.texture }, &data_layout, &img_size, s.texture_atlas.data);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,11 +41,16 @@ const d0 = 0.000001;
|
||||||
pub const name = .game;
|
pub const name = .game;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .init, .handler = init },
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
pub const Pipeline = enum(u32) {
|
pub const Pipeline = enum(u32) {
|
||||||
default,
|
default,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn init(
|
fn init(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
sprite_mod: *Sprite.Mod,
|
sprite_mod: *Sprite.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
|
|
@ -82,7 +87,7 @@ pub fn init(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(
|
fn tick(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
sprite_mod: *Sprite.Mod,
|
sprite_mod: *Sprite.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,12 @@ const d0 = 0.000001;
|
||||||
pub const name = .game;
|
pub const name = .game;
|
||||||
pub const Mod = mach.Mod(@This());
|
pub const Mod = mach.Mod(@This());
|
||||||
|
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .init, .handler = init },
|
||||||
|
.{ .global = .deinit, .handler = deinit },
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
pub const Pipeline = enum(u32) {
|
pub const Pipeline = enum(u32) {
|
||||||
default,
|
default,
|
||||||
};
|
};
|
||||||
|
|
@ -58,7 +64,7 @@ const text1: []const []const u8 = &.{
|
||||||
|
|
||||||
const text2: []const []const u8 = &.{"!$?😊"};
|
const text2: []const []const u8 = &.{"!$?😊"};
|
||||||
|
|
||||||
pub fn init(
|
fn init(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
text_mod: *Text.Mod,
|
text_mod: *Text.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
|
|
@ -122,11 +128,11 @@ pub fn init(
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(engine: *mach.Engine.Mod) !void {
|
fn deinit(engine: *mach.Engine.Mod) !void {
|
||||||
_ = engine;
|
_ = engine;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tick(
|
fn tick(
|
||||||
engine: *mach.Engine.Mod,
|
engine: *mach.Engine.Mod,
|
||||||
text_mod: *Text.Mod,
|
text_mod: *Text.Mod,
|
||||||
game: *Mod,
|
game: *Mod,
|
||||||
|
|
|
||||||
|
|
@ -40,39 +40,47 @@ test "inclusion" {
|
||||||
test "example" {
|
test "example" {
|
||||||
const allocator = testing.allocator;
|
const allocator = testing.allocator;
|
||||||
|
|
||||||
comptime var Renderer = type;
|
const root = struct {
|
||||||
comptime var Physics = type;
|
pub const modules = .{ Renderer, Physics };
|
||||||
Physics = mach.Module(struct {
|
|
||||||
pointer: u8,
|
|
||||||
|
|
||||||
pub const name = .physics;
|
const Physics = struct {
|
||||||
pub const components = struct {
|
pointer: u8,
|
||||||
pub const id = u32;
|
|
||||||
|
pub const name = .physics;
|
||||||
|
pub const components = struct {
|
||||||
|
pub const id = u32;
|
||||||
|
};
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
|
fn tick(physics: *World(modules).Mod(Physics)) void {
|
||||||
|
_ = physics;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn tick(physics: *World(.{ Renderer, Physics }).Mod(Physics)) void {
|
const Renderer = struct {
|
||||||
_ = physics;
|
pub const name = .renderer;
|
||||||
}
|
pub const components = struct {
|
||||||
});
|
pub const id = u16;
|
||||||
|
};
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
Renderer = mach.Module(struct {
|
fn tick(
|
||||||
pub const name = .renderer;
|
physics: *World(modules).Mod(Physics),
|
||||||
pub const components = struct {
|
renderer: *World(modules).Mod(Renderer),
|
||||||
pub const id = u16;
|
) void {
|
||||||
|
_ = renderer;
|
||||||
|
_ = physics;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
};
|
||||||
pub fn tick(
|
|
||||||
physics: *World(.{ Renderer, Physics }).Mod(Physics),
|
|
||||||
renderer: *World(.{ Renderer, Physics }).Mod(Renderer),
|
|
||||||
) void {
|
|
||||||
_ = renderer;
|
|
||||||
_ = physics;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//-------------------------------------------------------------------------
|
//-------------------------------------------------------------------------
|
||||||
// Create a world.
|
// Create a world.
|
||||||
var world: World(.{ Renderer, Physics }) = undefined;
|
var world: World(root.modules) = undefined;
|
||||||
try world.init(allocator);
|
try world.init(allocator);
|
||||||
defer world.deinit();
|
defer world.deinit();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ const mach = @import("../main.zig");
|
||||||
const Entities = @import("entities.zig").Entities;
|
const Entities = @import("entities.zig").Entities;
|
||||||
const EntityID = @import("entities.zig").EntityID;
|
const EntityID = @import("entities.zig").EntityID;
|
||||||
const comp = @import("comptime.zig");
|
const comp = @import("comptime.zig");
|
||||||
|
const Module = @import("../module.zig").Module;
|
||||||
|
|
||||||
pub fn World(comptime mods: anytype) type {
|
pub fn World(comptime mods: anytype) type {
|
||||||
const StateT = NamespacedState(mods);
|
const StateT = NamespacedState(mods);
|
||||||
|
|
@ -21,8 +22,8 @@ pub fn World(comptime mods: anytype) type {
|
||||||
pub const IsInjectedArgument = void;
|
pub const IsInjectedArgument = void;
|
||||||
|
|
||||||
const WorldT = @This();
|
const WorldT = @This();
|
||||||
pub fn Mod(comptime Module: anytype) type {
|
pub fn Mod(comptime M: anytype) type {
|
||||||
const module_tag = Module.name;
|
const module_tag = M.name;
|
||||||
const State = @TypeOf(@field(@as(StateT, undefined), @tagName(module_tag)));
|
const State = @TypeOf(@field(@as(StateT, undefined), @tagName(module_tag)));
|
||||||
const components = @field(ns_components, @tagName(module_tag));
|
const components = @field(ns_components, @tagName(module_tag));
|
||||||
return struct {
|
return struct {
|
||||||
|
|
@ -212,6 +213,8 @@ fn NamespacedComponents(comptime modules: anytype) type {
|
||||||
fn NamespacedState(comptime modules: anytype) type {
|
fn NamespacedState(comptime modules: anytype) type {
|
||||||
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
var fields: []const std.builtin.Type.StructField = &[0]std.builtin.Type.StructField{};
|
||||||
inline for (modules) |M| {
|
inline for (modules) |M| {
|
||||||
|
// TODO: can't verify module here because it would introduce a dependency loop
|
||||||
|
// _ = Module(M);
|
||||||
const state_fields = std.meta.fields(M);
|
const state_fields = std.meta.fields(M);
|
||||||
const State = if (state_fields.len > 0) @Type(.{
|
const State = if (state_fields.len > 0) @Type(.{
|
||||||
.Struct = .{
|
.Struct = .{
|
||||||
|
|
|
||||||
151
src/engine.zig
151
src/engine.zig
|
|
@ -11,84 +11,91 @@ const allocator = gpa.allocator();
|
||||||
pub const Engine = struct {
|
pub const Engine = struct {
|
||||||
device: *gpu.Device,
|
device: *gpu.Device,
|
||||||
queue: *gpu.Queue,
|
queue: *gpu.Queue,
|
||||||
exit: bool,
|
should_exit: bool,
|
||||||
pass: *gpu.RenderPassEncoder,
|
pass: *gpu.RenderPassEncoder,
|
||||||
encoder: *gpu.CommandEncoder,
|
encoder: *gpu.CommandEncoder,
|
||||||
|
|
||||||
pub const name = .engine;
|
pub const name = .engine;
|
||||||
pub const Mod = World.Mod(@This());
|
pub const Mod = World.Mod(@This());
|
||||||
|
|
||||||
pub const exit = fn () void;
|
pub const events = .{
|
||||||
|
.{ .local = .init, .handler = init },
|
||||||
pub const local = struct {
|
.{ .local = .deinit, .handler = deinit },
|
||||||
pub fn init(world: *World) !void {
|
.{ .local = .exit, .handler = exit },
|
||||||
core.allocator = allocator;
|
.{ .local = .beginPass, .handler = beginPass },
|
||||||
try core.init(.{});
|
.{ .local = .endPass, .handler = endPass },
|
||||||
const state = &world.mod.engine.state;
|
.{ .local = .present, .handler = present },
|
||||||
state.device = core.device;
|
.{ .global = .tick, .handler = fn () void },
|
||||||
state.queue = core.device.getQueue();
|
.{ .global = .exit, .handler = fn () void },
|
||||||
state.exit = false;
|
|
||||||
state.encoder = state.device.createCommandEncoder(&gpu.CommandEncoder.Descriptor{
|
|
||||||
.label = "engine.state.encoder",
|
|
||||||
});
|
|
||||||
|
|
||||||
world.modules.send(.init, .{});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(world: *World, engine: *Mod) void {
|
|
||||||
// TODO: this triggers a device loss error, which we should handle correctly
|
|
||||||
// engine.state.device.release();
|
|
||||||
engine.state.queue.release();
|
|
||||||
world.modules.send(.deinit, .{});
|
|
||||||
core.deinit();
|
|
||||||
world.deinit();
|
|
||||||
_ = gpa.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Engine module's exit handler
|
|
||||||
pub fn exit(world: *World) void {
|
|
||||||
world.modules.send(.exit, .{});
|
|
||||||
world.mod.engine.state.exit = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn beginPass(engine: *Mod, clear_color: gpu.Color) void {
|
|
||||||
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
|
|
||||||
defer back_buffer_view.release();
|
|
||||||
|
|
||||||
// TODO: expose options
|
|
||||||
const color_attachment = gpu.RenderPassColorAttachment{
|
|
||||||
.view = back_buffer_view,
|
|
||||||
.clear_value = clear_color,
|
|
||||||
.load_op = .clear,
|
|
||||||
.store_op = .store,
|
|
||||||
};
|
|
||||||
const pass_info = gpu.RenderPassDescriptor.init(.{
|
|
||||||
.color_attachments = &.{color_attachment},
|
|
||||||
});
|
|
||||||
|
|
||||||
engine.state.pass = engine.state.encoder.beginRenderPass(&pass_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn endPass(engine: *Mod) void {
|
|
||||||
// End this pass
|
|
||||||
engine.state.pass.end();
|
|
||||||
engine.state.pass.release();
|
|
||||||
|
|
||||||
var command = engine.state.encoder.finish(null);
|
|
||||||
defer command.release();
|
|
||||||
engine.state.encoder.release();
|
|
||||||
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command});
|
|
||||||
|
|
||||||
// Prepare for next pass
|
|
||||||
engine.state.encoder = engine.state.device.createCommandEncoder(&gpu.CommandEncoder.Descriptor{
|
|
||||||
.label = "engine.state.encoder",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn present() void {
|
|
||||||
core.swap_chain.present();
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn init(world: *World) !void {
|
||||||
|
core.allocator = allocator;
|
||||||
|
try core.init(.{});
|
||||||
|
const state = &world.mod.engine.state;
|
||||||
|
state.device = core.device;
|
||||||
|
state.queue = core.device.getQueue();
|
||||||
|
state.should_exit = false;
|
||||||
|
state.encoder = state.device.createCommandEncoder(&gpu.CommandEncoder.Descriptor{
|
||||||
|
.label = "engine.state.encoder",
|
||||||
|
});
|
||||||
|
|
||||||
|
world.modules.send(.init, .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(world: *World, engine: *Mod) void {
|
||||||
|
// TODO: this triggers a device loss error, which we should handle correctly
|
||||||
|
// engine.state.device.release();
|
||||||
|
engine.state.queue.release();
|
||||||
|
world.modules.send(.deinit, .{});
|
||||||
|
core.deinit();
|
||||||
|
world.deinit();
|
||||||
|
_ = gpa.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Engine module's exit handler
|
||||||
|
fn exit(world: *World) void {
|
||||||
|
world.modules.send(.exit, .{});
|
||||||
|
world.mod.engine.state.should_exit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn beginPass(engine: *Mod, clear_color: gpu.Color) void {
|
||||||
|
const back_buffer_view = core.swap_chain.getCurrentTextureView().?;
|
||||||
|
defer back_buffer_view.release();
|
||||||
|
|
||||||
|
// TODO: expose options
|
||||||
|
const color_attachment = gpu.RenderPassColorAttachment{
|
||||||
|
.view = back_buffer_view,
|
||||||
|
.clear_value = clear_color,
|
||||||
|
.load_op = .clear,
|
||||||
|
.store_op = .store,
|
||||||
|
};
|
||||||
|
const pass_info = gpu.RenderPassDescriptor.init(.{
|
||||||
|
.color_attachments = &.{color_attachment},
|
||||||
|
});
|
||||||
|
|
||||||
|
engine.state.pass = engine.state.encoder.beginRenderPass(&pass_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn endPass(engine: *Mod) void {
|
||||||
|
// End this pass
|
||||||
|
engine.state.pass.end();
|
||||||
|
engine.state.pass.release();
|
||||||
|
|
||||||
|
var command = engine.state.encoder.finish(null);
|
||||||
|
defer command.release();
|
||||||
|
engine.state.encoder.release();
|
||||||
|
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||||
|
|
||||||
|
// Prepare for next pass
|
||||||
|
engine.state.encoder = engine.state.device.createCommandEncoder(&gpu.CommandEncoder.Descriptor{
|
||||||
|
.label = "engine.state.encoder",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn present() void {
|
||||||
|
core.swap_chain.present();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const App = struct {
|
pub const App = struct {
|
||||||
|
|
@ -110,7 +117,7 @@ pub const App = struct {
|
||||||
app.world.modules.send(.tick, .{});
|
app.world.modules.send(.tick, .{});
|
||||||
try app.world.dispatch(); // dispatch .tick
|
try app.world.dispatch(); // dispatch .tick
|
||||||
try app.world.dispatch(); // dispatch any events produced by .tick
|
try app.world.dispatch(); // dispatch any events produced by .tick
|
||||||
return app.world.mod.engine.state.exit;
|
return app.world.mod.engine.state.should_exit;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,15 @@ pub const components = struct {
|
||||||
pub const size = Vec2;
|
pub const size = Vec2;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .deinit, .handler = deinit },
|
||||||
|
.{ .local = .init, .handler = init },
|
||||||
|
.{ .local = .initPipeline, .handler = initPipeline },
|
||||||
|
.{ .local = .updated, .handler = updated },
|
||||||
|
.{ .local = .preRender, .handler = preRender },
|
||||||
|
.{ .local = .render, .handler = render },
|
||||||
|
};
|
||||||
|
|
||||||
const Uniforms = extern struct {
|
const Uniforms = extern struct {
|
||||||
// WebGPU requires that the size of struct fields are multiples of 16
|
// WebGPU requires that the size of struct fields are multiples of 16
|
||||||
// So we use align(16) and 'extern' to maintain field order
|
// So we use align(16) and 'extern' to maintain field order
|
||||||
|
|
@ -121,251 +130,249 @@ pub const PipelineOptions = struct {
|
||||||
pipeline_layout: ?*gpu.PipelineLayout = null,
|
pipeline_layout: ?*gpu.PipelineLayout = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn deinit(sprite_mod: *Mod) !void {
|
fn deinit(sprite_mod: *Mod) !void {
|
||||||
for (sprite_mod.state.pipelines.entries.items(.value)) |*pipeline| pipeline.deinit();
|
for (sprite_mod.state.pipelines.entries.items(.value)) |*pipeline| pipeline.deinit();
|
||||||
sprite_mod.state.pipelines.deinit(sprite_mod.allocator);
|
sprite_mod.state.pipelines.deinit(sprite_mod.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const local = struct {
|
fn init(
|
||||||
pub fn init(
|
sprite_mod: *Mod,
|
||||||
sprite_mod: *Mod,
|
) !void {
|
||||||
) !void {
|
sprite_mod.state = .{
|
||||||
sprite_mod.state = .{
|
// TODO: struct default value initializers don't work
|
||||||
// TODO: struct default value initializers don't work
|
.pipelines = .{},
|
||||||
.pipelines = .{},
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
|
fn initPipeline(
|
||||||
|
engine: *Engine.Mod,
|
||||||
|
sprite_mod: *Mod,
|
||||||
|
opt: PipelineOptions,
|
||||||
|
) !void {
|
||||||
|
const device = engine.state.device;
|
||||||
|
|
||||||
|
const pipeline = try sprite_mod.state.pipelines.getOrPut(engine.allocator, opt.pipeline);
|
||||||
|
if (pipeline.found_existing) {
|
||||||
|
pipeline.value_ptr.*.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initPipeline(
|
// Storage buffers
|
||||||
engine: *Engine.Mod,
|
const sprite_buffer_cap = 1024 * 512; // TODO: allow user to specify preallocation
|
||||||
sprite_mod: *Mod,
|
const transforms = device.createBuffer(&.{
|
||||||
opt: PipelineOptions,
|
.usage = .{ .storage = true, .copy_dst = true },
|
||||||
) !void {
|
.size = @sizeOf(Mat4x4) * sprite_buffer_cap,
|
||||||
const device = engine.state.device;
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
const uv_transforms = device.createBuffer(&.{
|
||||||
|
.usage = .{ .storage = true, .copy_dst = true },
|
||||||
|
.size = @sizeOf(Mat3x3) * sprite_buffer_cap,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
const sizes = device.createBuffer(&.{
|
||||||
|
.usage = .{ .storage = true, .copy_dst = true },
|
||||||
|
.size = @sizeOf(Vec2) * sprite_buffer_cap,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
const pipeline = try sprite_mod.state.pipelines.getOrPut(engine.allocator, opt.pipeline);
|
const texture_sampler = opt.texture_sampler orelse device.createSampler(&.{
|
||||||
if (pipeline.found_existing) {
|
.mag_filter = .nearest,
|
||||||
pipeline.value_ptr.*.deinit();
|
.min_filter = .nearest,
|
||||||
}
|
});
|
||||||
|
const uniforms = device.createBuffer(&.{
|
||||||
// Storage buffers
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
const sprite_buffer_cap = 1024 * 512; // TODO: allow user to specify preallocation
|
.size = @sizeOf(Uniforms),
|
||||||
const transforms = device.createBuffer(&.{
|
.mapped_at_creation = .false,
|
||||||
.usage = .{ .storage = true, .copy_dst = true },
|
});
|
||||||
.size = @sizeOf(Mat4x4) * sprite_buffer_cap,
|
const bind_group_layout = opt.bind_group_layout orelse device.createBindGroupLayout(
|
||||||
.mapped_at_creation = .false,
|
&gpu.BindGroupLayout.Descriptor.init(.{
|
||||||
});
|
.entries = &.{
|
||||||
const uv_transforms = device.createBuffer(&.{
|
gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, false, 0),
|
||||||
.usage = .{ .storage = true, .copy_dst = true },
|
gpu.BindGroupLayout.Entry.buffer(1, .{ .vertex = true }, .read_only_storage, false, 0),
|
||||||
.size = @sizeOf(Mat3x3) * sprite_buffer_cap,
|
gpu.BindGroupLayout.Entry.buffer(2, .{ .vertex = true }, .read_only_storage, false, 0),
|
||||||
.mapped_at_creation = .false,
|
gpu.BindGroupLayout.Entry.buffer(3, .{ .vertex = true }, .read_only_storage, false, 0),
|
||||||
});
|
gpu.BindGroupLayout.Entry.sampler(4, .{ .fragment = true }, .filtering),
|
||||||
const sizes = device.createBuffer(&.{
|
gpu.BindGroupLayout.Entry.texture(5, .{ .fragment = true }, .float, .dimension_2d, false),
|
||||||
.usage = .{ .storage = true, .copy_dst = true },
|
gpu.BindGroupLayout.Entry.texture(6, .{ .fragment = true }, .float, .dimension_2d, false),
|
||||||
.size = @sizeOf(Vec2) * sprite_buffer_cap,
|
gpu.BindGroupLayout.Entry.texture(7, .{ .fragment = true }, .float, .dimension_2d, false),
|
||||||
.mapped_at_creation = .false,
|
gpu.BindGroupLayout.Entry.texture(8, .{ .fragment = true }, .float, .dimension_2d, false),
|
||||||
});
|
|
||||||
|
|
||||||
const texture_sampler = opt.texture_sampler orelse device.createSampler(&.{
|
|
||||||
.mag_filter = .nearest,
|
|
||||||
.min_filter = .nearest,
|
|
||||||
});
|
|
||||||
const uniforms = device.createBuffer(&.{
|
|
||||||
.usage = .{ .copy_dst = true, .uniform = true },
|
|
||||||
.size = @sizeOf(Uniforms),
|
|
||||||
.mapped_at_creation = .false,
|
|
||||||
});
|
|
||||||
const bind_group_layout = opt.bind_group_layout orelse device.createBindGroupLayout(
|
|
||||||
&gpu.BindGroupLayout.Descriptor.init(.{
|
|
||||||
.entries = &.{
|
|
||||||
gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, false, 0),
|
|
||||||
gpu.BindGroupLayout.Entry.buffer(1, .{ .vertex = true }, .read_only_storage, false, 0),
|
|
||||||
gpu.BindGroupLayout.Entry.buffer(2, .{ .vertex = true }, .read_only_storage, false, 0),
|
|
||||||
gpu.BindGroupLayout.Entry.buffer(3, .{ .vertex = true }, .read_only_storage, false, 0),
|
|
||||||
gpu.BindGroupLayout.Entry.sampler(4, .{ .fragment = true }, .filtering),
|
|
||||||
gpu.BindGroupLayout.Entry.texture(5, .{ .fragment = true }, .float, .dimension_2d, false),
|
|
||||||
gpu.BindGroupLayout.Entry.texture(6, .{ .fragment = true }, .float, .dimension_2d, false),
|
|
||||||
gpu.BindGroupLayout.Entry.texture(7, .{ .fragment = true }, .float, .dimension_2d, false),
|
|
||||||
gpu.BindGroupLayout.Entry.texture(8, .{ .fragment = true }, .float, .dimension_2d, false),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
defer bind_group_layout.release();
|
|
||||||
|
|
||||||
const texture_view = opt.texture.createView(&gpu.TextureView.Descriptor{});
|
|
||||||
const texture2_view = if (opt.texture2) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
|
||||||
const texture3_view = if (opt.texture3) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
|
||||||
const texture4_view = if (opt.texture4) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
|
||||||
defer texture_view.release();
|
|
||||||
defer texture2_view.release();
|
|
||||||
defer texture3_view.release();
|
|
||||||
defer texture4_view.release();
|
|
||||||
|
|
||||||
const bind_group = opt.bind_group orelse device.createBindGroup(
|
|
||||||
&gpu.BindGroup.Descriptor.init(.{
|
|
||||||
.layout = bind_group_layout,
|
|
||||||
.entries = &.{
|
|
||||||
gpu.BindGroup.Entry.buffer(0, uniforms, 0, @sizeOf(Uniforms)),
|
|
||||||
gpu.BindGroup.Entry.buffer(1, transforms, 0, @sizeOf(Mat4x4) * sprite_buffer_cap),
|
|
||||||
gpu.BindGroup.Entry.buffer(2, uv_transforms, 0, @sizeOf(Mat3x3) * sprite_buffer_cap),
|
|
||||||
gpu.BindGroup.Entry.buffer(3, sizes, 0, @sizeOf(Vec2) * sprite_buffer_cap),
|
|
||||||
gpu.BindGroup.Entry.sampler(4, texture_sampler),
|
|
||||||
gpu.BindGroup.Entry.textureView(5, texture_view),
|
|
||||||
gpu.BindGroup.Entry.textureView(6, texture2_view),
|
|
||||||
gpu.BindGroup.Entry.textureView(7, texture3_view),
|
|
||||||
gpu.BindGroup.Entry.textureView(8, texture4_view),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const blend_state = opt.blend_state orelse gpu.BlendState{
|
|
||||||
.color = .{
|
|
||||||
.operation = .add,
|
|
||||||
.src_factor = .src_alpha,
|
|
||||||
.dst_factor = .one_minus_src_alpha,
|
|
||||||
},
|
},
|
||||||
.alpha = .{
|
}),
|
||||||
.operation = .add,
|
);
|
||||||
.src_factor = .one,
|
defer bind_group_layout.release();
|
||||||
.dst_factor = .zero,
|
|
||||||
|
const texture_view = opt.texture.createView(&gpu.TextureView.Descriptor{});
|
||||||
|
const texture2_view = if (opt.texture2) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
||||||
|
const texture3_view = if (opt.texture3) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
||||||
|
const texture4_view = if (opt.texture4) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
||||||
|
defer texture_view.release();
|
||||||
|
defer texture2_view.release();
|
||||||
|
defer texture3_view.release();
|
||||||
|
defer texture4_view.release();
|
||||||
|
|
||||||
|
const bind_group = opt.bind_group orelse device.createBindGroup(
|
||||||
|
&gpu.BindGroup.Descriptor.init(.{
|
||||||
|
.layout = bind_group_layout,
|
||||||
|
.entries = &.{
|
||||||
|
gpu.BindGroup.Entry.buffer(0, uniforms, 0, @sizeOf(Uniforms)),
|
||||||
|
gpu.BindGroup.Entry.buffer(1, transforms, 0, @sizeOf(Mat4x4) * sprite_buffer_cap),
|
||||||
|
gpu.BindGroup.Entry.buffer(2, uv_transforms, 0, @sizeOf(Mat3x3) * sprite_buffer_cap),
|
||||||
|
gpu.BindGroup.Entry.buffer(3, sizes, 0, @sizeOf(Vec2) * sprite_buffer_cap),
|
||||||
|
gpu.BindGroup.Entry.sampler(4, texture_sampler),
|
||||||
|
gpu.BindGroup.Entry.textureView(5, texture_view),
|
||||||
|
gpu.BindGroup.Entry.textureView(6, texture2_view),
|
||||||
|
gpu.BindGroup.Entry.textureView(7, texture3_view),
|
||||||
|
gpu.BindGroup.Entry.textureView(8, texture4_view),
|
||||||
},
|
},
|
||||||
};
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
const shader_module = opt.shader orelse device.createShaderModuleWGSL("sprite.wgsl", @embedFile("sprite.wgsl"));
|
const blend_state = opt.blend_state orelse gpu.BlendState{
|
||||||
defer shader_module.release();
|
.color = .{
|
||||||
|
.operation = .add,
|
||||||
|
.src_factor = .src_alpha,
|
||||||
|
.dst_factor = .one_minus_src_alpha,
|
||||||
|
},
|
||||||
|
.alpha = .{
|
||||||
|
.operation = .add,
|
||||||
|
.src_factor = .one,
|
||||||
|
.dst_factor = .zero,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const color_target = opt.color_target_state orelse gpu.ColorTargetState{
|
const shader_module = opt.shader orelse device.createShaderModuleWGSL("sprite.wgsl", @embedFile("sprite.wgsl"));
|
||||||
.format = core.descriptor.format,
|
defer shader_module.release();
|
||||||
.blend = &blend_state,
|
|
||||||
.write_mask = gpu.ColorWriteMaskFlags.all,
|
const color_target = opt.color_target_state orelse gpu.ColorTargetState{
|
||||||
};
|
.format = core.descriptor.format,
|
||||||
const fragment = opt.fragment_state orelse gpu.FragmentState.init(.{
|
.blend = &blend_state,
|
||||||
|
.write_mask = gpu.ColorWriteMaskFlags.all,
|
||||||
|
};
|
||||||
|
const fragment = opt.fragment_state orelse gpu.FragmentState.init(.{
|
||||||
|
.module = shader_module,
|
||||||
|
.entry_point = "fragMain",
|
||||||
|
.targets = &.{color_target},
|
||||||
|
});
|
||||||
|
|
||||||
|
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
|
||||||
|
const pipeline_layout = opt.pipeline_layout orelse device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
|
||||||
|
.bind_group_layouts = &bind_group_layouts,
|
||||||
|
}));
|
||||||
|
defer pipeline_layout.release();
|
||||||
|
const render_pipeline = device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
|
||||||
|
.fragment = &fragment,
|
||||||
|
.layout = pipeline_layout,
|
||||||
|
.vertex = gpu.VertexState{
|
||||||
.module = shader_module,
|
.module = shader_module,
|
||||||
.entry_point = "fragMain",
|
.entry_point = "vertMain",
|
||||||
.targets = &.{color_target},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
|
pipeline.value_ptr.* = Pipeline{
|
||||||
const pipeline_layout = opt.pipeline_layout orelse device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
|
.render = render_pipeline,
|
||||||
.bind_group_layouts = &bind_group_layouts,
|
.texture_sampler = texture_sampler,
|
||||||
}));
|
.texture = opt.texture,
|
||||||
defer pipeline_layout.release();
|
.texture2 = opt.texture2,
|
||||||
const render_pipeline = device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
|
.texture3 = opt.texture3,
|
||||||
.fragment = &fragment,
|
.texture4 = opt.texture4,
|
||||||
.layout = pipeline_layout,
|
.bind_group = bind_group,
|
||||||
.vertex = gpu.VertexState{
|
.uniforms = uniforms,
|
||||||
.module = shader_module,
|
.num_sprites = 0,
|
||||||
.entry_point = "vertMain",
|
.transforms = transforms,
|
||||||
},
|
.uv_transforms = uv_transforms,
|
||||||
});
|
.sizes = sizes,
|
||||||
|
};
|
||||||
|
pipeline.value_ptr.reference();
|
||||||
|
}
|
||||||
|
|
||||||
pipeline.value_ptr.* = Pipeline{
|
fn updated(
|
||||||
.render = render_pipeline,
|
engine: *Engine.Mod,
|
||||||
.texture_sampler = texture_sampler,
|
sprite_mod: *Mod,
|
||||||
.texture = opt.texture,
|
pipeline_id: u32,
|
||||||
.texture2 = opt.texture2,
|
) !void {
|
||||||
.texture3 = opt.texture3,
|
const pipeline = sprite_mod.state.pipelines.getPtr(pipeline_id).?;
|
||||||
.texture4 = opt.texture4,
|
const device = engine.state.device;
|
||||||
.bind_group = bind_group,
|
|
||||||
.uniforms = uniforms,
|
// TODO: make sure these entities only belong to the given pipeline
|
||||||
.num_sprites = 0,
|
// we need a better tagging mechanism
|
||||||
.transforms = transforms,
|
var archetypes_iter = engine.entities.query(.{ .all = &.{
|
||||||
.uv_transforms = uv_transforms,
|
.{ .mach_gfx_sprite = &.{
|
||||||
.sizes = sizes,
|
.uv_transform,
|
||||||
};
|
.transform,
|
||||||
pipeline.value_ptr.reference();
|
.size,
|
||||||
|
.pipeline,
|
||||||
|
} },
|
||||||
|
} });
|
||||||
|
|
||||||
|
const encoder = device.createCommandEncoder(null);
|
||||||
|
defer encoder.release();
|
||||||
|
|
||||||
|
pipeline.num_sprites = 0;
|
||||||
|
var transforms_offset: usize = 0;
|
||||||
|
var uv_transforms_offset: usize = 0;
|
||||||
|
var sizes_offset: usize = 0;
|
||||||
|
while (archetypes_iter.next()) |archetype| {
|
||||||
|
const transforms = archetype.slice(.mach_gfx_sprite, .transform);
|
||||||
|
const uv_transforms = archetype.slice(.mach_gfx_sprite, .uv_transform);
|
||||||
|
const sizes = archetype.slice(.mach_gfx_sprite, .size);
|
||||||
|
|
||||||
|
// TODO: confirm the lifetime of these slices is OK for writeBuffer, how long do they need
|
||||||
|
// to live?
|
||||||
|
encoder.writeBuffer(pipeline.transforms, transforms_offset, transforms);
|
||||||
|
encoder.writeBuffer(pipeline.uv_transforms, uv_transforms_offset, uv_transforms);
|
||||||
|
encoder.writeBuffer(pipeline.sizes, sizes_offset, sizes);
|
||||||
|
|
||||||
|
transforms_offset += transforms.len;
|
||||||
|
uv_transforms_offset += uv_transforms.len;
|
||||||
|
sizes_offset += sizes.len;
|
||||||
|
pipeline.num_sprites += @intCast(transforms.len);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn updated(
|
var command = encoder.finish(null);
|
||||||
engine: *Engine.Mod,
|
defer command.release();
|
||||||
sprite_mod: *Mod,
|
|
||||||
pipeline_id: u32,
|
|
||||||
) !void {
|
|
||||||
const pipeline = sprite_mod.state.pipelines.getPtr(pipeline_id).?;
|
|
||||||
const device = engine.state.device;
|
|
||||||
|
|
||||||
// TODO: make sure these entities only belong to the given pipeline
|
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||||
// we need a better tagging mechanism
|
}
|
||||||
var archetypes_iter = engine.entities.query(.{ .all = &.{
|
|
||||||
.{ .mach_gfx_sprite = &.{
|
|
||||||
.uv_transform,
|
|
||||||
.transform,
|
|
||||||
.size,
|
|
||||||
.pipeline,
|
|
||||||
} },
|
|
||||||
} });
|
|
||||||
|
|
||||||
const encoder = device.createCommandEncoder(null);
|
fn preRender(
|
||||||
defer encoder.release();
|
engine: *Engine.Mod,
|
||||||
|
sprite_mod: *Mod,
|
||||||
|
pipeline_id: u32,
|
||||||
|
) !void {
|
||||||
|
const pipeline = sprite_mod.state.pipelines.get(pipeline_id).?;
|
||||||
|
|
||||||
pipeline.num_sprites = 0;
|
// Update uniform buffer
|
||||||
var transforms_offset: usize = 0;
|
const proj = Mat4x4.projection2D(.{
|
||||||
var uv_transforms_offset: usize = 0;
|
.left = -@as(f32, @floatFromInt(core.size().width)) / 2,
|
||||||
var sizes_offset: usize = 0;
|
.right = @as(f32, @floatFromInt(core.size().width)) / 2,
|
||||||
while (archetypes_iter.next()) |archetype| {
|
.bottom = -@as(f32, @floatFromInt(core.size().height)) / 2,
|
||||||
const transforms = archetype.slice(.mach_gfx_sprite, .transform);
|
.top = @as(f32, @floatFromInt(core.size().height)) / 2,
|
||||||
const uv_transforms = archetype.slice(.mach_gfx_sprite, .uv_transform);
|
.near = -0.1,
|
||||||
const sizes = archetype.slice(.mach_gfx_sprite, .size);
|
.far = 100000,
|
||||||
|
});
|
||||||
|
const uniforms = Uniforms{
|
||||||
|
.view_projection = proj,
|
||||||
|
// TODO: dimensions of other textures, number of textures present
|
||||||
|
.texture_size = vec2(
|
||||||
|
@as(f32, @floatFromInt(pipeline.texture.getWidth())),
|
||||||
|
@as(f32, @floatFromInt(pipeline.texture.getHeight())),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: confirm the lifetime of these slices is OK for writeBuffer, how long do they need
|
engine.state.encoder.writeBuffer(pipeline.uniforms, 0, &[_]Uniforms{uniforms});
|
||||||
// to live?
|
}
|
||||||
encoder.writeBuffer(pipeline.transforms, transforms_offset, transforms);
|
|
||||||
encoder.writeBuffer(pipeline.uv_transforms, uv_transforms_offset, uv_transforms);
|
|
||||||
encoder.writeBuffer(pipeline.sizes, sizes_offset, sizes);
|
|
||||||
|
|
||||||
transforms_offset += transforms.len;
|
fn render(
|
||||||
uv_transforms_offset += uv_transforms.len;
|
engine: *Engine.Mod,
|
||||||
sizes_offset += sizes.len;
|
sprite_mod: *Mod,
|
||||||
pipeline.num_sprites += @intCast(transforms.len);
|
pipeline_id: u32,
|
||||||
}
|
) !void {
|
||||||
|
const pipeline = sprite_mod.state.pipelines.get(pipeline_id).?;
|
||||||
|
|
||||||
var command = encoder.finish(null);
|
// Draw the sprite batch
|
||||||
defer command.release();
|
const pass = engine.state.pass;
|
||||||
|
const total_vertices = pipeline.num_sprites * 6;
|
||||||
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command});
|
pass.setPipeline(pipeline.render);
|
||||||
}
|
// TODO: remove dynamic offsets?
|
||||||
|
pass.setBindGroup(0, pipeline.bind_group, &.{});
|
||||||
pub fn preRender(
|
pass.draw(total_vertices, 1, 0, 0);
|
||||||
engine: *Engine.Mod,
|
}
|
||||||
sprite_mod: *Mod,
|
|
||||||
pipeline_id: u32,
|
|
||||||
) !void {
|
|
||||||
const pipeline = sprite_mod.state.pipelines.get(pipeline_id).?;
|
|
||||||
|
|
||||||
// Update uniform buffer
|
|
||||||
const proj = Mat4x4.projection2D(.{
|
|
||||||
.left = -@as(f32, @floatFromInt(core.size().width)) / 2,
|
|
||||||
.right = @as(f32, @floatFromInt(core.size().width)) / 2,
|
|
||||||
.bottom = -@as(f32, @floatFromInt(core.size().height)) / 2,
|
|
||||||
.top = @as(f32, @floatFromInt(core.size().height)) / 2,
|
|
||||||
.near = -0.1,
|
|
||||||
.far = 100000,
|
|
||||||
});
|
|
||||||
const uniforms = Uniforms{
|
|
||||||
.view_projection = proj,
|
|
||||||
// TODO: dimensions of other textures, number of textures present
|
|
||||||
.texture_size = vec2(
|
|
||||||
@as(f32, @floatFromInt(pipeline.texture.getWidth())),
|
|
||||||
@as(f32, @floatFromInt(pipeline.texture.getHeight())),
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
engine.state.encoder.writeBuffer(pipeline.uniforms, 0, &[_]Uniforms{uniforms});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn render(
|
|
||||||
engine: *Engine.Mod,
|
|
||||||
sprite_mod: *Mod,
|
|
||||||
pipeline_id: u32,
|
|
||||||
) !void {
|
|
||||||
const pipeline = sprite_mod.state.pipelines.get(pipeline_id).?;
|
|
||||||
|
|
||||||
// Draw the sprite batch
|
|
||||||
const pass = engine.state.pass;
|
|
||||||
const total_vertices = pipeline.num_sprites * 6;
|
|
||||||
pass.setPipeline(pipeline.render);
|
|
||||||
// TODO: remove dynamic offsets?
|
|
||||||
pass.setBindGroup(0, pipeline.bind_group, &.{});
|
|
||||||
pass.draw(total_vertices, 1, 0, 0);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
|
||||||
679
src/gfx/Text.zig
679
src/gfx/Text.zig
|
|
@ -64,6 +64,15 @@ pub const components = struct {
|
||||||
pub const color = Vec4; // e.g. vec4(0, 0, 0, 1.0),
|
pub const color = Vec4; // e.g. vec4(0, 0, 0, 1.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .deinit, .handler = deinit },
|
||||||
|
.{ .local = .init, .handler = init },
|
||||||
|
.{ .local = .initPipeline, .handler = initPipeline },
|
||||||
|
.{ .local = .updated, .handler = updated },
|
||||||
|
.{ .local = .preRender, .handler = preRender },
|
||||||
|
.{ .local = .render, .handler = render },
|
||||||
|
};
|
||||||
|
|
||||||
const Uniforms = extern struct {
|
const Uniforms = extern struct {
|
||||||
// WebGPU requires that the size of struct fields are multiples of 16
|
// WebGPU requires that the size of struct fields are multiples of 16
|
||||||
// So we use align(16) and 'extern' to maintain field order
|
// So we use align(16) and 'extern' to maintain field order
|
||||||
|
|
@ -171,383 +180,381 @@ pub const PipelineOptions = struct {
|
||||||
pipeline_layout: ?*gpu.PipelineLayout = null,
|
pipeline_layout: ?*gpu.PipelineLayout = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn deinit(text_mod: *Mod) !void {
|
fn deinit(text_mod: *Mod) !void {
|
||||||
for (text_mod.state.pipelines.entries.items(.value)) |*pipeline| pipeline.deinit(text_mod.allocator);
|
for (text_mod.state.pipelines.entries.items(.value)) |*pipeline| pipeline.deinit(text_mod.allocator);
|
||||||
text_mod.state.pipelines.deinit(text_mod.allocator);
|
text_mod.state.pipelines.deinit(text_mod.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const local = struct {
|
fn init(
|
||||||
pub fn init(
|
text_mod: *Mod,
|
||||||
text_mod: *Mod,
|
) !void {
|
||||||
) !void {
|
text_mod.state = .{
|
||||||
text_mod.state = .{
|
// TODO: struct default value initializers don't work
|
||||||
// TODO: struct default value initializers don't work
|
.pipelines = .{},
|
||||||
.pipelines = .{},
|
};
|
||||||
};
|
}
|
||||||
|
|
||||||
|
fn initPipeline(
|
||||||
|
engine: *Engine.Mod,
|
||||||
|
text_mod: *Mod,
|
||||||
|
opt: PipelineOptions,
|
||||||
|
) !void {
|
||||||
|
const device = engine.state.device;
|
||||||
|
|
||||||
|
const pipeline = try text_mod.state.pipelines.getOrPut(engine.allocator, opt.pipeline);
|
||||||
|
if (pipeline.found_existing) {
|
||||||
|
pipeline.value_ptr.*.deinit(engine.allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initPipeline(
|
// Prepare texture for the font atlas.
|
||||||
engine: *Engine.Mod,
|
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
|
||||||
text_mod: *Mod,
|
const texture = device.createTexture(&.{
|
||||||
opt: PipelineOptions,
|
.size = img_size,
|
||||||
) !void {
|
.format = .rgba8_unorm,
|
||||||
const device = engine.state.device;
|
.usage = .{
|
||||||
|
.texture_binding = true,
|
||||||
|
.copy_dst = true,
|
||||||
|
.render_attachment = true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const texture_atlas = try gfx.Atlas.init(
|
||||||
|
engine.allocator,
|
||||||
|
img_size.width,
|
||||||
|
.rgba,
|
||||||
|
);
|
||||||
|
|
||||||
const pipeline = try text_mod.state.pipelines.getOrPut(engine.allocator, opt.pipeline);
|
// Storage buffers
|
||||||
if (pipeline.found_existing) {
|
const buffer_cap = 1024 * 128; // TODO: allow user to specify preallocation
|
||||||
pipeline.value_ptr.*.deinit(engine.allocator);
|
const glyph_buffer_cap = 1024 * 512; // TODO: allow user to specify preallocation
|
||||||
}
|
const transforms = device.createBuffer(&.{
|
||||||
|
.usage = .{ .storage = true, .copy_dst = true },
|
||||||
|
.size = @sizeOf(Mat4x4) * buffer_cap,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
const colors = device.createBuffer(&.{
|
||||||
|
.usage = .{ .storage = true, .copy_dst = true },
|
||||||
|
.size = @sizeOf(Vec4) * buffer_cap,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
const glyphs = device.createBuffer(&.{
|
||||||
|
.usage = .{ .storage = true, .copy_dst = true },
|
||||||
|
.size = @sizeOf(Glyph) * glyph_buffer_cap,
|
||||||
|
.mapped_at_creation = .false,
|
||||||
|
});
|
||||||
|
|
||||||
// Prepare texture for the font atlas.
|
const texture_sampler = opt.texture_sampler orelse device.createSampler(&.{
|
||||||
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
|
.mag_filter = .nearest,
|
||||||
const texture = device.createTexture(&.{
|
.min_filter = .nearest,
|
||||||
.size = img_size,
|
});
|
||||||
.format = .rgba8_unorm,
|
const uniforms = device.createBuffer(&.{
|
||||||
.usage = .{
|
.usage = .{ .copy_dst = true, .uniform = true },
|
||||||
.texture_binding = true,
|
.size = @sizeOf(Uniforms),
|
||||||
.copy_dst = true,
|
.mapped_at_creation = .false,
|
||||||
.render_attachment = true,
|
});
|
||||||
|
const bind_group_layout = opt.bind_group_layout orelse device.createBindGroupLayout(
|
||||||
|
&gpu.BindGroupLayout.Descriptor.init(.{
|
||||||
|
.entries = &.{
|
||||||
|
gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, false, 0),
|
||||||
|
gpu.BindGroupLayout.Entry.buffer(1, .{ .vertex = true }, .read_only_storage, false, 0),
|
||||||
|
gpu.BindGroupLayout.Entry.buffer(2, .{ .vertex = true }, .read_only_storage, false, 0),
|
||||||
|
gpu.BindGroupLayout.Entry.buffer(3, .{ .vertex = true }, .read_only_storage, false, 0),
|
||||||
|
gpu.BindGroupLayout.Entry.sampler(4, .{ .fragment = true }, .filtering),
|
||||||
|
gpu.BindGroupLayout.Entry.texture(5, .{ .fragment = true }, .float, .dimension_2d, false),
|
||||||
|
gpu.BindGroupLayout.Entry.texture(6, .{ .fragment = true }, .float, .dimension_2d, false),
|
||||||
|
gpu.BindGroupLayout.Entry.texture(7, .{ .fragment = true }, .float, .dimension_2d, false),
|
||||||
|
gpu.BindGroupLayout.Entry.texture(8, .{ .fragment = true }, .float, .dimension_2d, false),
|
||||||
},
|
},
|
||||||
});
|
}),
|
||||||
const texture_atlas = try gfx.Atlas.init(
|
);
|
||||||
engine.allocator,
|
defer bind_group_layout.release();
|
||||||
img_size.width,
|
|
||||||
.rgba,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Storage buffers
|
const texture_view = texture.createView(&gpu.TextureView.Descriptor{});
|
||||||
const buffer_cap = 1024 * 128; // TODO: allow user to specify preallocation
|
const texture2_view = if (opt.texture2) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
||||||
const glyph_buffer_cap = 1024 * 512; // TODO: allow user to specify preallocation
|
const texture3_view = if (opt.texture3) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
||||||
const transforms = device.createBuffer(&.{
|
const texture4_view = if (opt.texture4) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
||||||
.usage = .{ .storage = true, .copy_dst = true },
|
defer texture_view.release();
|
||||||
.size = @sizeOf(Mat4x4) * buffer_cap,
|
defer texture2_view.release();
|
||||||
.mapped_at_creation = .false,
|
defer texture3_view.release();
|
||||||
});
|
defer texture4_view.release();
|
||||||
const colors = device.createBuffer(&.{
|
|
||||||
.usage = .{ .storage = true, .copy_dst = true },
|
|
||||||
.size = @sizeOf(Vec4) * buffer_cap,
|
|
||||||
.mapped_at_creation = .false,
|
|
||||||
});
|
|
||||||
const glyphs = device.createBuffer(&.{
|
|
||||||
.usage = .{ .storage = true, .copy_dst = true },
|
|
||||||
.size = @sizeOf(Glyph) * glyph_buffer_cap,
|
|
||||||
.mapped_at_creation = .false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const texture_sampler = opt.texture_sampler orelse device.createSampler(&.{
|
const bind_group = opt.bind_group orelse device.createBindGroup(
|
||||||
.mag_filter = .nearest,
|
&gpu.BindGroup.Descriptor.init(.{
|
||||||
.min_filter = .nearest,
|
.layout = bind_group_layout,
|
||||||
});
|
.entries = &.{
|
||||||
const uniforms = device.createBuffer(&.{
|
gpu.BindGroup.Entry.buffer(0, uniforms, 0, @sizeOf(Uniforms)),
|
||||||
.usage = .{ .copy_dst = true, .uniform = true },
|
gpu.BindGroup.Entry.buffer(1, transforms, 0, @sizeOf(Mat4x4) * buffer_cap),
|
||||||
.size = @sizeOf(Uniforms),
|
gpu.BindGroup.Entry.buffer(2, colors, 0, @sizeOf(Vec4) * buffer_cap),
|
||||||
.mapped_at_creation = .false,
|
gpu.BindGroup.Entry.buffer(3, glyphs, 0, @sizeOf(Glyph) * glyph_buffer_cap),
|
||||||
});
|
gpu.BindGroup.Entry.sampler(4, texture_sampler),
|
||||||
const bind_group_layout = opt.bind_group_layout orelse device.createBindGroupLayout(
|
gpu.BindGroup.Entry.textureView(5, texture_view),
|
||||||
&gpu.BindGroupLayout.Descriptor.init(.{
|
gpu.BindGroup.Entry.textureView(6, texture2_view),
|
||||||
.entries = &.{
|
gpu.BindGroup.Entry.textureView(7, texture3_view),
|
||||||
gpu.BindGroupLayout.Entry.buffer(0, .{ .vertex = true }, .uniform, false, 0),
|
gpu.BindGroup.Entry.textureView(8, texture4_view),
|
||||||
gpu.BindGroupLayout.Entry.buffer(1, .{ .vertex = true }, .read_only_storage, false, 0),
|
|
||||||
gpu.BindGroupLayout.Entry.buffer(2, .{ .vertex = true }, .read_only_storage, false, 0),
|
|
||||||
gpu.BindGroupLayout.Entry.buffer(3, .{ .vertex = true }, .read_only_storage, false, 0),
|
|
||||||
gpu.BindGroupLayout.Entry.sampler(4, .{ .fragment = true }, .filtering),
|
|
||||||
gpu.BindGroupLayout.Entry.texture(5, .{ .fragment = true }, .float, .dimension_2d, false),
|
|
||||||
gpu.BindGroupLayout.Entry.texture(6, .{ .fragment = true }, .float, .dimension_2d, false),
|
|
||||||
gpu.BindGroupLayout.Entry.texture(7, .{ .fragment = true }, .float, .dimension_2d, false),
|
|
||||||
gpu.BindGroupLayout.Entry.texture(8, .{ .fragment = true }, .float, .dimension_2d, false),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
defer bind_group_layout.release();
|
|
||||||
|
|
||||||
const texture_view = texture.createView(&gpu.TextureView.Descriptor{});
|
|
||||||
const texture2_view = if (opt.texture2) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
|
||||||
const texture3_view = if (opt.texture3) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
|
||||||
const texture4_view = if (opt.texture4) |tex| tex.createView(&gpu.TextureView.Descriptor{}) else texture_view;
|
|
||||||
defer texture_view.release();
|
|
||||||
defer texture2_view.release();
|
|
||||||
defer texture3_view.release();
|
|
||||||
defer texture4_view.release();
|
|
||||||
|
|
||||||
const bind_group = opt.bind_group orelse device.createBindGroup(
|
|
||||||
&gpu.BindGroup.Descriptor.init(.{
|
|
||||||
.layout = bind_group_layout,
|
|
||||||
.entries = &.{
|
|
||||||
gpu.BindGroup.Entry.buffer(0, uniforms, 0, @sizeOf(Uniforms)),
|
|
||||||
gpu.BindGroup.Entry.buffer(1, transforms, 0, @sizeOf(Mat4x4) * buffer_cap),
|
|
||||||
gpu.BindGroup.Entry.buffer(2, colors, 0, @sizeOf(Vec4) * buffer_cap),
|
|
||||||
gpu.BindGroup.Entry.buffer(3, glyphs, 0, @sizeOf(Glyph) * glyph_buffer_cap),
|
|
||||||
gpu.BindGroup.Entry.sampler(4, texture_sampler),
|
|
||||||
gpu.BindGroup.Entry.textureView(5, texture_view),
|
|
||||||
gpu.BindGroup.Entry.textureView(6, texture2_view),
|
|
||||||
gpu.BindGroup.Entry.textureView(7, texture3_view),
|
|
||||||
gpu.BindGroup.Entry.textureView(8, texture4_view),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
const blend_state = opt.blend_state orelse gpu.BlendState{
|
|
||||||
.color = .{
|
|
||||||
.operation = .add,
|
|
||||||
.src_factor = .src_alpha,
|
|
||||||
.dst_factor = .one_minus_src_alpha,
|
|
||||||
},
|
},
|
||||||
.alpha = .{
|
}),
|
||||||
.operation = .add,
|
);
|
||||||
.src_factor = .one,
|
|
||||||
.dst_factor = .zero,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const shader_module = opt.shader orelse device.createShaderModuleWGSL("text.wgsl", @embedFile("text.wgsl"));
|
const blend_state = opt.blend_state orelse gpu.BlendState{
|
||||||
defer shader_module.release();
|
.color = .{
|
||||||
|
.operation = .add,
|
||||||
|
.src_factor = .src_alpha,
|
||||||
|
.dst_factor = .one_minus_src_alpha,
|
||||||
|
},
|
||||||
|
.alpha = .{
|
||||||
|
.operation = .add,
|
||||||
|
.src_factor = .one,
|
||||||
|
.dst_factor = .zero,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
const color_target = opt.color_target_state orelse gpu.ColorTargetState{
|
const shader_module = opt.shader orelse device.createShaderModuleWGSL("text.wgsl", @embedFile("text.wgsl"));
|
||||||
.format = core.descriptor.format,
|
defer shader_module.release();
|
||||||
.blend = &blend_state,
|
|
||||||
.write_mask = gpu.ColorWriteMaskFlags.all,
|
const color_target = opt.color_target_state orelse gpu.ColorTargetState{
|
||||||
};
|
.format = core.descriptor.format,
|
||||||
const fragment = opt.fragment_state orelse gpu.FragmentState.init(.{
|
.blend = &blend_state,
|
||||||
|
.write_mask = gpu.ColorWriteMaskFlags.all,
|
||||||
|
};
|
||||||
|
const fragment = opt.fragment_state orelse gpu.FragmentState.init(.{
|
||||||
|
.module = shader_module,
|
||||||
|
.entry_point = "fragMain",
|
||||||
|
.targets = &.{color_target},
|
||||||
|
});
|
||||||
|
|
||||||
|
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
|
||||||
|
const pipeline_layout = opt.pipeline_layout orelse device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
|
||||||
|
.bind_group_layouts = &bind_group_layouts,
|
||||||
|
}));
|
||||||
|
defer pipeline_layout.release();
|
||||||
|
const render_pipeline = device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
|
||||||
|
.fragment = &fragment,
|
||||||
|
.layout = pipeline_layout,
|
||||||
|
.vertex = gpu.VertexState{
|
||||||
.module = shader_module,
|
.module = shader_module,
|
||||||
.entry_point = "fragMain",
|
.entry_point = "vertMain",
|
||||||
.targets = &.{color_target},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const bind_group_layouts = [_]*gpu.BindGroupLayout{bind_group_layout};
|
pipeline.value_ptr.* = Pipeline{
|
||||||
const pipeline_layout = opt.pipeline_layout orelse device.createPipelineLayout(&gpu.PipelineLayout.Descriptor.init(.{
|
.render = render_pipeline,
|
||||||
.bind_group_layouts = &bind_group_layouts,
|
.texture_sampler = texture_sampler,
|
||||||
}));
|
.texture = texture,
|
||||||
defer pipeline_layout.release();
|
.texture_atlas = texture_atlas,
|
||||||
const render_pipeline = device.createRenderPipeline(&gpu.RenderPipeline.Descriptor{
|
.texture2 = opt.texture2,
|
||||||
.fragment = &fragment,
|
.texture3 = opt.texture3,
|
||||||
.layout = pipeline_layout,
|
.texture4 = opt.texture4,
|
||||||
.vertex = gpu.VertexState{
|
.bind_group = bind_group,
|
||||||
.module = shader_module,
|
.uniforms = uniforms,
|
||||||
.entry_point = "vertMain",
|
.num_texts = 0,
|
||||||
},
|
.num_glyphs = 0,
|
||||||
});
|
.transforms = transforms,
|
||||||
|
.colors = colors,
|
||||||
|
.glyphs = glyphs,
|
||||||
|
};
|
||||||
|
pipeline.value_ptr.reference();
|
||||||
|
}
|
||||||
|
|
||||||
pipeline.value_ptr.* = Pipeline{
|
fn updated(
|
||||||
.render = render_pipeline,
|
engine: *Engine.Mod,
|
||||||
.texture_sampler = texture_sampler,
|
text_mod: *Mod,
|
||||||
.texture = texture,
|
pipeline_id: u32,
|
||||||
.texture_atlas = texture_atlas,
|
) !void {
|
||||||
.texture2 = opt.texture2,
|
const pipeline = text_mod.state.pipelines.getPtr(pipeline_id).?;
|
||||||
.texture3 = opt.texture3,
|
const device = engine.state.device;
|
||||||
.texture4 = opt.texture4,
|
|
||||||
.bind_group = bind_group,
|
|
||||||
.uniforms = uniforms,
|
|
||||||
.num_texts = 0,
|
|
||||||
.num_glyphs = 0,
|
|
||||||
.transforms = transforms,
|
|
||||||
.colors = colors,
|
|
||||||
.glyphs = glyphs,
|
|
||||||
};
|
|
||||||
pipeline.value_ptr.reference();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updated(
|
// TODO: make sure these entities only belong to the given pipeline
|
||||||
engine: *Engine.Mod,
|
// we need a better tagging mechanism
|
||||||
text_mod: *Mod,
|
var archetypes_iter = engine.entities.query(.{ .all = &.{
|
||||||
pipeline_id: u32,
|
.{ .mach_gfx_text = &.{
|
||||||
) !void {
|
.pipeline,
|
||||||
const pipeline = text_mod.state.pipelines.getPtr(pipeline_id).?;
|
.transform,
|
||||||
const device = engine.state.device;
|
.text,
|
||||||
|
} },
|
||||||
|
} });
|
||||||
|
|
||||||
// TODO: make sure these entities only belong to the given pipeline
|
const encoder = device.createCommandEncoder(null);
|
||||||
// we need a better tagging mechanism
|
defer encoder.release();
|
||||||
var archetypes_iter = engine.entities.query(.{ .all = &.{
|
|
||||||
.{ .mach_gfx_text = &.{
|
|
||||||
.pipeline,
|
|
||||||
.transform,
|
|
||||||
.text,
|
|
||||||
} },
|
|
||||||
} });
|
|
||||||
|
|
||||||
const encoder = device.createCommandEncoder(null);
|
pipeline.num_texts = 0;
|
||||||
defer encoder.release();
|
pipeline.num_glyphs = 0;
|
||||||
|
var glyphs = std.ArrayListUnmanaged(Glyph){};
|
||||||
|
var transforms_offset: usize = 0;
|
||||||
|
var texture_update = false;
|
||||||
|
while (archetypes_iter.next()) |archetype| {
|
||||||
|
const transforms = archetype.slice(.mach_gfx_text, .transform);
|
||||||
|
|
||||||
pipeline.num_texts = 0;
|
// TODO: confirm the lifetime of these slices is OK for writeBuffer, how long do they need
|
||||||
pipeline.num_glyphs = 0;
|
// to live?
|
||||||
var glyphs = std.ArrayListUnmanaged(Glyph){};
|
encoder.writeBuffer(pipeline.transforms, transforms_offset, transforms);
|
||||||
var transforms_offset: usize = 0;
|
// encoder.writeBuffer(pipeline.colors, colors_offset, colors);
|
||||||
var texture_update = false;
|
|
||||||
while (archetypes_iter.next()) |archetype| {
|
|
||||||
const transforms = archetype.slice(.mach_gfx_text, .transform);
|
|
||||||
|
|
||||||
// TODO: confirm the lifetime of these slices is OK for writeBuffer, how long do they need
|
transforms_offset += transforms.len;
|
||||||
// to live?
|
// colors_offset += colors.len;
|
||||||
encoder.writeBuffer(pipeline.transforms, transforms_offset, transforms);
|
pipeline.num_texts += @intCast(transforms.len);
|
||||||
// encoder.writeBuffer(pipeline.colors, colors_offset, colors);
|
|
||||||
|
|
||||||
transforms_offset += transforms.len;
|
// Render texts
|
||||||
// colors_offset += colors.len;
|
// TODO: this is very expensive and shouldn't be done here, should be done only on detected
|
||||||
pipeline.num_texts += @intCast(transforms.len);
|
// text change.
|
||||||
|
const px_density = 2.0;
|
||||||
|
const segment_lists = archetype.slice(.mach_gfx_text, .text);
|
||||||
|
const style_lists = archetype.slice(.mach_gfx_text, .style);
|
||||||
|
for (segment_lists, style_lists) |segments, styles| {
|
||||||
|
var origin_x: f32 = 0.0;
|
||||||
|
var origin_y: f32 = 0.0;
|
||||||
|
|
||||||
// Render texts
|
for (segments, styles) |segment, style| {
|
||||||
// TODO: this is very expensive and shouldn't be done here, should be done only on detected
|
// Load a font
|
||||||
// text change.
|
const font_name = engine.entities.getComponent(style, .mach_gfx_text, .font_name).?;
|
||||||
const px_density = 2.0;
|
_ = font_name; // TODO: actually use font name
|
||||||
const segment_lists = archetype.slice(.mach_gfx_text, .text);
|
const font_bytes = @import("font-assets").fira_sans_regular_ttf;
|
||||||
const style_lists = archetype.slice(.mach_gfx_text, .style);
|
var font = try gfx.Font.initBytes(font_bytes);
|
||||||
for (segment_lists, style_lists) |segments, styles| {
|
defer font.deinit(engine.allocator);
|
||||||
var origin_x: f32 = 0.0;
|
|
||||||
var origin_y: f32 = 0.0;
|
|
||||||
|
|
||||||
for (segments, styles) |segment, style| {
|
const font_size = engine.entities.getComponent(style, .mach_gfx_text, .font_size).?;
|
||||||
// Load a font
|
const font_weight = engine.entities.getComponent(style, .mach_gfx_text, .font_weight);
|
||||||
const font_name = engine.entities.getComponent(style, .mach_gfx_text, .font_name).?;
|
const italic = engine.entities.getComponent(style, .mach_gfx_text, .italic);
|
||||||
_ = font_name; // TODO: actually use font name
|
const color = engine.entities.getComponent(style, .mach_gfx_text, .color);
|
||||||
const font_bytes = @import("font-assets").fira_sans_regular_ttf;
|
// TODO: actually apply these
|
||||||
var font = try gfx.Font.initBytes(font_bytes);
|
_ = font_weight;
|
||||||
defer font.deinit(engine.allocator);
|
_ = italic;
|
||||||
|
_ = color;
|
||||||
|
|
||||||
const font_size = engine.entities.getComponent(style, .mach_gfx_text, .font_size).?;
|
// Create a text shaper
|
||||||
const font_weight = engine.entities.getComponent(style, .mach_gfx_text, .font_weight);
|
var run = try gfx.TextRun.init();
|
||||||
const italic = engine.entities.getComponent(style, .mach_gfx_text, .italic);
|
run.font_size_px = font_size;
|
||||||
const color = engine.entities.getComponent(style, .mach_gfx_text, .color);
|
run.px_density = 2; // TODO
|
||||||
// TODO: actually apply these
|
|
||||||
_ = font_weight;
|
|
||||||
_ = italic;
|
|
||||||
_ = color;
|
|
||||||
|
|
||||||
// Create a text shaper
|
defer run.deinit();
|
||||||
var run = try gfx.TextRun.init();
|
|
||||||
run.font_size_px = font_size;
|
|
||||||
run.px_density = 2; // TODO
|
|
||||||
|
|
||||||
defer run.deinit();
|
run.addText(segment);
|
||||||
|
try font.shape(&run);
|
||||||
|
|
||||||
run.addText(segment);
|
while (run.next()) |glyph| {
|
||||||
try font.shape(&run);
|
const codepoint = segment[glyph.cluster];
|
||||||
|
// TODO: use flags(?) to detect newline, or at least something more reliable?
|
||||||
while (run.next()) |glyph| {
|
if (codepoint != '\n') {
|
||||||
const codepoint = segment[glyph.cluster];
|
const region = try pipeline.regions.getOrPut(engine.allocator, .{
|
||||||
// TODO: use flags(?) to detect newline, or at least something more reliable?
|
.index = glyph.glyph_index,
|
||||||
if (codepoint != '\n') {
|
.size = @bitCast(font_size),
|
||||||
const region = try pipeline.regions.getOrPut(engine.allocator, .{
|
});
|
||||||
.index = glyph.glyph_index,
|
if (!region.found_existing) {
|
||||||
.size = @bitCast(font_size),
|
const rendered_glyph = try font.render(engine.allocator, glyph.glyph_index, .{
|
||||||
|
.font_size_px = run.font_size_px,
|
||||||
});
|
});
|
||||||
if (!region.found_existing) {
|
if (rendered_glyph.bitmap) |bitmap| {
|
||||||
const rendered_glyph = try font.render(engine.allocator, glyph.glyph_index, .{
|
var glyph_atlas_region = try pipeline.texture_atlas.reserve(engine.allocator, rendered_glyph.width, rendered_glyph.height);
|
||||||
.font_size_px = run.font_size_px,
|
pipeline.texture_atlas.set(glyph_atlas_region, @as([*]const u8, @ptrCast(bitmap.ptr))[0 .. bitmap.len * 4]);
|
||||||
});
|
texture_update = true;
|
||||||
if (rendered_glyph.bitmap) |bitmap| {
|
|
||||||
var glyph_atlas_region = try pipeline.texture_atlas.reserve(engine.allocator, rendered_glyph.width, rendered_glyph.height);
|
|
||||||
pipeline.texture_atlas.set(glyph_atlas_region, @as([*]const u8, @ptrCast(bitmap.ptr))[0 .. bitmap.len * 4]);
|
|
||||||
texture_update = true;
|
|
||||||
|
|
||||||
// Exclude the 1px blank space margin when describing the region of the texture
|
// Exclude the 1px blank space margin when describing the region of the texture
|
||||||
// that actually represents the glyph.
|
// that actually represents the glyph.
|
||||||
const margin = 1;
|
const margin = 1;
|
||||||
glyph_atlas_region.x += margin;
|
glyph_atlas_region.x += margin;
|
||||||
glyph_atlas_region.y += margin;
|
glyph_atlas_region.y += margin;
|
||||||
glyph_atlas_region.width -= margin * 2;
|
glyph_atlas_region.width -= margin * 2;
|
||||||
glyph_atlas_region.height -= margin * 2;
|
glyph_atlas_region.height -= margin * 2;
|
||||||
region.value_ptr.* = glyph_atlas_region;
|
region.value_ptr.* = glyph_atlas_region;
|
||||||
} else {
|
} else {
|
||||||
// whitespace
|
// whitespace
|
||||||
region.value_ptr.* = gfx.Atlas.Region{
|
region.value_ptr.* = gfx.Atlas.Region{
|
||||||
.width = 0,
|
.width = 0,
|
||||||
.height = 0,
|
.height = 0,
|
||||||
.x = 0,
|
.x = 0,
|
||||||
.y = 0,
|
.y = 0,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const r = region.value_ptr.*;
|
|
||||||
const size = vec2(@floatFromInt(r.width), @floatFromInt(r.height));
|
|
||||||
try glyphs.append(engine.allocator, .{
|
|
||||||
.pos = vec2(
|
|
||||||
origin_x + glyph.offset.x(),
|
|
||||||
origin_y - (size.y() - glyph.offset.y()),
|
|
||||||
).divScalar(px_density),
|
|
||||||
.size = size.divScalar(px_density),
|
|
||||||
.text_index = 0,
|
|
||||||
.uv_pos = vec2(@floatFromInt(r.x), @floatFromInt(r.y)),
|
|
||||||
});
|
|
||||||
pipeline.num_glyphs += 1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (codepoint == '\n') {
|
const r = region.value_ptr.*;
|
||||||
origin_x = 0;
|
const size = vec2(@floatFromInt(r.width), @floatFromInt(r.height));
|
||||||
origin_y -= font_size;
|
try glyphs.append(engine.allocator, .{
|
||||||
} else {
|
.pos = vec2(
|
||||||
origin_x += glyph.advance.x();
|
origin_x + glyph.offset.x(),
|
||||||
}
|
origin_y - (size.y() - glyph.offset.y()),
|
||||||
|
).divScalar(px_density),
|
||||||
|
.size = size.divScalar(px_density),
|
||||||
|
.text_index = 0,
|
||||||
|
.uv_pos = vec2(@floatFromInt(r.x), @floatFromInt(r.y)),
|
||||||
|
});
|
||||||
|
pipeline.num_glyphs += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (codepoint == '\n') {
|
||||||
|
origin_x = 0;
|
||||||
|
origin_y -= font_size;
|
||||||
|
} else {
|
||||||
|
origin_x += glyph.advance.x();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: could writeBuffer check for zero?
|
|
||||||
if (glyphs.items.len > 0) encoder.writeBuffer(pipeline.glyphs, 0, glyphs.items);
|
|
||||||
defer glyphs.deinit(engine.allocator);
|
|
||||||
if (texture_update) {
|
|
||||||
// rgba32_pixels
|
|
||||||
// TODO: use proper texture dimensions here
|
|
||||||
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
|
|
||||||
const data_layout = gpu.Texture.DataLayout{
|
|
||||||
.bytes_per_row = @as(u32, @intCast(img_size.width * 4)),
|
|
||||||
.rows_per_image = @as(u32, @intCast(img_size.height)),
|
|
||||||
};
|
|
||||||
engine.state.queue.writeTexture(
|
|
||||||
&.{ .texture = pipeline.texture },
|
|
||||||
&data_layout,
|
|
||||||
&img_size,
|
|
||||||
pipeline.texture_atlas.data,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var command = encoder.finish(null);
|
|
||||||
defer command.release();
|
|
||||||
|
|
||||||
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preRender(
|
// TODO: could writeBuffer check for zero?
|
||||||
engine: *Engine.Mod,
|
if (glyphs.items.len > 0) encoder.writeBuffer(pipeline.glyphs, 0, glyphs.items);
|
||||||
text_mod: *Mod,
|
defer glyphs.deinit(engine.allocator);
|
||||||
pipeline_id: u32,
|
if (texture_update) {
|
||||||
) !void {
|
// rgba32_pixels
|
||||||
const pipeline = text_mod.state.pipelines.get(pipeline_id).?;
|
// TODO: use proper texture dimensions here
|
||||||
|
const img_size = gpu.Extent3D{ .width = 1024, .height = 1024 };
|
||||||
// Update uniform buffer
|
const data_layout = gpu.Texture.DataLayout{
|
||||||
const proj = Mat4x4.projection2D(.{
|
.bytes_per_row = @as(u32, @intCast(img_size.width * 4)),
|
||||||
.left = -@as(f32, @floatFromInt(core.size().width)) / 2,
|
.rows_per_image = @as(u32, @intCast(img_size.height)),
|
||||||
.right = @as(f32, @floatFromInt(core.size().width)) / 2,
|
|
||||||
.bottom = -@as(f32, @floatFromInt(core.size().height)) / 2,
|
|
||||||
.top = @as(f32, @floatFromInt(core.size().height)) / 2,
|
|
||||||
.near = -0.1,
|
|
||||||
.far = 100000,
|
|
||||||
});
|
|
||||||
const uniforms = Uniforms{
|
|
||||||
.view_projection = proj,
|
|
||||||
// TODO: dimensions of other textures, number of textures present
|
|
||||||
.texture_size = vec2(
|
|
||||||
@as(f32, @floatFromInt(pipeline.texture.getWidth())),
|
|
||||||
@as(f32, @floatFromInt(pipeline.texture.getHeight())),
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
|
engine.state.queue.writeTexture(
|
||||||
engine.state.encoder.writeBuffer(pipeline.uniforms, 0, &[_]Uniforms{uniforms});
|
&.{ .texture = pipeline.texture },
|
||||||
|
&data_layout,
|
||||||
|
&img_size,
|
||||||
|
pipeline.texture_atlas.data,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(
|
var command = encoder.finish(null);
|
||||||
engine: *Engine.Mod,
|
defer command.release();
|
||||||
text_mod: *Mod,
|
|
||||||
pipeline_id: u32,
|
|
||||||
) !void {
|
|
||||||
const pipeline = text_mod.state.pipelines.get(pipeline_id).?;
|
|
||||||
|
|
||||||
// Draw the text batch
|
engine.state.queue.submit(&[_]*gpu.CommandBuffer{command});
|
||||||
const pass = engine.state.pass;
|
}
|
||||||
const total_vertices = pipeline.num_glyphs * 6;
|
|
||||||
pass.setPipeline(pipeline.render);
|
fn preRender(
|
||||||
// TODO: remove dynamic offsets?
|
engine: *Engine.Mod,
|
||||||
pass.setBindGroup(0, pipeline.bind_group, &.{});
|
text_mod: *Mod,
|
||||||
pass.draw(total_vertices, 1, 0, 0);
|
pipeline_id: u32,
|
||||||
}
|
) !void {
|
||||||
};
|
const pipeline = text_mod.state.pipelines.get(pipeline_id).?;
|
||||||
|
|
||||||
|
// Update uniform buffer
|
||||||
|
const proj = Mat4x4.projection2D(.{
|
||||||
|
.left = -@as(f32, @floatFromInt(core.size().width)) / 2,
|
||||||
|
.right = @as(f32, @floatFromInt(core.size().width)) / 2,
|
||||||
|
.bottom = -@as(f32, @floatFromInt(core.size().height)) / 2,
|
||||||
|
.top = @as(f32, @floatFromInt(core.size().height)) / 2,
|
||||||
|
.near = -0.1,
|
||||||
|
.far = 100000,
|
||||||
|
});
|
||||||
|
const uniforms = Uniforms{
|
||||||
|
.view_projection = proj,
|
||||||
|
// TODO: dimensions of other textures, number of textures present
|
||||||
|
.texture_size = vec2(
|
||||||
|
@as(f32, @floatFromInt(pipeline.texture.getWidth())),
|
||||||
|
@as(f32, @floatFromInt(pipeline.texture.getHeight())),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
engine.state.encoder.writeBuffer(pipeline.uniforms, 0, &[_]Uniforms{uniforms});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(
|
||||||
|
engine: *Engine.Mod,
|
||||||
|
text_mod: *Mod,
|
||||||
|
pipeline_id: u32,
|
||||||
|
) !void {
|
||||||
|
const pipeline = text_mod.state.pipelines.get(pipeline_id).?;
|
||||||
|
|
||||||
|
// Draw the text batch
|
||||||
|
const pass = engine.state.pass;
|
||||||
|
const total_vertices = pipeline.num_glyphs * 6;
|
||||||
|
pass.setPipeline(pipeline.render);
|
||||||
|
// TODO: remove dynamic offsets?
|
||||||
|
pass.setBindGroup(0, pipeline.bind_group, &.{});
|
||||||
|
pass.draw(total_vertices, 1, 0, 0);
|
||||||
|
}
|
||||||
|
|
|
||||||
493
src/module.zig
493
src/module.zig
|
|
@ -2,17 +2,21 @@ const builtin = @import("builtin");
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const testing = @import("testing.zig");
|
const testing = @import("testing.zig");
|
||||||
|
|
||||||
/// Verifies that T matches the basic layout of a Mach module
|
/// Verifies that M matches the basic layout of a Mach module
|
||||||
pub fn Module(comptime T: type) type {
|
pub fn Module(comptime M: type) type {
|
||||||
if (@typeInfo(T) != .Struct) @compileError("Module must be a struct type. Found:" ++ @typeName(T));
|
if (@typeInfo(M) != .Struct) @compileError("mach: expected module struct, found: " ++ @typeName(M));
|
||||||
if (!@hasDecl(T, "name")) @compileError("Module must have `pub const name = .foobar;`");
|
if (!@hasDecl(M, "name")) @compileError("mach: module must have `pub const name = .foobar;`");
|
||||||
if (@typeInfo(@TypeOf(T.name)) != .EnumLiteral) @compileError("Module must have `pub const name = .foobar;`, found type:" ++ @typeName(T.name));
|
if (@typeInfo(@TypeOf(M.name)) != .EnumLiteral) @compileError("mach: module must have `pub const name = .foobar;`, found type:" ++ @typeName(M.name));
|
||||||
|
|
||||||
|
const prefix = "mach: module ." ++ @tagName(M.name) ++ " ";
|
||||||
|
if (!@hasDecl(M, "events")) @compileError(prefix ++ "must have `pub const events = .{};`");
|
||||||
|
validateEvents("mach: module ." ++ @tagName(M.name) ++ " ", M.events);
|
||||||
|
|
||||||
// TODO: move this to ecs
|
// TODO: move this to ecs
|
||||||
if (@hasDecl(T, "components")) {
|
if (@hasDecl(M, "components")) {
|
||||||
if (@typeInfo(T.components) != .Struct) @compileError("Module.components must be `pub const components = struct { ... };`, found type:" ++ @typeName(T.components));
|
if (@typeInfo(M.components) != .Struct) @compileError("Module.components must be `pub const components = struct { ... };`, found type:" ++ @typeName(M.components));
|
||||||
}
|
}
|
||||||
return T;
|
return M;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: implement serialization constraints
|
// TODO: implement serialization constraints
|
||||||
|
|
@ -65,49 +69,63 @@ pub fn Modules(comptime mods: anytype) type {
|
||||||
|
|
||||||
/// Returns an args tuple representing the standard, uninjected, arguments which the given
|
/// Returns an args tuple representing the standard, uninjected, arguments which the given
|
||||||
/// local event handler requires.
|
/// local event handler requires.
|
||||||
fn LocalArgs(module_name: ModuleName(mods), event_name: EventName(mods)) type {
|
fn LocalArgs(module_name: ModuleName(mods), event_name: LocalEventEnum(mods)) type {
|
||||||
inline for (modules) |M| {
|
inline for (modules) |M| {
|
||||||
|
_ = Module(M); // Validate the module
|
||||||
if (M.name != module_name) continue;
|
if (M.name != module_name) continue;
|
||||||
if (!@hasDecl(M, "local")) @compileError("Module " ++ @tagName(module_name) ++ " has no `pub const local = struct { ... };` event handlers");
|
inline for (M.events) |event| {
|
||||||
if (!@hasDecl(M.local, @tagName(event_name))) @compileError("Module " ++ @tagName(module_name) ++ ".local has no event handler named: " ++ @tagName(event_name));
|
const Ev = @TypeOf(event);
|
||||||
const handler = @field(M.local, @tagName(event_name));
|
const name_tag = if (@hasField(Ev, "local")) event.local else continue;
|
||||||
switch (@typeInfo(@TypeOf(handler))) {
|
if (name_tag != event_name) continue;
|
||||||
|
|
||||||
|
const Handler = switch (@typeInfo(@TypeOf(event.handler))) {
|
||||||
|
.Fn => @TypeOf(event.handler),
|
||||||
|
.Type => |t| switch (@typeInfo(t)) {
|
||||||
|
.Fn => event.handler,
|
||||||
|
else => unreachable,
|
||||||
|
},
|
||||||
|
else => unreachable,
|
||||||
|
};
|
||||||
|
|
||||||
// TODO: passing std.meta.Tuple here instead of TupleHACK results in a compiler
|
// TODO: passing std.meta.Tuple here instead of TupleHACK results in a compiler
|
||||||
// segfault. The only difference is that TupleHACk does not produce a real tuple,
|
// segfault. The only difference is that TupleHACk does not produce a real tuple,
|
||||||
// `@Type(.{.Struct = .{ .is_tuple = false }})` instead of `.is_tuple = true`.
|
// `@Type(.{.Struct = .{ .is_tuple = false }})` instead of `.is_tuple = true`.
|
||||||
.Fn => return UninjectedArgsTuple(TupleHACK, @TypeOf(handler)),
|
return UninjectedArgsTuple(TupleHACK, Handler);
|
||||||
// Note: This means the module does have some other field by the same name, but it is not a function.
|
|
||||||
// TODO: allow pre-declarations
|
|
||||||
else => @compileError("Module " ++ @tagName(module_name) ++ ".local." ++ @tagName(event_name) ++ " is not a function"),
|
|
||||||
}
|
}
|
||||||
|
@compileError("mach: module ." ++ @tagName(M.name) ++ " has no .local event handler for ." ++ @tagName(event_name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an args tuple representing the standard, uninjected, arguments which the given
|
/// Returns an args tuple representing the standard, uninjected, arguments which the given
|
||||||
/// global event handler requires.
|
/// global event handler requires.
|
||||||
fn Args(event_name: EventName(mods)) type {
|
fn Args(event_name: GlobalEventEnum(mods)) type {
|
||||||
inline for (modules) |M| {
|
inline for (modules) |M| {
|
||||||
// TODO: enforce any defined event handlers of the same name have the same argument types
|
_ = Module(M); // Validate the module
|
||||||
if (@hasDecl(M, @tagName(event_name))) {
|
|
||||||
const Handler = switch (@typeInfo(@TypeOf(@field(M, @tagName(event_name))))) {
|
inline for (M.events) |event| {
|
||||||
.Fn => @TypeOf(@field(M, @tagName(event_name))),
|
const Ev = @TypeOf(event);
|
||||||
.Type => switch (@typeInfo(@field(M, @tagName(event_name)))) {
|
const name_tag = if (@hasField(Ev, "global")) event.global else continue;
|
||||||
.Fn => @field(M, @tagName(event_name)),
|
if (name_tag != event_name) continue;
|
||||||
else => continue,
|
|
||||||
|
const Handler = switch (@typeInfo(@TypeOf(event.handler))) {
|
||||||
|
.Fn => @TypeOf(event.handler),
|
||||||
|
.Type => switch (@typeInfo(event.handler)) {
|
||||||
|
.Fn => event.handler,
|
||||||
|
else => unreachable,
|
||||||
},
|
},
|
||||||
else => continue,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
return UninjectedArgsTuple(std.meta.Tuple, Handler);
|
return UninjectedArgsTuple(std.meta.Tuple, Handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@compileError("No global event handler " ++ @tagName(event_name) ++ " is defined in any module.");
|
@compileError("No global event handler ." ++ @tagName(event_name) ++ " is defined in any module.");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Send a global event
|
/// Send a global event
|
||||||
pub fn send(
|
pub fn send(
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
// TODO: is a variant of this function where event_name is not comptime known, but asserted to be a valid enum, useful?
|
// TODO: is a variant of this function where event_name is not comptime known, but asserted to be a valid enum, useful?
|
||||||
comptime event_name: EventName(mods),
|
comptime event_name: GlobalEventEnum(mods),
|
||||||
args: Args(event_name),
|
args: Args(event_name),
|
||||||
) void {
|
) void {
|
||||||
// TODO: comptime safety/debugging
|
// TODO: comptime safety/debugging
|
||||||
|
|
@ -119,7 +137,7 @@ pub fn Modules(comptime mods: anytype) type {
|
||||||
m: *@This(),
|
m: *@This(),
|
||||||
// TODO: is a variant of this function where module_name/event_name is not comptime known, but asserted to be a valid enum, useful?
|
// TODO: is a variant of this function where module_name/event_name is not comptime known, but asserted to be a valid enum, useful?
|
||||||
comptime module_name: ModuleName(mods),
|
comptime module_name: ModuleName(mods),
|
||||||
comptime event_name: EventName(mods),
|
comptime event_name: LocalEventEnum(mods),
|
||||||
args: LocalArgs(module_name, event_name),
|
args: LocalArgs(module_name, event_name),
|
||||||
) void {
|
) void {
|
||||||
// TODO: comptime safety/debugging
|
// TODO: comptime safety/debugging
|
||||||
|
|
@ -187,23 +205,29 @@ pub fn Modules(comptime mods: anytype) type {
|
||||||
try @This().callLocal(@enumFromInt(module_name), @enumFromInt(ev.event_name), ev.args_slice, injectable);
|
try @This().callLocal(@enumFromInt(module_name), @enumFromInt(ev.event_name), ev.args_slice, injectable);
|
||||||
} else {
|
} else {
|
||||||
// TODO: dispatch arguments
|
// TODO: dispatch arguments
|
||||||
try @This().call(@enumFromInt(ev.event_name), ev.args_slice, injectable);
|
try @This().callGlobal(@enumFromInt(ev.event_name), ev.args_slice, injectable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call global event handler with the specified name in all modules
|
/// Call global event handler with the specified name in all modules
|
||||||
inline fn call(event_name: EventName(mods), args: []u8, injectable: anytype) !void {
|
inline fn callGlobal(event_name: GlobalEventEnum(mods), args: []u8, injectable: anytype) !void {
|
||||||
|
if (@typeInfo(@TypeOf(event_name)).Enum.fields.len == 0) return;
|
||||||
switch (event_name) {
|
switch (event_name) {
|
||||||
inline else => |name| {
|
inline else => |ev_name| {
|
||||||
inline for (modules) |M| {
|
inline for (modules) |M| {
|
||||||
if (@hasDecl(M, @tagName(name))) {
|
_ = Module(M); // Validate the module
|
||||||
switch (@typeInfo(@TypeOf(@field(M, @tagName(name))))) {
|
inline for (M.events) |event| {
|
||||||
.Fn => {
|
const Ev = @TypeOf(event);
|
||||||
const handler = @field(M, @tagName(name));
|
const name_tag = if (@hasField(Ev, "global")) event.global else continue;
|
||||||
try callHandler(handler, args, injectable);
|
if (name_tag != ev_name) continue;
|
||||||
|
switch (@typeInfo(@TypeOf(event.handler))) {
|
||||||
|
.Fn => try callHandler(event.handler, args, injectable),
|
||||||
|
.Type => switch (@typeInfo(event.handler)) {
|
||||||
|
.Fn => {}, // Pre-declaration of what args an event has, nothing to run.
|
||||||
|
else => unreachable,
|
||||||
},
|
},
|
||||||
else => {},
|
else => unreachable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -212,22 +236,29 @@ pub fn Modules(comptime mods: anytype) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call local event handler with the specified name in the specified module
|
/// Call local event handler with the specified name in the specified module
|
||||||
inline fn callLocal(module_name: ModuleName(mods), event_name: EventName(mods), args: []u8, injectable: anytype) !void {
|
inline fn callLocal(module_name: ModuleName(mods), event_name: LocalEventEnum(mods), args: []u8, injectable: anytype) !void {
|
||||||
|
if (@typeInfo(@TypeOf(event_name)).Enum.fields.len == 0) return;
|
||||||
// TODO: invert switch case for hypothetically better branch prediction
|
// TODO: invert switch case for hypothetically better branch prediction
|
||||||
switch (module_name) {
|
switch (module_name) {
|
||||||
inline else => |mod_name| {
|
inline else => |mod_name| {
|
||||||
switch (event_name) {
|
switch (event_name) {
|
||||||
inline else => |ev_name| {
|
inline else => |ev_name| {
|
||||||
const M = @field(NamespacedModules(@This().modules){}, @tagName(mod_name));
|
const M = @field(NamespacedModules(@This().modules){}, @tagName(mod_name));
|
||||||
// TODO: no need for hasDecl, assertion should be event can be sent at send() time.
|
_ = Module(M); // Validate the module
|
||||||
if (@hasDecl(M, "local") and @hasDecl(M.local, @tagName(ev_name))) {
|
|
||||||
const handler = @field(M.local, @tagName(ev_name));
|
inline for (M.events) |event| {
|
||||||
switch (@typeInfo(@TypeOf(handler))) {
|
const Ev = @TypeOf(event);
|
||||||
.Fn => {
|
const name_tag = if (@hasField(Ev, "local")) event.local else continue;
|
||||||
try callHandler(handler, args, injectable);
|
if (name_tag != ev_name) continue;
|
||||||
|
switch (@typeInfo(@TypeOf(event.handler))) {
|
||||||
|
.Fn => try callHandler(event.handler, args, injectable),
|
||||||
|
.Type => switch (@typeInfo(event.handler)) {
|
||||||
|
.Fn => {}, // Pre-declaration of what args an event has, nothing to run.
|
||||||
|
else => unreachable,
|
||||||
},
|
},
|
||||||
else => {},
|
else => unreachable,
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -336,37 +367,29 @@ fn UninjectedArgsTuple(
|
||||||
return Tuple(std_args);
|
return Tuple(std_args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// enum describing every possible comptime-known global event name.
|
/// enum describing every possible comptime-known local event name
|
||||||
fn GlobalEvent(comptime mods: anytype) type {
|
fn LocalEventEnum(comptime mods: anytype) type {
|
||||||
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
|
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
for (mods) |M| {
|
for (mods) |M| {
|
||||||
// Global event handlers
|
_ = Module(M); // Validate the module
|
||||||
for (@typeInfo(M).Struct.decls) |decl| {
|
inline for (M.events) |event| {
|
||||||
const is_event_handler = switch (@typeInfo(@TypeOf(@field(M, decl.name)))) {
|
const Event = @TypeOf(event);
|
||||||
.Fn => true,
|
const name_tag = if (@hasField(Event, "local")) event.local else continue;
|
||||||
.Type => switch (@typeInfo(@field(M, decl.name))) {
|
|
||||||
.Fn => true,
|
const exists_already = blk: {
|
||||||
else => false,
|
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, @tagName(name_tag))) break :blk true;
|
||||||
},
|
break :blk false;
|
||||||
else => false,
|
|
||||||
};
|
};
|
||||||
if (is_event_handler) {
|
if (!exists_already) {
|
||||||
const exists_already = blk2: {
|
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = @tagName(name_tag), .value = i }};
|
||||||
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, decl.name)) break :blk2 true;
|
i += 1;
|
||||||
break :blk2 false;
|
|
||||||
};
|
|
||||||
if (!exists_already) {
|
|
||||||
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = decl.name, .value = i }};
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return @Type(.{
|
return @Type(.{
|
||||||
.Enum = .{
|
.Enum = .{
|
||||||
.tag_type = std.math.IntFittingRange(0, enum_fields.len - 1),
|
.tag_type = if (enum_fields.len > 0) std.math.IntFittingRange(0, enum_fields.len - 1) else u0,
|
||||||
.fields = enum_fields,
|
.fields = enum_fields,
|
||||||
.decls = &[_]std.builtin.Type.Declaration{},
|
.decls = &[_]std.builtin.Type.Declaration{},
|
||||||
.is_exhaustive = true,
|
.is_exhaustive = true,
|
||||||
|
|
@ -374,55 +397,29 @@ fn GlobalEvent(comptime mods: anytype) type {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// enum describing every possible comptime-known event name
|
/// enum describing every possible comptime-known global event name
|
||||||
fn EventName(comptime mods: anytype) type {
|
fn GlobalEventEnum(comptime mods: anytype) type {
|
||||||
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
|
var enum_fields: []const std.builtin.Type.EnumField = &[0]std.builtin.Type.EnumField{};
|
||||||
var i: u32 = 0;
|
var i: u32 = 0;
|
||||||
for (mods) |M| {
|
for (mods) |M| {
|
||||||
// Global event handlers
|
_ = Module(M); // Validate the module
|
||||||
for (@typeInfo(M).Struct.decls) |decl| {
|
inline for (M.events) |event| {
|
||||||
const is_event_handler = switch (@typeInfo(@TypeOf(@field(M, decl.name)))) {
|
const Event = @TypeOf(event);
|
||||||
.Fn => true,
|
const name_tag = if (@hasField(Event, "global")) event.global else continue;
|
||||||
.Type => switch (@typeInfo(@field(M, decl.name))) {
|
|
||||||
.Fn => true,
|
|
||||||
else => false,
|
|
||||||
},
|
|
||||||
else => false,
|
|
||||||
};
|
|
||||||
if (is_event_handler) {
|
|
||||||
const exists_already = blk2: {
|
|
||||||
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, decl.name)) break :blk2 true;
|
|
||||||
break :blk2 false;
|
|
||||||
};
|
|
||||||
if (!exists_already) {
|
|
||||||
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = decl.name, .value = i }};
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Local event handlers
|
const exists_already = blk: {
|
||||||
if (@hasDecl(M, "local")) {
|
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, @tagName(name_tag))) break :blk true;
|
||||||
for (@typeInfo(M.local).Struct.decls) |decl| {
|
break :blk false;
|
||||||
switch (@typeInfo(@TypeOf(@field(M.local, decl.name)))) {
|
};
|
||||||
.Fn => {
|
if (!exists_already) {
|
||||||
const exists_already = blk2: {
|
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = @tagName(name_tag), .value = i }};
|
||||||
for (enum_fields) |existing| if (std.mem.eql(u8, existing.name, decl.name)) break :blk2 true;
|
i += 1;
|
||||||
break :blk2 false;
|
|
||||||
};
|
|
||||||
if (!exists_already) {
|
|
||||||
enum_fields = enum_fields ++ [_]std.builtin.Type.EnumField{.{ .name = decl.name, .value = i }};
|
|
||||||
i += 1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return @Type(.{
|
return @Type(.{
|
||||||
.Enum = .{
|
.Enum = .{
|
||||||
.tag_type = std.math.IntFittingRange(0, enum_fields.len - 1),
|
.tag_type = if (enum_fields.len > 0) std.math.IntFittingRange(0, enum_fields.len - 1) else u0,
|
||||||
.fields = enum_fields,
|
.fields = enum_fields,
|
||||||
.decls = &[_]std.builtin.Type.Declaration{},
|
.decls = &[_]std.builtin.Type.Declaration{},
|
||||||
.is_exhaustive = true,
|
.is_exhaustive = true,
|
||||||
|
|
@ -468,6 +465,48 @@ fn NamespacedModules(comptime modules: anytype) type {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validateEvents(comptime error_prefix: anytype, comptime events: anytype) void {
|
||||||
|
if (@typeInfo(@TypeOf(events)) != .Struct or !@typeInfo(@TypeOf(events)).Struct.is_tuple) {
|
||||||
|
@compileError(error_prefix ++ "expected a tuple of structs, found: " ++ @typeName(@TypeOf(events)));
|
||||||
|
}
|
||||||
|
inline for (events, 0..) |event, i| {
|
||||||
|
const Event = @TypeOf(event);
|
||||||
|
if (@typeInfo(Event) != .Struct) @compileError(std.fmt.comptimePrint(
|
||||||
|
error_prefix ++ "expected a tuple of structs, found tuple element ({}): {s}",
|
||||||
|
.{ i, @typeName(Event) },
|
||||||
|
));
|
||||||
|
|
||||||
|
// Verify .global = .foo, or .local = .foo, event handler name field
|
||||||
|
const name_tag = if (@hasField(Event, "global")) event.global else if (@hasField(Event, "local")) event.local else @compileError(std.fmt.comptimePrint(
|
||||||
|
error_prefix ++ "tuple element ({}) missing field `.global = .foo` or `.local = .foo` (event handler kind / name)",
|
||||||
|
.{i},
|
||||||
|
));
|
||||||
|
const is_global = if (@hasField(Event, "global")) true else false;
|
||||||
|
if (@typeInfo(@TypeOf(name_tag)) != .EnumLiteral) @compileError(std.fmt.comptimePrint(
|
||||||
|
error_prefix ++ "tuple element ({}) expected field `.{s} = .foo`, found: {s}",
|
||||||
|
.{ i, if (is_global) "global" else "local", @typeName(@TypeOf(name_tag)) },
|
||||||
|
));
|
||||||
|
|
||||||
|
// Verify .handler = fn, field
|
||||||
|
if (!@hasField(Event, "handler")) @compileError(std.fmt.comptimePrint(
|
||||||
|
error_prefix ++ "tuple element ({}) missing field `.handler = fn`",
|
||||||
|
.{i},
|
||||||
|
));
|
||||||
|
const valid_handler_type = switch (@typeInfo(@TypeOf(event.handler))) {
|
||||||
|
.Fn => true,
|
||||||
|
.Type => switch (@typeInfo(event.handler)) {
|
||||||
|
.Fn => true,
|
||||||
|
else => false,
|
||||||
|
},
|
||||||
|
else => false,
|
||||||
|
};
|
||||||
|
if (!valid_handler_type) @compileError(std.fmt.comptimePrint(
|
||||||
|
error_prefix ++ "tuple element ({}) expected field `.handler = fn`, found: {s}",
|
||||||
|
.{ i, @typeName(@TypeOf(event.handler)) },
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
test {
|
test {
|
||||||
testing.refAllDeclsRecursive(@This());
|
testing.refAllDeclsRecursive(@This());
|
||||||
}
|
}
|
||||||
|
|
@ -486,7 +525,11 @@ test Module {
|
||||||
pub const location = @Vector(3, f32);
|
pub const location = @Vector(3, f32);
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn tick() !void {}
|
pub const events = .{
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
|
fn tick() !void {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -504,20 +547,28 @@ test Modules {
|
||||||
pub const location = @Vector(3, f32);
|
pub const location = @Vector(3, f32);
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn tick() !void {}
|
pub const events = .{
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
|
fn tick() !void {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Renderer = Module(struct {
|
const Renderer = Module(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
};
|
||||||
|
|
||||||
/// Renderer module components
|
/// Renderer module components
|
||||||
pub const components = struct {};
|
pub const components = struct {};
|
||||||
|
|
||||||
pub fn tick() !void {}
|
fn tick() !void {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Sprite2D = Module(struct {
|
const Sprite2D = Module(struct {
|
||||||
pub const name = .engine_sprite2d;
|
pub const name = .engine_sprite2d;
|
||||||
|
pub const events = .{};
|
||||||
});
|
});
|
||||||
|
|
||||||
var modules: Modules(.{
|
var modules: Modules(.{
|
||||||
|
|
@ -532,39 +583,48 @@ test Modules {
|
||||||
testing.refAllDeclsRecursive(Sprite2D);
|
testing.refAllDeclsRecursive(Sprite2D);
|
||||||
}
|
}
|
||||||
|
|
||||||
test EventName {
|
test "event name" {
|
||||||
const Physics = Module(struct {
|
const Physics = Module(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
pub const components = struct {};
|
pub const components = struct {};
|
||||||
|
pub const events = .{
|
||||||
pub fn foo() !void {}
|
.{ .global = .foo, .handler = foo },
|
||||||
pub fn bar() !void {}
|
.{ .global = .bar, .handler = bar },
|
||||||
|
.{ .local = .baz, .handler = baz },
|
||||||
pub const local = struct {
|
.{ .local = .bam, .handler = bam },
|
||||||
pub fn baz() !void {}
|
|
||||||
pub fn bam() !void {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn foo() !void {}
|
||||||
|
fn bar() !void {}
|
||||||
|
fn baz() !void {}
|
||||||
|
fn bam() !void {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Renderer = Module(struct {
|
const Renderer = Module(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const components = struct {};
|
pub const components = struct {};
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .foo_unused, .handler = fn (f32, i32) void },
|
||||||
|
.{ .global = .bar_unused, .handler = fn (i32, f32) void },
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
.{ .global = .foo, .handler = foo },
|
||||||
|
.{ .global = .bar, .handler = bar },
|
||||||
|
};
|
||||||
|
|
||||||
pub const fooUnused = fn (f32, i32) void;
|
fn tick() !void {}
|
||||||
pub const barUnused = fn (i32, f32) void;
|
fn foo() !void {} // same .foo name as .engine_physics.foo
|
||||||
|
fn bar() !void {} // same .bar name as .engine_physics.bar
|
||||||
pub fn tick() !void {}
|
|
||||||
pub fn foo() !void {} // same .foo name as .engine_physics.foo
|
|
||||||
pub fn bar() !void {} // same .bar name as .engine_physics.bar
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const Sprite2D = Module(struct {
|
const Sprite2D = Module(struct {
|
||||||
pub const name = .engine_sprite2d;
|
pub const name = .engine_sprite2d;
|
||||||
|
pub const events = .{
|
||||||
pub fn tick() void {} // same .tick as .engine_renderer.tick
|
.{ .global = .tick, .handler = tick },
|
||||||
pub const local = struct {
|
.{ .global = .foobar, .handler = foobar },
|
||||||
pub fn foobar() void {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn tick() void {} // same .tick as .engine_renderer.tick
|
||||||
|
fn foobar() void {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const Mods = Modules(.{
|
const Mods = Modules(.{
|
||||||
|
|
@ -572,38 +632,36 @@ test EventName {
|
||||||
Renderer,
|
Renderer,
|
||||||
Sprite2D,
|
Sprite2D,
|
||||||
});
|
});
|
||||||
const info = @typeInfo(EventName(Mods.modules)).Enum;
|
|
||||||
|
|
||||||
try testing.expect(type, u3).eql(info.tag_type);
|
const locals = @typeInfo(LocalEventEnum(Mods.modules)).Enum;
|
||||||
try testing.expect(usize, 8).eql(info.fields.len);
|
try testing.expect(type, u1).eql(locals.tag_type);
|
||||||
try testing.expect([]const u8, "foo").eql(info.fields[0].name);
|
try testing.expect(usize, 2).eql(locals.fields.len);
|
||||||
try testing.expect([]const u8, "bar").eql(info.fields[1].name);
|
try testing.expect([]const u8, "baz").eql(locals.fields[0].name);
|
||||||
try testing.expect([]const u8, "baz").eql(info.fields[2].name);
|
try testing.expect([]const u8, "bam").eql(locals.fields[1].name);
|
||||||
try testing.expect([]const u8, "bam").eql(info.fields[3].name);
|
|
||||||
try testing.expect([]const u8, "fooUnused").eql(info.fields[4].name);
|
|
||||||
try testing.expect([]const u8, "barUnused").eql(info.fields[5].name);
|
|
||||||
try testing.expect([]const u8, "tick").eql(info.fields[6].name);
|
|
||||||
try testing.expect([]const u8, "foobar").eql(info.fields[7].name);
|
|
||||||
|
|
||||||
const global_info = @typeInfo(GlobalEvent(Mods.modules)).Enum;
|
const globals = @typeInfo(GlobalEventEnum(Mods.modules)).Enum;
|
||||||
try testing.expect(type, u3).eql(global_info.tag_type);
|
try testing.expect(type, u3).eql(globals.tag_type);
|
||||||
try testing.expect(usize, 5).eql(global_info.fields.len);
|
try testing.expect(usize, 6).eql(globals.fields.len);
|
||||||
try testing.expect([]const u8, "foo").eql(global_info.fields[0].name);
|
try testing.expect([]const u8, "foo").eql(globals.fields[0].name);
|
||||||
try testing.expect([]const u8, "bar").eql(global_info.fields[1].name);
|
try testing.expect([]const u8, "bar").eql(globals.fields[1].name);
|
||||||
try testing.expect([]const u8, "fooUnused").eql(global_info.fields[2].name);
|
try testing.expect([]const u8, "foo_unused").eql(globals.fields[2].name);
|
||||||
try testing.expect([]const u8, "barUnused").eql(global_info.fields[3].name);
|
try testing.expect([]const u8, "bar_unused").eql(globals.fields[3].name);
|
||||||
try testing.expect([]const u8, "tick").eql(global_info.fields[4].name);
|
try testing.expect([]const u8, "tick").eql(globals.fields[4].name);
|
||||||
|
try testing.expect([]const u8, "foobar").eql(globals.fields[5].name);
|
||||||
}
|
}
|
||||||
|
|
||||||
test ModuleName {
|
test ModuleName {
|
||||||
const Physics = Module(struct {
|
const Physics = Module(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
|
pub const events = .{};
|
||||||
});
|
});
|
||||||
const Renderer = Module(struct {
|
const Renderer = Module(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
|
pub const events = .{};
|
||||||
});
|
});
|
||||||
const Sprite2D = Module(struct {
|
const Sprite2D = Module(struct {
|
||||||
pub const name = .engine_sprite2d;
|
pub const name = .engine_sprite2d;
|
||||||
|
pub const events = .{};
|
||||||
});
|
});
|
||||||
const Mods = Modules(.{
|
const Mods = Modules(.{
|
||||||
Physics,
|
Physics,
|
||||||
|
|
@ -742,34 +800,39 @@ test "event name calling" {
|
||||||
const Physics = Module(struct {
|
const Physics = Module(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
pub const components = struct {};
|
pub const components = struct {};
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
.{ .local = .update, .handler = update },
|
||||||
|
.{ .local = .calc, .handler = calc },
|
||||||
|
};
|
||||||
|
|
||||||
pub fn tick() void {
|
fn tick() void {
|
||||||
global.ticks += 1;
|
global.ticks += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const local = struct {
|
fn update() void {
|
||||||
pub fn update() void {
|
global.physics_updates += 1;
|
||||||
global.physics_updates += 1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calc() void {
|
fn calc() void {
|
||||||
global.physics_calc += 1;
|
global.physics_calc += 1;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
});
|
});
|
||||||
const Renderer = Module(struct {
|
const Renderer = Module(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const components = struct {};
|
pub const components = struct {};
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
.{ .local = .update, .handler = update },
|
||||||
|
};
|
||||||
|
|
||||||
pub fn tick() void {
|
fn tick() void {
|
||||||
global.ticks += 1;
|
global.ticks += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const local = struct {
|
fn update() void {
|
||||||
pub fn update() void {
|
global.renderer_updates += 1;
|
||||||
global.renderer_updates += 1;
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var modules: Modules(.{
|
var modules: Modules(.{
|
||||||
|
|
@ -779,52 +842,46 @@ test "event name calling" {
|
||||||
try modules.init(testing.allocator);
|
try modules.init(testing.allocator);
|
||||||
defer modules.deinit(testing.allocator);
|
defer modules.deinit(testing.allocator);
|
||||||
|
|
||||||
try @TypeOf(modules).call(.tick, &.{}, .{});
|
try @TypeOf(modules).callGlobal(.tick, &.{}, .{});
|
||||||
try testing.expect(usize, 2).eql(global.ticks);
|
try testing.expect(usize, 2).eql(global.ticks);
|
||||||
|
|
||||||
// Check we can use .call() with a runtime-known event name.
|
// Check we can use .callGlobal() with a runtime-known event name.
|
||||||
const alloc = try testing.allocator.create(u3);
|
const alloc = try testing.allocator.create(u3);
|
||||||
defer testing.allocator.destroy(alloc);
|
defer testing.allocator.destroy(alloc);
|
||||||
const E = EventName(@TypeOf(modules).modules);
|
const GE = GlobalEventEnum(@TypeOf(modules).modules);
|
||||||
alloc.* = @intFromEnum(@as(E, .tick));
|
const LE = LocalEventEnum(@TypeOf(modules).modules);
|
||||||
|
alloc.* = @intFromEnum(@as(GE, .tick));
|
||||||
|
|
||||||
var event_name = @as(E, @enumFromInt(alloc.*));
|
const global_event_name = @as(GE, @enumFromInt(alloc.*));
|
||||||
try @TypeOf(modules).call(event_name, &.{}, .{});
|
try @TypeOf(modules).callGlobal(global_event_name, &.{}, .{});
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
try testing.expect(usize, 4).eql(global.ticks);
|
||||||
|
|
||||||
// Check call() behavior with a valid event name enum, but not a valid global event handler name
|
|
||||||
alloc.* = @intFromEnum(@as(E, .update));
|
|
||||||
event_name = @as(E, @enumFromInt(alloc.*));
|
|
||||||
try @TypeOf(modules).call(event_name, &.{}, .{});
|
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
|
||||||
try testing.expect(usize, 0).eql(global.physics_updates);
|
|
||||||
try testing.expect(usize, 0).eql(global.renderer_updates);
|
|
||||||
|
|
||||||
// Check we can use .callLocal() with a runtime-known event and module name.
|
// Check we can use .callLocal() with a runtime-known event and module name.
|
||||||
const m_alloc = try testing.allocator.create(u3);
|
const m_alloc = try testing.allocator.create(u3);
|
||||||
defer testing.allocator.destroy(m_alloc);
|
defer testing.allocator.destroy(m_alloc);
|
||||||
const M = ModuleName(@TypeOf(modules).modules);
|
const M = ModuleName(@TypeOf(modules).modules);
|
||||||
m_alloc.* = @intFromEnum(@as(M, .engine_renderer));
|
m_alloc.* = @intFromEnum(@as(M, .engine_renderer));
|
||||||
alloc.* = @intFromEnum(@as(E, .update));
|
alloc.* = @intFromEnum(@as(LE, .update));
|
||||||
var module_name = @as(M, @enumFromInt(m_alloc.*));
|
var module_name = @as(M, @enumFromInt(m_alloc.*));
|
||||||
try @TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
|
var local_event_name = @as(LE, @enumFromInt(alloc.*));
|
||||||
try @TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
|
try @TypeOf(modules).callLocal(module_name, local_event_name, &.{}, .{});
|
||||||
|
try @TypeOf(modules).callLocal(module_name, local_event_name, &.{}, .{});
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
try testing.expect(usize, 4).eql(global.ticks);
|
||||||
try testing.expect(usize, 0).eql(global.physics_updates);
|
try testing.expect(usize, 0).eql(global.physics_updates);
|
||||||
try testing.expect(usize, 2).eql(global.renderer_updates);
|
try testing.expect(usize, 2).eql(global.renderer_updates);
|
||||||
|
|
||||||
m_alloc.* = @intFromEnum(@as(M, .engine_physics));
|
m_alloc.* = @intFromEnum(@as(M, .engine_physics));
|
||||||
alloc.* = @intFromEnum(@as(E, .update));
|
alloc.* = @intFromEnum(@as(LE, .update));
|
||||||
module_name = @as(M, @enumFromInt(m_alloc.*));
|
module_name = @as(M, @enumFromInt(m_alloc.*));
|
||||||
event_name = @as(E, @enumFromInt(alloc.*));
|
local_event_name = @as(LE, @enumFromInt(alloc.*));
|
||||||
try @TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
|
try @TypeOf(modules).callLocal(module_name, local_event_name, &.{}, .{});
|
||||||
try testing.expect(usize, 1).eql(global.physics_updates);
|
try testing.expect(usize, 1).eql(global.physics_updates);
|
||||||
|
|
||||||
m_alloc.* = @intFromEnum(@as(M, .engine_physics));
|
m_alloc.* = @intFromEnum(@as(M, .engine_physics));
|
||||||
alloc.* = @intFromEnum(@as(E, .calc));
|
alloc.* = @intFromEnum(@as(LE, .calc));
|
||||||
module_name = @as(M, @enumFromInt(m_alloc.*));
|
module_name = @as(M, @enumFromInt(m_alloc.*));
|
||||||
event_name = @as(E, @enumFromInt(alloc.*));
|
local_event_name = @as(LE, @enumFromInt(alloc.*));
|
||||||
try @TypeOf(modules).callLocal(module_name, event_name, &.{}, .{});
|
try @TypeOf(modules).callLocal(module_name, local_event_name, &.{}, .{});
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
try testing.expect(usize, 4).eql(global.ticks);
|
||||||
try testing.expect(usize, 1).eql(global.physics_calc);
|
try testing.expect(usize, 1).eql(global.physics_calc);
|
||||||
try testing.expect(usize, 1).eql(global.physics_updates);
|
try testing.expect(usize, 1).eql(global.physics_updates);
|
||||||
|
|
@ -846,48 +903,57 @@ test "dispatch" {
|
||||||
}{};
|
}{};
|
||||||
const Minimal = Module(struct {
|
const Minimal = Module(struct {
|
||||||
pub const name = .engine_minimal;
|
pub const name = .engine_minimal;
|
||||||
|
pub const events = .{};
|
||||||
});
|
});
|
||||||
const Physics = Module(struct {
|
const Physics = Module(struct {
|
||||||
pub const name = .engine_physics;
|
pub const name = .engine_physics;
|
||||||
pub const components = struct {};
|
pub const components = struct {};
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
.{ .local = .update, .handler = update },
|
||||||
|
.{ .local = .calc, .handler = calc },
|
||||||
|
};
|
||||||
|
|
||||||
pub fn tick() void {
|
fn tick() void {
|
||||||
global.ticks += 1;
|
global.ticks += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const local = struct {
|
fn update() void {
|
||||||
pub fn update() void {
|
global.physics_updates += 1;
|
||||||
global.physics_updates += 1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn calc() void {
|
fn calc() void {
|
||||||
global.physics_calc += 1;
|
global.physics_calc += 1;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
});
|
});
|
||||||
const Renderer = Module(struct {
|
const Renderer = Module(struct {
|
||||||
pub const name = .engine_renderer;
|
pub const name = .engine_renderer;
|
||||||
pub const components = struct {};
|
pub const components = struct {};
|
||||||
|
pub const events = .{
|
||||||
|
.{ .global = .tick, .handler = tick },
|
||||||
|
.{ .global = .frame_done, .handler = fn (i32) void },
|
||||||
|
.{ .local = .update, .handler = update },
|
||||||
|
.{ .local = .basic_args, .handler = basicArgs },
|
||||||
|
.{ .local = .injected_args, .handler = injectedArgs },
|
||||||
|
};
|
||||||
|
|
||||||
pub const frameDone = fn (i32) void;
|
pub const frameDone = fn (i32) void;
|
||||||
|
|
||||||
pub fn tick() void {
|
fn tick() void {
|
||||||
global.ticks += 1;
|
global.ticks += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const local = struct {
|
fn update() void {
|
||||||
pub fn update() void {
|
global.renderer_updates += 1;
|
||||||
global.renderer_updates += 1;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub fn basicArgs(a: u32, b: u32) void {
|
fn basicArgs(a: u32, b: u32) void {
|
||||||
global.basic_args_sum = a + b;
|
global.basic_args_sum = a + b;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn injectedArgs(foo_ptr: *@TypeOf(foo), a: u32, b: u32) void {
|
fn injectedArgs(foo_ptr: *@TypeOf(foo), a: u32, b: u32) void {
|
||||||
foo_ptr.*.injected_args_sum = a + b;
|
foo_ptr.*.injected_args_sum = a + b;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var modules: Modules(.{
|
var modules: Modules(.{
|
||||||
|
|
@ -898,7 +964,8 @@ test "dispatch" {
|
||||||
try modules.init(testing.allocator);
|
try modules.init(testing.allocator);
|
||||||
defer modules.deinit(testing.allocator);
|
defer modules.deinit(testing.allocator);
|
||||||
|
|
||||||
const E = EventName(@TypeOf(modules).modules);
|
const GE = GlobalEventEnum(@TypeOf(modules).modules);
|
||||||
|
const LE = LocalEventEnum(@TypeOf(modules).modules);
|
||||||
const M = ModuleName(@TypeOf(modules).modules);
|
const M = ModuleName(@TypeOf(modules).modules);
|
||||||
|
|
||||||
// Global events
|
// Global events
|
||||||
|
|
@ -912,14 +979,14 @@ test "dispatch" {
|
||||||
try modules.dispatch(.{&foo});
|
try modules.dispatch(.{&foo});
|
||||||
try testing.expect(usize, 2).eql(global.ticks);
|
try testing.expect(usize, 2).eql(global.ticks);
|
||||||
// TODO: make sendDynamic take an args type to avoid footguns with comptime values, etc.
|
// TODO: make sendDynamic take an args type to avoid footguns with comptime values, etc.
|
||||||
modules.sendDynamic(@intFromEnum(@as(E, .tick)), .{});
|
modules.sendDynamic(@intFromEnum(@as(GE, .tick)), .{});
|
||||||
try modules.dispatch(.{&foo});
|
try modules.dispatch(.{&foo});
|
||||||
try testing.expect(usize, 4).eql(global.ticks);
|
try testing.expect(usize, 4).eql(global.ticks);
|
||||||
|
|
||||||
// Global events which are not handled by anyone yet can be written as `pub const fooBar = fn() void;`
|
// Global events which are not handled by anyone yet can be written as `pub const fooBar = fn() void;`
|
||||||
// within a module, which allows pre-declaring that `fooBar` is a valid global event, and enables
|
// within a module, which allows pre-declaring that `fooBar` is a valid global event, and enables
|
||||||
// its arguments to be inferred still like this:
|
// its arguments to be inferred still like this:
|
||||||
modules.send(.frameDone, .{1337});
|
modules.send(.frame_done, .{1337});
|
||||||
|
|
||||||
// Local events
|
// Local events
|
||||||
modules.sendToModule(.engine_renderer, .update, .{});
|
modules.sendToModule(.engine_renderer, .update, .{});
|
||||||
|
|
@ -928,7 +995,7 @@ test "dispatch" {
|
||||||
modules.sendToModule(.engine_physics, .update, .{});
|
modules.sendToModule(.engine_physics, .update, .{});
|
||||||
modules.sendToModuleDynamic(
|
modules.sendToModuleDynamic(
|
||||||
@intFromEnum(@as(M, .engine_physics)),
|
@intFromEnum(@as(M, .engine_physics)),
|
||||||
@intFromEnum(@as(E, .calc)),
|
@intFromEnum(@as(LE, .calc)),
|
||||||
.{},
|
.{},
|
||||||
);
|
);
|
||||||
try modules.dispatch(.{&foo});
|
try modules.dispatch(.{&foo});
|
||||||
|
|
@ -936,8 +1003,8 @@ test "dispatch" {
|
||||||
try testing.expect(usize, 1).eql(global.physics_calc);
|
try testing.expect(usize, 1).eql(global.physics_calc);
|
||||||
|
|
||||||
// Local events
|
// Local events
|
||||||
modules.sendToModule(.engine_renderer, .basicArgs, .{ .@"0" = @as(u32, 1), .@"1" = @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference
|
modules.sendToModule(.engine_renderer, .basic_args, .{ .@"0" = @as(u32, 1), .@"1" = @as(u32, 2) }); // TODO: match arguments against fn ArgsTuple, for correctness and type inference
|
||||||
modules.sendToModule(.engine_renderer, .injectedArgs, .{ .@"0" = @as(u32, 1), .@"1" = @as(u32, 2) });
|
modules.sendToModule(.engine_renderer, .injected_args, .{ .@"0" = @as(u32, 1), .@"1" = @as(u32, 2) });
|
||||||
try modules.dispatch(.{&foo});
|
try modules.dispatch(.{&foo});
|
||||||
try testing.expect(usize, 3).eql(global.basic_args_sum);
|
try testing.expect(usize, 3).eql(global.basic_args_sum);
|
||||||
try testing.expect(usize, 3).eql(foo.injected_args_sum);
|
try testing.expect(usize, 3).eql(foo.injected_args_sum);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue