Further ECS-ification, use systems

main
copygirl 1 month ago
parent fc0e5efc8c
commit b1895bbef8
  1. 73
      src/main.zig
  2. 56
      src/renderer.zig

@ -5,20 +5,23 @@ const core = @import("mach").core;
const Renderer = @import("./renderer.zig"); const Renderer = @import("./renderer.zig");
const flecszigble = @import("flecs-zig-ble"); 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(); pub const App = @This();
gpa: GeneralPurposeAllocator, gpa: GeneralPurposeAllocator,
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
random: std.Random,
world: *World, world: *World,
random: std.rand.Random,
renderer: *Renderer, renderer: *Renderer,
app_timer: core.Timer,
title_timer: core.Timer,
pub fn init(app: *App) !void { pub fn init(app: *App) !void {
try core.init(.{ try core.init(.{
// Request high performance = prefer dedicated GPU when available. // Request high performance = prefer dedicated GPU when available.
@ -35,6 +38,11 @@ pub fn init(app: *App) !void {
app.gpa = GeneralPurposeAllocator{}; app.gpa = GeneralPurposeAllocator{};
app.allocator = app.gpa.allocator(); 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. // Initialize flecs-zig-ble and create a new Flecs world.
// //
// Flecs is a library for using Entity Component System (ECS) design // 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 // and modified. For example by using systems, you are able to get
// entities that match a set of components, and modify their values. // entities that match a set of components, and modify their values.
flecszigble.init(app.allocator); 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 // Create a singleton component for accessing the `App` from ECS.
// a constant seed so we always get the same result when launching. _ = try world.singleton("App", *App, app);
var prng = std.rand.DefaultPrng.init(0);
app.random = prng.random();
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.renderer = try Renderer.init(app);
app.title_timer = try core.Timer.start();
} }
pub fn deinit(app: *App) void { pub fn deinit(app: *App) void {
// Using `defer` here, so we can specify them // Using `defer` here, so we can specify them
// in the order they were created in `init`. // in the order they were created in `init`.
defer core.deinit(); defer core.deinit();
defer _ = app.gpa.deinit(); // TODO: Check for memory leaks? defer _ = app.gpa.deinit();
defer app.world.deinit(); defer app.world.deinit();
defer app.renderer.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 { pub fn update(app: *App) !bool {
// Read events from the OS such as input. return !app.world.progress(0.0);
var iter = core.pollEvents(); }
while (iter.next()) |event| {
/// 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) { switch (event) {
// Allow the renderer to act on the window being resized. // Allow the renderer to act on the window being resized.
// This is required so we can resize necessary buffers. // 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 // Close the window when requested, such as when
// pressing the X button in the window title bar. // pressing the X button in the window title bar.
.close => return true, .close => it.world.quit(),
else => {}, else => {},
} }
} }
}
app.renderer.update(); /// Update the window title to show FPS and input frequency.
pub fn updateWindowTitle(_: Iter) void {
// Update the window title to show FPS and input frequency. core.printTitle(
if (app.title_timer.read() >= 1.0) { "Triangle [ {d}fps ] [ Input {d}hz ]",
app.title_timer.reset(); .{ core.frameRate(), core.inputRate() },
try core.printTitle("Triangle [ {d}fps ] [ Input {d}hz ]", .{ core.frameRate(), core.inputRate() }); ) catch @panic("Title too long!");
}
return false;
} }

@ -18,7 +18,12 @@ const VertexData = primitives.VertexData;
const PrimitiveData = primitives.PrimitiveData; const PrimitiveData = primitives.PrimitiveData;
const flecszigble = @import("flecs-zig-ble"); 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 Transform = struct { value: Mat };
const CameraPerspective = struct { const CameraPerspective = struct {
@ -46,6 +51,7 @@ const ObjectData = struct {
const Renderer = @This(); const Renderer = @This();
app: *App, app: *App,
time: f32 = 0.0,
pipeline: *gpu.RenderPipeline, pipeline: *gpu.RenderPipeline,
view_proj_buffer: *gpu.Buffer, view_proj_buffer: *gpu.Buffer,
@ -56,7 +62,6 @@ depth_texture_view: ?*gpu.TextureView = null,
primitive_data: []PrimitiveData, primitive_data: []PrimitiveData,
object_data: []ObjectData, object_data: []ObjectData,
camera_entity: Entity,
pub fn init(app: *App) !*Renderer { pub fn init(app: *App) !*Renderer {
// A string buffer used to format objects' labels. // A string buffer used to format objects' labels.
@ -225,11 +230,22 @@ pub fn init(app: *App) !*Renderer {
}; };
} }
_ = try app.world.component(Transform); // Register components necessary for the camera.
_ = try app.world.component(CameraPerspective); _ = 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 }); const render_expr = "App, [in] CameraPerspective(Camera), [out] Transform(Camera)";
camera_entity.set(CameraPerspective, .{ .field_of_view = 45.0, .near_plane = 0.05, .far_plane = 80.0 }); _ = try app.world.system("Render", render, OnStore, render_expr);
const result = try app.allocator.create(Renderer); const result = try app.allocator.create(Renderer);
result.* = .{ result.* = .{
@ -239,7 +255,6 @@ pub fn init(app: *App) !*Renderer {
.camera_bind_group = camera_bind_group, .camera_bind_group = camera_bind_group,
.primitive_data = primitive_data, .primitive_data = primitive_data,
.object_data = object_data, .object_data = object_data,
.camera_entity = camera_entity,
}; };
// Initialize the depth texture. // Initialize the depth texture.
@ -250,8 +265,8 @@ pub fn init(app: *App) !*Renderer {
} }
pub fn deinit(self: *Renderer) void { pub fn deinit(self: *Renderer) void {
// Using `defer` here, so we can specify them // Using `defer` here, so we can specify resources we
// in the order they were created in `init`. // want to free in the order they were created in `init`.
defer self.app.allocator.destroy(self); defer self.app.allocator.destroy(self);
defer self.pipeline.release(); defer self.pipeline.release();
@ -278,34 +293,39 @@ pub fn resize(self: *Renderer) void {
self.recreateDepthTexture(); 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. // Set up a view matrix from the camera transform.
// This moves everything to be relative to the camera. // This moves everything to be relative to the camera.
// TODO: Actually implement camera transform instead of hardcoding a look-at matrix. // TODO: Actually implement camera transform instead of hardcoding a look-at matrix.
// const view_matrix = zm.inverse(app.camera_transform); // const view_matrix = zm.inverse(app.camera_transform);
const time = self.app.app_timer.read();
const camera_distance = 8.0; const camera_distance = 8.0;
const x = @cos(time * std.math.tau / 20) * camera_distance; const x = @cos(self.time * std.math.tau / 20) * camera_distance;
const z = @sin(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 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)); 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 // 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 // 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. // 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. // TODO: Not sure if this is the proper transform, or actually inverted.
// Set up a projection matrix using the size of the window. // Set up a projection matrix using the size of the window.
// The perspective projection will make things further away appear smaller. // The perspective projection will make things further away appear smaller.
const width: f32 = @floatFromInt(core.descriptor.width); const width: f32 = @floatFromInt(core.descriptor.width);
const height: f32 = @floatFromInt(core.descriptor.height); const height: f32 = @floatFromInt(core.descriptor.height);
const perspective = self.camera_entity.get(CameraPerspective).?;
const proj_matrix = zm.perspectiveFovLh( const proj_matrix = zm.perspectiveFovLh(
std.math.degreesToRadians(f32, perspective.field_of_view), std.math.degreesToRadians(f32, camera_perspective.field_of_view),
width / height, width / height,
perspective.near_plane, camera_perspective.near_plane,
perspective.far_plane, camera_perspective.far_plane,
); );
const view_proj_matrix = zm.mul(view_matrix, proj_matrix); const view_proj_matrix = zm.mul(view_matrix, proj_matrix);

Loading…
Cancel
Save