From b1895bbef8dd69e7a3c80c8e073fa795a36e53e1 Mon Sep 17 00:00:00 2001 From: copygirl Date: Sun, 7 Apr 2024 18:27:18 +0200 Subject: [PATCH] Further ECS-ification, use systems --- src/main.zig | 73 +++++++++++++++++++++++++++++------------------- src/renderer.zig | 56 +++++++++++++++++++++++++------------ 2 files changed, 83 insertions(+), 46 deletions(-) diff --git a/src/main.zig b/src/main.zig index a955604..0616ee7 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5,20 +5,23 @@ const core = @import("mach").core; const Renderer = @import("./renderer.zig"); const flecszigble = @import("flecs-zig-ble"); -const World = flecszigble.World(void); +const Context = flecszigble.Context(void); +const World = Context.World; +const Iter = Context.Iter; + +const flecs = flecszigble.flecs; +const OnLoad = flecs.pipeline.OnLoad; +const OnUpdate = flecs.pipeline.OnUpdate; +const OnStore = flecs.pipeline.OnStore; pub const App = @This(); gpa: GeneralPurposeAllocator, allocator: std.mem.Allocator, +random: std.Random, world: *World, -random: std.rand.Random, - renderer: *Renderer, -app_timer: core.Timer, -title_timer: core.Timer, - pub fn init(app: *App) !void { try core.init(.{ // Request high performance = prefer dedicated GPU when available. @@ -35,6 +38,11 @@ pub fn init(app: *App) !void { app.gpa = GeneralPurposeAllocator{}; app.allocator = app.gpa.allocator(); + // Create a pseudo-random number generator, but initialize it with + // a constant seed so we always get the same result when launching. + var prng = std.Random.DefaultPrng.init(0); + app.random = prng.random(); + // Initialize flecs-zig-ble and create a new Flecs world. // // Flecs is a library for using Entity Component System (ECS) design @@ -45,32 +53,43 @@ pub fn init(app: *App) !void { // and modified. For example by using systems, you are able to get // entities that match a set of components, and modify their values. flecszigble.init(app.allocator); - app.world = try World.init(); + const world = try World.init(); + app.world = world; - // Create a pseudo-random number generator, but initialize it with - // a constant seed so we always get the same result when launching. - var prng = std.rand.DefaultPrng.init(0); - app.random = prng.random(); + // Create a singleton component for accessing the `App` from ECS. + _ = try world.singleton("App", *App, app); - app.renderer = try Renderer.init(app); + // TODO: The way we register systems using flecs-zig-ble is still very WIP. + _ = try world.system("PollEvents", pollEvents, OnLoad, "App"); + + const s = try world.system("UpdateWindowTitle", updateWindowTitle, OnStore, ""); + // Set the update interval of the `UpdateWindowTitle` system to 1 second. + _ = flecszigble.c.ecs_set_interval(world.raw, s.raw, 1.0); - app.app_timer = try core.Timer.start(); - app.title_timer = try core.Timer.start(); + app.renderer = try Renderer.init(app); } pub fn deinit(app: *App) void { // Using `defer` here, so we can specify them // in the order they were created in `init`. defer core.deinit(); - defer _ = app.gpa.deinit(); // TODO: Check for memory leaks? + defer _ = app.gpa.deinit(); defer app.world.deinit(); defer app.renderer.deinit(); } +/// Update function called by Mach Core, which we'll just use to update Flecs. +/// This will then process all the systems we've registered in our pipeline. pub fn update(app: *App) !bool { - // Read events from the OS such as input. - var iter = core.pollEvents(); - while (iter.next()) |event| { + return !app.world.progress(0.0); +} + +/// Read events from the OS such as input. +pub fn pollEvents(it: Iter) void { + const app = it.field(*App, 1)[0]; + + var pollIter = core.pollEvents(); + while (pollIter.next()) |event| { switch (event) { // Allow the renderer to act on the window being resized. // This is required so we can resize necessary buffers. @@ -78,19 +97,17 @@ pub fn update(app: *App) !bool { // Close the window when requested, such as when // pressing the X button in the window title bar. - .close => return true, + .close => it.world.quit(), else => {}, } } +} - app.renderer.update(); - - // Update the window title to show FPS and input frequency. - if (app.title_timer.read() >= 1.0) { - app.title_timer.reset(); - try core.printTitle("Triangle [ {d}fps ] [ Input {d}hz ]", .{ core.frameRate(), core.inputRate() }); - } - - return false; +/// Update the window title to show FPS and input frequency. +pub fn updateWindowTitle(_: Iter) void { + core.printTitle( + "Triangle [ {d}fps ] [ Input {d}hz ]", + .{ core.frameRate(), core.inputRate() }, + ) catch @panic("Title too long!"); } diff --git a/src/renderer.zig b/src/renderer.zig index 50d6f68..cb4d310 100644 --- a/src/renderer.zig +++ b/src/renderer.zig @@ -18,7 +18,12 @@ const VertexData = primitives.VertexData; const PrimitiveData = primitives.PrimitiveData; const flecszigble = @import("flecs-zig-ble"); -const Entity = flecszigble.Entity(void); +const Context = flecszigble.Context(void); +const Entity = Context.Entity; +const Iter = Context.Iter; + +const flecs = flecszigble.flecs; +const OnStore = flecs.pipeline.OnStore; const Transform = struct { value: Mat }; const CameraPerspective = struct { @@ -46,6 +51,7 @@ const ObjectData = struct { const Renderer = @This(); app: *App, +time: f32 = 0.0, pipeline: *gpu.RenderPipeline, view_proj_buffer: *gpu.Buffer, @@ -56,7 +62,6 @@ depth_texture_view: ?*gpu.TextureView = null, primitive_data: []PrimitiveData, object_data: []ObjectData, -camera_entity: Entity, pub fn init(app: *App) !*Renderer { // A string buffer used to format objects' labels. @@ -225,11 +230,22 @@ pub fn init(app: *App) !*Renderer { }; } - _ = try app.world.component(Transform); - _ = try app.world.component(CameraPerspective); + // Register components necessary for the camera. + _ = try app.world.component("Transform", Transform); + _ = try app.world.component("CameraPerspective", CameraPerspective); + + const camera_entity = try app.world.entity( + .{ .name = "Camera", .symbol = "Camera" }, + .{ Transform, CameraPerspective }, + ); + camera_entity.set(CameraPerspective, .{ + .field_of_view = 45.0, + .near_plane = 0.05, + .far_plane = 80.0, + }); - const camera_entity = try app.world.entity(.{ .name = "Camera" }, .{ Transform, CameraPerspective }); - camera_entity.set(CameraPerspective, .{ .field_of_view = 45.0, .near_plane = 0.05, .far_plane = 80.0 }); + const render_expr = "App, [in] CameraPerspective(Camera), [out] Transform(Camera)"; + _ = try app.world.system("Render", render, OnStore, render_expr); const result = try app.allocator.create(Renderer); result.* = .{ @@ -239,7 +255,6 @@ pub fn init(app: *App) !*Renderer { .camera_bind_group = camera_bind_group, .primitive_data = primitive_data, .object_data = object_data, - .camera_entity = camera_entity, }; // Initialize the depth texture. @@ -250,8 +265,8 @@ pub fn init(app: *App) !*Renderer { } pub fn deinit(self: *Renderer) void { - // Using `defer` here, so we can specify them - // in the order they were created in `init`. + // Using `defer` here, so we can specify resources we + // want to free in the order they were created in `init`. defer self.app.allocator.destroy(self); defer self.pipeline.release(); @@ -278,34 +293,39 @@ pub fn resize(self: *Renderer) void { self.recreateDepthTexture(); } -pub fn update(self: *Renderer) void { +pub fn render(it: Iter) void { + const app = it.field(*App, 1)[0]; + const camera_perspective = it.field(CameraPerspective, 2)[0]; + const camera_transform = &it.field(Transform, 3)[0]; + + const self = app.renderer; + self.time += it.deltaTime(); + // Set up a view matrix from the camera transform. // This moves everything to be relative to the camera. // TODO: Actually implement camera transform instead of hardcoding a look-at matrix. // const view_matrix = zm.inverse(app.camera_transform); - const time = self.app.app_timer.read(); const camera_distance = 8.0; - const x = @cos(time * std.math.tau / 20) * camera_distance; - const z = @sin(time * std.math.tau / 20) * camera_distance; + const x = @cos(self.time * std.math.tau / 20) * camera_distance; + const z = @sin(self.time * std.math.tau / 20) * camera_distance; const camera_pos = vec(x, 2.0, z, 1.0); const view_matrix = zm.lookAtLh(camera_pos, vec(0, 0, 0, 1), vec(0, 1, 0, 1)); // Setting the transform here doesn't do anything because it's not used // anywhere. In the future we would want to set the camera transform // outside of the rendering step, and then get and use it here, instead. - self.camera_entity.set(Transform, .{ .value = view_matrix }); + camera_transform.* = .{ .value = view_matrix }; // TODO: Not sure if this is the proper transform, or actually inverted. // Set up a projection matrix using the size of the window. // The perspective projection will make things further away appear smaller. const width: f32 = @floatFromInt(core.descriptor.width); const height: f32 = @floatFromInt(core.descriptor.height); - const perspective = self.camera_entity.get(CameraPerspective).?; const proj_matrix = zm.perspectiveFovLh( - std.math.degreesToRadians(f32, perspective.field_of_view), + std.math.degreesToRadians(f32, camera_perspective.field_of_view), width / height, - perspective.near_plane, - perspective.far_plane, + camera_perspective.near_plane, + camera_perspective.far_plane, ); const view_proj_matrix = zm.mul(view_matrix, proj_matrix);