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>
Modules define lists of objects, e.g. a `SpriteRenderer` module may define
```zig
sprites: mach.Objects(struct {
// ...
}),
```
Previously, the only way for another Mach module to 'attach data to a sprite object' or 'tag a sprite' would be to (ab)use the graph relations system, creating their own object and using parent/child relations to express that the sprite has some tag/data associated with it. For example:
```zig
// Game.zig
is_monster: mach.Object(struct{}), // empty object just to indicate 'some other object is a monster'
// ...
// Create a 'tag object'
const is_monster_tag_obj_id = game.is_monster.new(.{});
// Add the 'tag object' as a child of our sprite
sprite_renderer.sprites.addChild(my_sprite_id, is_monster_tag_obj_id);
// ...
```
This usage of the API was quite ugly/usage, and importantly eliminated your ability to use the parent/child relations for _other_ things where they are more appropriate. However, it did mean that you didn't have to go and fork+modify the `SpriteRenderer` module that you e.g. imported as a reusable package.
With this change, we add object _tags_ and _tags with values_. Any module can add their own tags or tags with values to any object, whether it is from their module or not. For example, the `is_monster` example above could now be written as:
```zig
// Game.zig
pub const mach_tags = .{ .is_monster };
// ...
try sprite_renderer.sprites.setTag(sprite_id, Game, .is_monster, null);
const is_monster: bool = sprite_renderer.sprites.hasTag(sprite_id, Game, .is_monster);
// is_monster == true!
// No longer a monster
try sprite_renderer.sprites.removeTag(sprite_id, Game, .is_monster);
```
This allows for effectively tagging objects as distinct kinds, states, etc. even though they aren't our object and we can't modify their `struct {}` type to include an `is_monster: bool` field of our own.
Internally, the implementation stores tags using a hashmap under the assumption that not all objects in a list will have a tag.
Tags with values work almost identically, the only difference is that the last parameter to `setTag` is set to another `mach.ObjectID` which points to whatever arbitrary data you'd like to attach to the object, and `getTag` returns it. For example:
```zig
// Game.zig
pub const mach_tags = .{
/// Whether a sprite is a monster
.is_monster,
/// Whether a sprite has a friendly sprite attached to it
.{ .friend, Sprite, .sprites },
};
// ...
try sprite_renderer.sprites.setTag(sprite_id, Game, .friend, friendly_sprite_id);
const has_friend: bool = sprite_renderer.sprites.hasTag(sprite_id, Game, .friend);
// has_friend == true!
// Get our friend
const friend_id: mach.ObjectID = sprite_renderer.sprites.getTag(sprite_id, Game, .friend);
// friend_id == friendly_sprite_id
// Delete our friend
try sprite_renderer.sprites.removeTag(sprite_id, Game, .friend);
```
Signed-off-by: Emi Gutekanst <emi@hexops.com>