This adds a helper that can be used people's `build.zig` code, called `@import("mach").addExecutable`,
a direct replacement for `b.addExecutable`.
The benefits of using this method are:
1. Your `build.zig` code does not need to be aware of platform-specifics that may be required to build an executable,
for example setting a Windows manifest to ensure your app is DPI-aware.
2. You do not need to write `main.zig` entrypoint code, which although simple today is expected to become more complex
over time as we add support for more platforms. For example, WASM and other platforms require different entrypoints
and this can account for that without your `build.zig` containing that logic.
Steps to use:
1. Delete your `main.zig` file.
2. Define your `Modules` as a public const in your `App.zig` file, e.g.:
```zig
// The set of Mach modules our application may use.
pub const Modules = mach.Modules(.{
mach.Core,
App,
});
```
3. Update your `build.zig` code to use `@import("mach").addExecutable` like so:
```zig
const std = @import("std");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const app_mod = b.createModule(.{
.root_source_file = b.path("src/App.zig"),
.optimize = optimize,
.target = target,
});
// Add Mach to our library and executable
const mach_dep = b.dependency("mach", .{
.target = target,
.optimize = optimize,
});
app_mod.addImport("mach", mach_dep.module("mach"));
// Use the Mach entrypoint to write main for us
const exe = @import("mach").addExecutable(mach_dep.builder, .{
.name = "hello-world",
.app = app_mod,
.target = target,
.optimize = optimize,
});
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
const app_unit_tests = b.addTest(.{
.root_module = app_mod,
});
const run_app_unit_tests = b.addRunArtifact(app_unit_tests);
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_app_unit_tests.step);
}
```
Signed-off-by: Emi <emi@hexops.com>
279 lines
8.5 KiB
Zig
279 lines
8.5 KiB
Zig
const std = @import("std");
|
|
const zigimg = @import("zigimg");
|
|
const assets = @import("assets");
|
|
const mach = @import("mach");
|
|
const gfx = mach.gfx;
|
|
const gpu = mach.gpu;
|
|
const math = mach.math;
|
|
|
|
const vec2 = math.vec2;
|
|
const vec3 = math.vec3;
|
|
const vec4 = math.vec4;
|
|
const Vec2 = math.Vec2;
|
|
const Vec3 = math.Vec3;
|
|
const Mat3x3 = math.Mat3x3;
|
|
const Mat4x4 = math.Mat4x4;
|
|
|
|
const App = @This();
|
|
|
|
// The set of Mach modules our application may use.
|
|
pub const Modules = mach.Modules(.{
|
|
mach.Core,
|
|
mach.gfx.Text,
|
|
App,
|
|
});
|
|
|
|
pub const mach_module = .app;
|
|
|
|
pub const mach_systems = .{ .main, .init, .tick, .deinit };
|
|
|
|
pub const main = mach.schedule(.{
|
|
.{ mach.Core, .init },
|
|
.{ gfx.Text, .init },
|
|
.{ App, .init },
|
|
.{ mach.Core, .main },
|
|
});
|
|
|
|
allocator: std.mem.Allocator,
|
|
window: mach.ObjectID,
|
|
timer: mach.time.Timer,
|
|
spawn_timer: mach.time.Timer,
|
|
fps_timer: mach.time.Timer,
|
|
rand: std.Random.DefaultPrng,
|
|
|
|
frame_count: usize = 0,
|
|
time: f32 = 0,
|
|
direction: Vec2 = vec2(0, 0),
|
|
spawning: bool = false,
|
|
player_id: mach.ObjectID = undefined,
|
|
style1_id: mach.ObjectID = undefined,
|
|
pipeline_id: mach.ObjectID = undefined,
|
|
|
|
const upscale = 1.0;
|
|
|
|
const text1: []const []const u8 = &.{
|
|
"Text but with spaces\n",
|
|
"and\n",
|
|
"newlines\n",
|
|
};
|
|
|
|
const text2: []const []const u8 = &.{"$!?"};
|
|
|
|
pub fn init(
|
|
core: *mach.Core,
|
|
app: *App,
|
|
app_mod: mach.Mod(App),
|
|
) !void {
|
|
core.on_tick = app_mod.id.tick;
|
|
core.on_exit = app_mod.id.deinit;
|
|
|
|
const window = try core.windows.new(.{
|
|
.title = "gfx.Text",
|
|
});
|
|
|
|
// TODO(allocator): find a better way to get an allocator here
|
|
const allocator = std.heap.c_allocator;
|
|
|
|
app.* = .{
|
|
.allocator = allocator,
|
|
.window = window,
|
|
.timer = try mach.time.Timer.start(),
|
|
.spawn_timer = try mach.time.Timer.start(),
|
|
.fps_timer = try mach.time.Timer.start(),
|
|
.rand = std.Random.DefaultPrng.init(1337),
|
|
};
|
|
}
|
|
|
|
fn setupPipeline(
|
|
app: *App,
|
|
text: *gfx.Text,
|
|
window_id: mach.ObjectID,
|
|
) !void {
|
|
// Create a text rendering pipeline
|
|
app.pipeline_id = try text.pipelines.new(.{
|
|
.window = window_id,
|
|
.render_pass = undefined,
|
|
});
|
|
|
|
// Create a text style
|
|
app.style1_id = try text.styles.new(.{
|
|
.font_size = 48 * gfx.px_per_pt, // 48pt
|
|
});
|
|
|
|
// TODO(text): release this memory somewhere
|
|
const player_text_value =
|
|
\\ Text with spaces
|
|
\\ and newlines
|
|
\\ but nothing fancy (yet)
|
|
;
|
|
const player_text = try app.allocator.alloc(u8, player_text_value.len);
|
|
@memcpy(player_text, player_text_value);
|
|
const player_segments = try app.allocator.alloc(gfx.Text.Segment, 1);
|
|
player_segments[0] = .{
|
|
.text = player_text,
|
|
.style = app.style1_id,
|
|
};
|
|
|
|
// Create our player text
|
|
app.player_id = try text.objects.new(.{
|
|
.transform = Mat4x4.translate(vec3(-0.02, 0, 0)),
|
|
.segments = player_segments,
|
|
});
|
|
// Attach the text object to our text rendering pipeline.
|
|
try text.pipelines.setParent(app.player_id, app.pipeline_id);
|
|
}
|
|
|
|
pub fn tick(
|
|
core: *mach.Core,
|
|
app: *App,
|
|
text: *gfx.Text,
|
|
text_mod: mach.Mod(gfx.Text),
|
|
) !void {
|
|
const label = @tagName(mach_module) ++ ".tick";
|
|
|
|
var direction = app.direction;
|
|
var spawning = app.spawning;
|
|
while (core.nextEvent()) |event| {
|
|
switch (event) {
|
|
.key_press => |ev| {
|
|
switch (ev.key) {
|
|
.left => direction.v[0] -= 1,
|
|
.right => direction.v[0] += 1,
|
|
.up => direction.v[1] += 1,
|
|
.down => direction.v[1] -= 1,
|
|
.space => spawning = true,
|
|
else => {},
|
|
}
|
|
},
|
|
.key_release => |ev| {
|
|
switch (ev.key) {
|
|
.left => direction.v[0] += 1,
|
|
.right => direction.v[0] -= 1,
|
|
.up => direction.v[1] -= 1,
|
|
.down => direction.v[1] += 1,
|
|
.space => spawning = false,
|
|
else => {},
|
|
}
|
|
},
|
|
.window_open => |ev| try setupPipeline(app, text, ev.window_id),
|
|
.close => core.exit(),
|
|
else => {},
|
|
}
|
|
}
|
|
app.direction = direction;
|
|
app.spawning = spawning;
|
|
|
|
var player = text.objects.getValue(app.player_id);
|
|
var player_pos = player.transform.translation();
|
|
if (spawning and app.spawn_timer.read() > 1.0 / 60.0) {
|
|
// Spawn new entities
|
|
_ = app.spawn_timer.lap();
|
|
for (0..10) |_| {
|
|
var new_pos = player_pos;
|
|
new_pos.v[0] += app.rand.random().floatNorm(f32) * 50;
|
|
new_pos.v[1] += app.rand.random().floatNorm(f32) * 50;
|
|
|
|
// TODO(text): release this memory somewhere
|
|
const new_text_value = "?!";
|
|
const new_text = try app.allocator.alloc(u8, new_text_value.len);
|
|
@memcpy(new_text, new_text_value);
|
|
const new_segments = try app.allocator.alloc(gfx.Text.Segment, 1);
|
|
new_segments[0] = .{
|
|
.text = new_text,
|
|
.style = app.style1_id,
|
|
};
|
|
|
|
const new_text_id = try text.objects.new(.{
|
|
.transform = Mat4x4.scaleScalar(upscale).mul(&Mat4x4.translate(new_pos)),
|
|
.segments = new_segments,
|
|
});
|
|
try text.pipelines.setParent(new_text_id, app.pipeline_id);
|
|
}
|
|
}
|
|
|
|
// Multiply by delta_time to ensure that movement is the same speed regardless of the frame rate.
|
|
const delta_time = app.timer.lap();
|
|
|
|
// Rotate all text objects in the pipeline.
|
|
var pipeline_children = try text.pipelines.getChildren(app.pipeline_id);
|
|
defer pipeline_children.deinit();
|
|
for (pipeline_children.items) |text_id| {
|
|
if (!text.objects.is(text_id)) continue;
|
|
if (text_id == app.player_id) continue; // don't rotate the player
|
|
var s = text.objects.getValue(text_id);
|
|
|
|
const location = s.transform.translation();
|
|
var transform = Mat4x4.ident;
|
|
transform = transform.mul(&Mat4x4.translate(location));
|
|
transform = transform.mul(&Mat4x4.rotateZ(2 * math.pi * app.time));
|
|
transform = transform.mul(&Mat4x4.scaleScalar(@min(math.cos(app.time / 2.0), 0.5)));
|
|
text.objects.set(text_id, .transform, transform);
|
|
}
|
|
|
|
// Calculate the player position, by moving in the direction the player wants to go
|
|
// by the speed amount.
|
|
const speed = 200.0;
|
|
player_pos.v[0] += direction.x() * speed * delta_time;
|
|
player_pos.v[1] += direction.y() * speed * delta_time;
|
|
text.objects.set(app.player_id, .transform, Mat4x4.translate(player_pos));
|
|
|
|
const window = core.windows.getValue(app.window);
|
|
|
|
// Grab the back buffer of the swapchain
|
|
// TODO(Core)
|
|
const back_buffer_view = window.swap_chain.getCurrentTextureView().?;
|
|
defer back_buffer_view.release();
|
|
|
|
// Create a command encoder
|
|
const encoder = window.device.createCommandEncoder(&.{ .label = label });
|
|
defer encoder.release();
|
|
|
|
// Begin render pass
|
|
const sky_blue = gpu.Color{ .r = 0.776, .g = 0.988, .b = 1, .a = 1 };
|
|
const color_attachments = [_]gpu.RenderPassColorAttachment{.{
|
|
.view = back_buffer_view,
|
|
.clear_value = sky_blue,
|
|
.load_op = .clear,
|
|
.store_op = .store,
|
|
}};
|
|
const render_pass = encoder.beginRenderPass(&gpu.RenderPassDescriptor.init(.{
|
|
.label = label,
|
|
.color_attachments = &color_attachments,
|
|
}));
|
|
|
|
// Render text
|
|
text.pipelines.set(app.pipeline_id, .render_pass, render_pass);
|
|
text_mod.call(.tick);
|
|
|
|
// Finish render pass
|
|
render_pass.end();
|
|
var command = encoder.finish(&.{ .label = label });
|
|
window.queue.submit(&[_]*gpu.CommandBuffer{command});
|
|
command.release();
|
|
render_pass.release();
|
|
|
|
app.frame_count += 1;
|
|
app.time += delta_time;
|
|
|
|
// TODO(object): window-title
|
|
// // Every second, update the window title with the FPS
|
|
// if (app.fps_timer.read() >= 1.0) {
|
|
// const pipeline = text.pipelines.getValue(app.pipeline_id);
|
|
// try core.printTitle(
|
|
// core.main_window,
|
|
// "text [ FPS: {d} ] [ Texts: {d} ] [ Segments: {d} ] [ Styles: {d} ]",
|
|
// .{ app.frame_count, pipeline.num_texts, pipeline.num_segments, pipeline.num_styles },
|
|
// );
|
|
// core.schedule(.update);
|
|
// app.fps_timer.reset();
|
|
// app.frame_count = 0;
|
|
// }
|
|
}
|
|
|
|
pub fn deinit(
|
|
app: *App,
|
|
text: *gfx.Text,
|
|
) void {
|
|
// Cleanup here, if desired.
|
|
text.objects.delete(app.player_id);
|
|
}
|