We doing global variables now
This commit is contained in:
@@ -1,284 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const sdl = @import("sdl");
|
|
||||||
const math = @import("math.zig");
|
|
||||||
const Time = @import("time.zig");
|
|
||||||
const Transform = @import("graphics/transform.zig");
|
|
||||||
const Controller = @import("graph/controller.zig");
|
|
||||||
const Graphics = @import("graphics.zig");
|
|
||||||
const Game = @import("game.zig");
|
|
||||||
|
|
||||||
const CUBE_MESH_DATA = [_]f32{
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, 0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
-0.5, -0.5, 0.5, 0.0, 0.0,
|
|
||||||
0.5, -0.5, -0.5, 0.0, 0.0,
|
|
||||||
};
|
|
||||||
const PLANE_MESH_DATA = [_]f32{
|
|
||||||
-0.5, -0.5, 0, 0.0, 1.0,
|
|
||||||
0.5, 0.5, 0, 1.0, 0.0,
|
|
||||||
-0.5, 0.5, 0, 0.0, 0.0,
|
|
||||||
0.5, 0.5, 0, 1.0, 0.0,
|
|
||||||
-0.5, -0.5, 0, 0.0, 1.0,
|
|
||||||
0.5, -0.5, 0, 1.0, 1.0,
|
|
||||||
};
|
|
||||||
// const TEXTURE_DATA = [_]u8{
|
|
||||||
// 255, 0, 0, 255,
|
|
||||||
// 0, 255, 0, 255,
|
|
||||||
// 0, 0, 255, 255,
|
|
||||||
// 0, 0, 0, 255,
|
|
||||||
// };
|
|
||||||
const TEXTURE_DATA = [_]u8{
|
|
||||||
255, 0, 0, 255,
|
|
||||||
0, 255, 0, 255,
|
|
||||||
0, 0, 255, 255,
|
|
||||||
0, 0, 0, 255,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const WorldTime = struct {
|
|
||||||
time: Time,
|
|
||||||
last_time: Time,
|
|
||||||
view_unresolved: f32,
|
|
||||||
view_timescale: f32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const PlayerState = union(enum) {
|
|
||||||
idle,
|
|
||||||
moving: struct {
|
|
||||||
from_position: @Vector(3, f32),
|
|
||||||
from_time: Time,
|
|
||||||
to_time: Time,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Player = struct {
|
|
||||||
mesh: Graphics.Mesh,
|
|
||||||
texture: Graphics.Texture,
|
|
||||||
transform: Transform,
|
|
||||||
position: @Vector(2, i32),
|
|
||||||
state: PlayerState,
|
|
||||||
|
|
||||||
move_units: f32,
|
|
||||||
idle_timescale: f32,
|
|
||||||
moving_timescale: f32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Environment = struct {
|
|
||||||
mesh: Graphics.Mesh,
|
|
||||||
texture: Graphics.Texture,
|
|
||||||
transform: Transform,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn init(controller: *Controller, graphics: *Graphics) !void {
|
|
||||||
controller.addResource(Player{
|
|
||||||
.mesh = try graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA)),
|
|
||||||
.texture = try graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA)),
|
|
||||||
.transform = .{},
|
|
||||||
.position = .{ 0, 0 },
|
|
||||||
.state = .idle,
|
|
||||||
.move_units = 0.125,
|
|
||||||
.moving_timescale = 1.0,
|
|
||||||
.idle_timescale = 0.0625,
|
|
||||||
});
|
|
||||||
controller.addResource(Environment{
|
|
||||||
.mesh = try graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA)),
|
|
||||||
.texture = try graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA)),
|
|
||||||
.transform = .{
|
|
||||||
.position = .{ 0, 0, -1 },
|
|
||||||
.scale = @splat(5),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
controller.addResource(WorldTime{
|
|
||||||
.time = Time.ZERO,
|
|
||||||
.last_time = Time.ZERO,
|
|
||||||
.view_unresolved = 0.0,
|
|
||||||
.view_timescale = 0.0625,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit() void {}
|
|
||||||
|
|
||||||
pub fn updateReal(
|
|
||||||
real_time: *Game.Time,
|
|
||||||
world_time: *WorldTime,
|
|
||||||
controller: *Controller,
|
|
||||||
) void {
|
|
||||||
world_time.view_unresolved += real_time.delta;
|
|
||||||
controller.queue(.{
|
|
||||||
updateWorld,
|
|
||||||
updatePlayerTransform,
|
|
||||||
updateCamera,
|
|
||||||
Controller.Option.ordered,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updateWorld(
|
|
||||||
world_time: *WorldTime,
|
|
||||||
controller: *Controller,
|
|
||||||
player: *Player,
|
|
||||||
) void {
|
|
||||||
if (world_time.view_unresolved <= 0) return;
|
|
||||||
|
|
||||||
var real_delta = world_time.view_unresolved;
|
|
||||||
var world_delta = Time.durationFromUnits(real_delta * world_time.view_timescale);
|
|
||||||
|
|
||||||
switch (player.state) {
|
|
||||||
.moving => |move| {
|
|
||||||
if (move.to_time.clock > world_time.time.clock) {
|
|
||||||
world_delta = @min(world_delta, move.to_time.clock - world_time.time.clock);
|
|
||||||
real_delta = Time.unitsFromDuration(world_delta) / world_time.view_timescale;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
.idle => {},
|
|
||||||
}
|
|
||||||
|
|
||||||
if (world_delta == 0) return;
|
|
||||||
|
|
||||||
world_time.last_time = world_time.time;
|
|
||||||
|
|
||||||
world_time.time.clock += world_delta;
|
|
||||||
world_time.view_unresolved -= real_delta;
|
|
||||||
|
|
||||||
controller.queue(.{
|
|
||||||
updatePlayer,
|
|
||||||
updateWorld,
|
|
||||||
Controller.Option.ordered,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updatePlayer(
|
|
||||||
player: *Player,
|
|
||||||
keyboard: *Game.Keyboard,
|
|
||||||
world_time: *WorldTime,
|
|
||||||
) void {
|
|
||||||
switch (player.state) {
|
|
||||||
.idle => {},
|
|
||||||
.moving => |move| {
|
|
||||||
if (world_time.time.past(move.to_time)) {
|
|
||||||
player.state = .idle;
|
|
||||||
world_time.view_timescale = player.idle_timescale;
|
|
||||||
} else return;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var delta: @Vector(2, i32) = .{ 0, 0 };
|
|
||||||
if (keyboard.keys.is_pressed(sdl.SCANCODE_W)) {
|
|
||||||
delta[1] += 1;
|
|
||||||
}
|
|
||||||
if (keyboard.keys.is_pressed(sdl.SCANCODE_S)) {
|
|
||||||
delta[1] -= 1;
|
|
||||||
}
|
|
||||||
if (keyboard.keys.is_pressed(sdl.SCANCODE_D)) {
|
|
||||||
delta[0] += 1;
|
|
||||||
}
|
|
||||||
if (keyboard.keys.is_pressed(sdl.SCANCODE_A)) {
|
|
||||||
delta[0] -= 1;
|
|
||||||
}
|
|
||||||
if (delta[0] != 0 or delta[1] != 0) {
|
|
||||||
updatePlayerTransform(player, world_time);
|
|
||||||
player.state = .{ .moving = .{
|
|
||||||
.from_position = player.transform.position,
|
|
||||||
.from_time = world_time.time,
|
|
||||||
.to_time = world_time.time.offset(player.move_units * math.lengthInt(delta)),
|
|
||||||
} };
|
|
||||||
player.position[0] += delta[0];
|
|
||||||
player.position[1] += delta[1];
|
|
||||||
world_time.view_timescale = player.moving_timescale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updateCamera(
|
|
||||||
graphics: *Graphics,
|
|
||||||
player: *Player,
|
|
||||||
real_time: *Game.Time,
|
|
||||||
) void {
|
|
||||||
graphics.camera.transform.position = math.lerpTimeLn(
|
|
||||||
graphics.camera.transform.position,
|
|
||||||
player.transform.position + @Vector(3, f32){ 0.0, -2.0, 5.0 },
|
|
||||||
real_time.delta,
|
|
||||||
-25,
|
|
||||||
);
|
|
||||||
|
|
||||||
const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 };
|
|
||||||
const INIT_ROTATION = Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5);
|
|
||||||
|
|
||||||
const ROTATED_DIR = Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION);
|
|
||||||
|
|
||||||
const target_rotation = Transform.combineRotations(
|
|
||||||
INIT_ROTATION,
|
|
||||||
Transform.rotationToward(
|
|
||||||
ROTATED_DIR,
|
|
||||||
player.transform.position - graphics.camera.transform.position,
|
|
||||||
.{ .normalize_to = true },
|
|
||||||
),
|
|
||||||
);
|
|
||||||
graphics.camera.transform.rotation = Transform.normalizeRotation(math.slerpTimeLn(
|
|
||||||
graphics.camera.transform.rotation,
|
|
||||||
target_rotation,
|
|
||||||
real_time.delta,
|
|
||||||
-2,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn updatePlayerTransform(player: *Player, world_time: *WorldTime) void {
|
|
||||||
switch (player.state) {
|
|
||||||
.idle => player.transform.position = .{ @floatFromInt(player.position[0]), @floatFromInt(player.position[1]), 0.0 },
|
|
||||||
.moving => |move| {
|
|
||||||
const to_position = @Vector(3, f32){ @floatFromInt(player.position[0]), @floatFromInt(player.position[1]), 0.0 };
|
|
||||||
player.transform.position = math.lerp(
|
|
||||||
move.from_position,
|
|
||||||
to_position,
|
|
||||||
world_time.time.progress(move.from_time, move.to_time),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw(
|
|
||||||
player: *Player,
|
|
||||||
env: *Environment,
|
|
||||||
graphics: *Graphics,
|
|
||||||
world_time: *WorldTime,
|
|
||||||
) !void {
|
|
||||||
env.transform.rotation = Transform.combineRotations(
|
|
||||||
env.transform.rotation,
|
|
||||||
Transform.rotationByAxis(.{ 0, 0, 1 }, world_time.time.unitsSince(world_time.last_time) * std.math.pi),
|
|
||||||
);
|
|
||||||
try graphics.drawMesh(env.mesh, env.texture, env.transform);
|
|
||||||
try graphics.drawMesh(env.mesh, env.texture, Transform{
|
|
||||||
.position = .{ 0, 0, -0.5 },
|
|
||||||
.scale = @splat(5),
|
|
||||||
});
|
|
||||||
try graphics.drawMesh(player.mesh, player.texture, player.transform);
|
|
||||||
}
|
|
118
src/entity.zig
Normal file
118
src/entity.zig
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const sdl = @import("sdl");
|
||||||
|
const Game = @import("game.zig");
|
||||||
|
const Graphics = @import("graphics.zig");
|
||||||
|
const Time = @import("time.zig");
|
||||||
|
const World = @import("world.zig");
|
||||||
|
const math = @import("math.zig");
|
||||||
|
|
||||||
|
position: @Vector(2, i32),
|
||||||
|
player: bool = false,
|
||||||
|
enemy: bool = false,
|
||||||
|
controller: Controller = .{},
|
||||||
|
next_update: Time = Time.ZERO,
|
||||||
|
|
||||||
|
const Controller = struct {
|
||||||
|
const Action = union(enum) {
|
||||||
|
move: @Vector(2, i32),
|
||||||
|
};
|
||||||
|
wanted_action: ?Action = null,
|
||||||
|
move_units: f32 = 0.125,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
pub fn update(self: *Self) void {
|
||||||
|
if (!World.time.past(self.next_update)) return;
|
||||||
|
|
||||||
|
if (self.player) self.updatePlayer();
|
||||||
|
if (self.enemy) self.updateEnemy();
|
||||||
|
self.updateController();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updatePlayer(self: *Self) void {
|
||||||
|
var delta: @Vector(2, i32) = .{ 0, 0 };
|
||||||
|
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_UP)) {
|
||||||
|
delta[1] += 1;
|
||||||
|
}
|
||||||
|
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_DOWN)) {
|
||||||
|
delta[1] -= 1;
|
||||||
|
}
|
||||||
|
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_RIGHT)) {
|
||||||
|
delta[0] += 1;
|
||||||
|
}
|
||||||
|
if (Game.keyboard.keys.is_pressed(sdl.SCANCODE_LEFT)) {
|
||||||
|
delta[0] -= 1;
|
||||||
|
}
|
||||||
|
if (@reduce(.Or, delta != @Vector(2, i32){ 0, 0 }))
|
||||||
|
self.controller.wanted_action = .{ .move = delta }
|
||||||
|
else
|
||||||
|
self.controller.wanted_action = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateEnemy(self: *Self) void {
|
||||||
|
if (World.getPlayer()) |player| {
|
||||||
|
var delta = player.position - self.position;
|
||||||
|
if (@reduce(.And, @abs(delta) <= @Vector(2, i64){ 1, 1 })) {
|
||||||
|
self.controller.wanted_action = null;
|
||||||
|
} else {
|
||||||
|
delta[0] = @max(-1, @min(1, delta[0]));
|
||||||
|
delta[1] = @max(-1, @min(1, delta[1]));
|
||||||
|
self.controller.wanted_action = .{ .move = delta };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn updateController(self: *Self) void {
|
||||||
|
if (self.controller.wanted_action) |action| {
|
||||||
|
switch (action) {
|
||||||
|
.move => |delta| {
|
||||||
|
const target = self.position + delta;
|
||||||
|
if (World.isFree(target)) {
|
||||||
|
self.next_update = World.time.offset(self.controller.move_units * math.lengthInt(delta));
|
||||||
|
self.position[0] += delta[0];
|
||||||
|
self.position[1] += delta[1];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(self: *Self, delta: f32) void {
|
||||||
|
const transform = Graphics.Transform{
|
||||||
|
.position = .{
|
||||||
|
@floatFromInt(self.position[0]),
|
||||||
|
@floatFromInt(self.position[1]),
|
||||||
|
0.5,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
Graphics.drawMesh(World.cube_mesh, World.texture, transform);
|
||||||
|
|
||||||
|
if (!self.player) return;
|
||||||
|
|
||||||
|
Graphics.camera.transform.position = math.lerpTimeLn(
|
||||||
|
Graphics.camera.transform.position,
|
||||||
|
transform.position + @Vector(3, f32){ 0.0, -2.0, 5.0 },
|
||||||
|
delta,
|
||||||
|
-25,
|
||||||
|
);
|
||||||
|
|
||||||
|
const ORIGIN_DIR = @Vector(3, f32){ 0.0, 0.0, -1.0 };
|
||||||
|
const INIT_ROTATION = Graphics.Transform.rotationByAxis(.{ 1.0, 0.0, 0.0 }, std.math.pi * 0.5);
|
||||||
|
|
||||||
|
const ROTATED_DIR = Graphics.Transform.rotateVector(ORIGIN_DIR, INIT_ROTATION);
|
||||||
|
|
||||||
|
const target_rotation = Graphics.Transform.combineRotations(
|
||||||
|
INIT_ROTATION,
|
||||||
|
Graphics.Transform.rotationToward(
|
||||||
|
ROTATED_DIR,
|
||||||
|
transform.position - Graphics.camera.transform.position,
|
||||||
|
.{ .normalize_to = true },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Graphics.camera.transform.rotation = Graphics.Transform.normalizeRotation(math.slerpTimeLn(
|
||||||
|
Graphics.camera.transform.rotation,
|
||||||
|
target_rotation,
|
||||||
|
delta,
|
||||||
|
-2,
|
||||||
|
));
|
||||||
|
}
|
16
src/error.zig
Normal file
16
src/error.zig
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const std = @import("std");
|
||||||
|
const libsdl = @import("sdl");
|
||||||
|
|
||||||
|
pub fn sdl() noreturn {
|
||||||
|
std.debug.panic("SDL Error:\n{s}\n", .{libsdl.GetError()});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn oom() noreturn {
|
||||||
|
std.debug.panic("Out of memory!\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
|
const FileError = std.mem.Allocator.Error || std.fs.File.OpenError || std.fs.File.ReadError;
|
||||||
|
|
||||||
|
pub fn file(err: FileError, path: []const u8) noreturn {
|
||||||
|
std.debug.panic("Error while reading \"{s}\": {any}", .{ path, err });
|
||||||
|
}
|
188
src/game.zig
188
src/game.zig
@@ -1,155 +1,106 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const sdl = @import("sdl");
|
const sdl = @import("sdl");
|
||||||
|
const err = @import("error.zig");
|
||||||
|
const Mouse = @import("mouse.zig");
|
||||||
|
const Keyboard = @import("keyboard.zig");
|
||||||
|
|
||||||
const debug_scene = @import("debug_scene.zig");
|
pub const Graphics = @import("graphics.zig");
|
||||||
const Graph = @import("graph.zig");
|
pub const World = @import("world.zig");
|
||||||
const Graphics = @import("graphics.zig");
|
|
||||||
|
|
||||||
// TODO:
|
const Time = struct {
|
||||||
// - Do something about deallocating `Resource`s when `Graph` fails
|
|
||||||
|
|
||||||
pub const RunInfo = struct { running: bool };
|
|
||||||
pub const Mouse = @import("mouse.zig");
|
|
||||||
pub const Keyboard = @import("keyboard.zig");
|
|
||||||
pub const Time = struct {
|
|
||||||
delta: f32,
|
delta: f32,
|
||||||
now: sdl.Time,
|
now: sdl.Time,
|
||||||
};
|
};
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
var alloc: std.mem.Allocator = undefined;
|
||||||
graph: Graph,
|
|
||||||
|
|
||||||
const Self = @This();
|
var running: bool = false;
|
||||||
pub fn init(alloc: std.mem.Allocator) GameError!Self {
|
var time: Time = .{ .delta = 0, .now = 0 };
|
||||||
var graph = try Graph.init(alloc);
|
pub var keyboard: Keyboard = .{};
|
||||||
errdefer graph.deinit();
|
pub var mouse: Mouse = .{};
|
||||||
|
|
||||||
const graphics = try Graphics.create();
|
const Game = @This();
|
||||||
|
pub fn init(game_alloc: std.mem.Allocator) void {
|
||||||
var controller = try graph.getController();
|
Game.alloc = game_alloc;
|
||||||
controller.addResource(graphics);
|
Game.running = false;
|
||||||
controller.addResource(Mouse{
|
Game.time = Time{ .now = 0, .delta = 0 };
|
||||||
.buttons = .{},
|
Game.keyboard = .{};
|
||||||
.x = 0.0,
|
Game.mouse = .{ .x = 0, .y = 0, .dx = 0, .dy = 0 };
|
||||||
.y = 0.0,
|
Graphics.create();
|
||||||
.dx = 0.0,
|
World.initDebug();
|
||||||
.dy = 0.0,
|
|
||||||
});
|
|
||||||
controller.addResource(Keyboard{});
|
|
||||||
controller.addResource(Time{
|
|
||||||
.delta = 0.0,
|
|
||||||
.now = 0,
|
|
||||||
});
|
|
||||||
controller.queue(debug_scene.init);
|
|
||||||
try graph.freeController(controller);
|
|
||||||
|
|
||||||
defer graph.reset();
|
|
||||||
try graph.runAllSystems();
|
|
||||||
|
|
||||||
return Self{
|
|
||||||
.alloc = alloc,
|
|
||||||
.graph = graph,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(self: *Self) GameError!void {
|
pub fn run() void {
|
||||||
{
|
Game.running = true;
|
||||||
var controller = try self.graph.getController();
|
|
||||||
controller.addResource(RunInfo{ .running = true });
|
|
||||||
try self.graph.freeController(controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (!self.graph.getResource(RunInfo).?.running) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
while (Game.running) {
|
||||||
var current_time: sdl.Time = undefined;
|
var current_time: sdl.Time = undefined;
|
||||||
if (sdl.GetCurrentTime(¤t_time)) {
|
if (sdl.GetCurrentTime(¤t_time)) {
|
||||||
const time = self.graph.getResource(Time).?;
|
if (Game.time.now != 0) {
|
||||||
if (time.now != 0) {
|
Game.time.delta = @as(f32, @floatFromInt(current_time - Game.time.now)) * 0.000000001;
|
||||||
time.delta = @as(f32, @floatFromInt(current_time - time.now)) * 0.000000001;
|
|
||||||
}
|
}
|
||||||
time.now = current_time;
|
Game.time.now = current_time;
|
||||||
} else return error.SdlError;
|
} else err.sdl();
|
||||||
|
|
||||||
var controller = try self.graph.getController();
|
Game.processEvents();
|
||||||
controller.queue(.{
|
World.updateReal(Game.time.delta);
|
||||||
processEvents,
|
if (Game.beginDraw()) {
|
||||||
debug_scene.updateReal,
|
World.draw(Game.time.delta);
|
||||||
beginDraw,
|
Game.endDraw();
|
||||||
endDraw,
|
}
|
||||||
Graph.Controller.Option.ordered,
|
|
||||||
});
|
|
||||||
try self.graph.freeController(controller);
|
|
||||||
|
|
||||||
defer self.graph.reset();
|
|
||||||
try self.graph.runAllSystems();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn beginDraw(graphics: *Graphics, controller: *Graph.Controller) GameError!void {
|
fn beginDraw() bool {
|
||||||
if (try graphics.beginDraw()) {
|
return Graphics.beginDraw();
|
||||||
controller.queue(debug_scene.draw);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn endDraw(graphics: *Graphics) GameError!void {
|
fn endDraw() void {
|
||||||
try graphics.endDraw();
|
Graphics.endDraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clean(graphics: *Graphics) !void {
|
fn processEvents() void {
|
||||||
graphics.destroy();
|
Game.mouse.dx = 0.0;
|
||||||
// TODO: Also remove the resource
|
Game.mouse.dy = 0.0;
|
||||||
}
|
Game.keyboard.keys.reset();
|
||||||
|
|
||||||
fn processEvents(
|
|
||||||
graphics: *Graphics,
|
|
||||||
run_info: *RunInfo,
|
|
||||||
mouse: *Mouse,
|
|
||||||
keyboard: *Keyboard,
|
|
||||||
) GameError!void {
|
|
||||||
mouse.dx = 0.0;
|
|
||||||
mouse.dy = 0.0;
|
|
||||||
keyboard.keys.reset();
|
|
||||||
|
|
||||||
sdl.PumpEvents();
|
sdl.PumpEvents();
|
||||||
while (true) {
|
while (true) {
|
||||||
var buffer: [16]sdl.Event = undefined;
|
var buffer: [16]sdl.Event = undefined;
|
||||||
const count: usize = @intCast(sdl.PeepEvents(&buffer, buffer.len, sdl.GETEVENT, sdl.EVENT_FIRST, sdl.EVENT_LAST));
|
const count: usize = @intCast(sdl.PeepEvents(&buffer, buffer.len, sdl.GETEVENT, sdl.EVENT_FIRST, sdl.EVENT_LAST));
|
||||||
if (count == -1) return GameError.SdlError;
|
if (count == -1) err.sdl();
|
||||||
for (buffer[0..count]) |event| {
|
for (buffer[0..count]) |event| {
|
||||||
switch (event.type) {
|
switch (event.type) {
|
||||||
sdl.EVENT_QUIT => {
|
sdl.EVENT_QUIT => {
|
||||||
run_info.running = false;
|
Game.running = false;
|
||||||
},
|
},
|
||||||
sdl.EVENT_WINDOW_RESIZED => {
|
sdl.EVENT_WINDOW_RESIZED => {
|
||||||
if (event.window.windowID != sdl.GetWindowID(graphics.window)) continue;
|
if (event.window.windowID != Graphics.windowId()) continue;
|
||||||
graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2));
|
Graphics.resize(@intCast(event.window.data1), @intCast(event.window.data2));
|
||||||
},
|
},
|
||||||
sdl.EVENT_MOUSE_MOTION => {
|
sdl.EVENT_MOUSE_MOTION => {
|
||||||
if (event.motion.windowID != sdl.GetWindowID(graphics.window)) continue;
|
if (event.motion.windowID != Graphics.windowId()) continue;
|
||||||
mouse.x = event.motion.x;
|
Game.mouse.x = event.motion.x;
|
||||||
mouse.y = event.motion.y;
|
Game.mouse.y = event.motion.y;
|
||||||
mouse.dx += event.motion.xrel;
|
Game.mouse.dx += event.motion.xrel;
|
||||||
mouse.dy += event.motion.yrel;
|
Game.mouse.dy += event.motion.yrel;
|
||||||
},
|
},
|
||||||
sdl.EVENT_KEY_DOWN => {
|
sdl.EVENT_KEY_DOWN => {
|
||||||
if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue;
|
if (event.key.windowID != Graphics.windowId()) continue;
|
||||||
keyboard.keys.press(event.key.scancode);
|
Game.keyboard.keys.press(event.key.scancode);
|
||||||
},
|
},
|
||||||
sdl.EVENT_KEY_UP => {
|
sdl.EVENT_KEY_UP => {
|
||||||
if (event.key.windowID != sdl.GetWindowID(graphics.window)) continue;
|
if (event.key.windowID != Graphics.windowId()) continue;
|
||||||
keyboard.keys.release(event.key.scancode);
|
Game.keyboard.keys.release(event.key.scancode);
|
||||||
},
|
},
|
||||||
sdl.EVENT_MOUSE_BUTTON_DOWN => {
|
sdl.EVENT_MOUSE_BUTTON_DOWN => {
|
||||||
if (event.button.windowID != sdl.GetWindowID(graphics.window)) continue;
|
if (event.button.windowID != Graphics.windowId()) continue;
|
||||||
mouse.buttons.press(event.button.button);
|
Game.mouse.buttons.press(event.button.button);
|
||||||
},
|
},
|
||||||
sdl.EVENT_MOUSE_BUTTON_UP => {
|
sdl.EVENT_MOUSE_BUTTON_UP => {
|
||||||
if (event.button.windowID != sdl.GetWindowID(graphics.window)) continue;
|
if (event.button.windowID != Graphics.windowId()) continue;
|
||||||
mouse.buttons.release(event.button.button);
|
Game.mouse.buttons.release(event.button.button);
|
||||||
},
|
},
|
||||||
else => {},
|
else => {},
|
||||||
}
|
}
|
||||||
@@ -161,25 +112,8 @@ fn processEvents(
|
|||||||
sdl.FlushEvents(sdl.EVENT_FIRST, sdl.EVENT_LAST);
|
sdl.FlushEvents(sdl.EVENT_FIRST, sdl.EVENT_LAST);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
pub fn deinit() void {
|
||||||
var controller = self.graph.getController() catch unreachable;
|
World.deinit();
|
||||||
controller.queue(.{
|
Graphics.destroy();
|
||||||
debug_scene.deinit,
|
|
||||||
clean,
|
|
||||||
Graph.Controller.Option.ordered,
|
|
||||||
});
|
|
||||||
self.graph.freeController(controller) catch unreachable;
|
|
||||||
self.graph.runAllSystems() catch unreachable;
|
|
||||||
|
|
||||||
self.graph.deinit();
|
|
||||||
|
|
||||||
sdl.Quit();
|
sdl.Quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const GameError = error{
|
|
||||||
SdlError,
|
|
||||||
OSError,
|
|
||||||
OutOfMemory,
|
|
||||||
MissingResource,
|
|
||||||
SystemDeadlock,
|
|
||||||
};
|
|
||||||
|
377
src/graph.zig
377
src/graph.zig
@@ -1,377 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const utils = @import("graph/utils.zig");
|
|
||||||
const Resource = @import("graph/resource.zig");
|
|
||||||
const System = @import("graph/system.zig");
|
|
||||||
|
|
||||||
// TODO:
|
|
||||||
// - Use arena allocator?
|
|
||||||
// - Resolve missing resource problem
|
|
||||||
// - Organize a better way to execute single commands on graph
|
|
||||||
// - Handle system errors
|
|
||||||
// - Removing of resources
|
|
||||||
|
|
||||||
pub const Controller = @import("graph/controller.zig");
|
|
||||||
|
|
||||||
const MAX_SYSTEM_REQUESTS = 8;
|
|
||||||
const DEFAULT_SYSTEM_CAPACITY = 16;
|
|
||||||
const DEFAULT_CONTROLLERS = 2;
|
|
||||||
const DEFAULT_DUDS_PER_CONTROLLER = 32;
|
|
||||||
|
|
||||||
const ResourceMap = std.AutoArrayHashMapUnmanaged(utils.Hash, Resource);
|
|
||||||
const SystemQueue = std.ArrayListUnmanaged(System);
|
|
||||||
const Controllers = std.ArrayListUnmanaged(Controller);
|
|
||||||
const Duds = std.ArrayListUnmanaged(System.Dud);
|
|
||||||
|
|
||||||
/// Assumed to be thread-safe
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
resources: ResourceMap,
|
|
||||||
system_queue: SystemQueue,
|
|
||||||
controllers: Controllers,
|
|
||||||
duds: Duds,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
pub fn init(alloc: std.mem.Allocator) !Self {
|
|
||||||
var resources = try ResourceMap.init(alloc, &.{}, &.{});
|
|
||||||
errdefer resources.deinit(alloc);
|
|
||||||
|
|
||||||
var system_queue = try SystemQueue.initCapacity(alloc, DEFAULT_SYSTEM_CAPACITY);
|
|
||||||
errdefer system_queue.deinit(alloc);
|
|
||||||
|
|
||||||
var controllers = try Controllers.initCapacity(alloc, DEFAULT_CONTROLLERS);
|
|
||||||
errdefer controllers.deinit(alloc);
|
|
||||||
|
|
||||||
errdefer for (controllers.items) |*controller| {
|
|
||||||
controller.deinit();
|
|
||||||
};
|
|
||||||
|
|
||||||
var duds = try Duds.initCapacity(alloc, DEFAULT_CONTROLLERS * DEFAULT_DUDS_PER_CONTROLLER);
|
|
||||||
errdefer duds.deinit(alloc);
|
|
||||||
|
|
||||||
for (0..DEFAULT_CONTROLLERS * DEFAULT_DUDS_PER_CONTROLLER) |_| {
|
|
||||||
duds.appendAssumeCapacity(.{});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0..DEFAULT_CONTROLLERS) |i| {
|
|
||||||
var controller = try Controller.create(alloc);
|
|
||||||
controller.setDuds(@intCast(DEFAULT_DUDS_PER_CONTROLLER * i), @intCast(DEFAULT_DUDS_PER_CONTROLLER * (i + 1)));
|
|
||||||
controllers.appendAssumeCapacity(controller);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.resources = resources,
|
|
||||||
.system_queue = system_queue,
|
|
||||||
.controllers = controllers,
|
|
||||||
.duds = duds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
var resource_iter = self.resources.iterator();
|
|
||||||
while (resource_iter.next()) |entry| {
|
|
||||||
entry.value_ptr.deinit(self.alloc);
|
|
||||||
}
|
|
||||||
self.resources.clearAndFree(self.alloc);
|
|
||||||
self.resources.deinit(self.alloc);
|
|
||||||
|
|
||||||
for (self.system_queue.items) |system| {
|
|
||||||
self.alloc.free(system.requested_types);
|
|
||||||
}
|
|
||||||
self.system_queue.deinit(self.alloc);
|
|
||||||
|
|
||||||
for (self.controllers.items) |*controller| {
|
|
||||||
controller.deinit();
|
|
||||||
}
|
|
||||||
self.controllers.deinit(self.alloc);
|
|
||||||
|
|
||||||
self.duds.deinit(self.alloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clear all internal data in preparation for the next run cycle
|
|
||||||
/// Does not clear any `Resource`s
|
|
||||||
pub fn reset(self: *Self) void {
|
|
||||||
// Controller cleanup
|
|
||||||
for (self.controllers.items, 0..) |*controller, i| {
|
|
||||||
for (controller.commands()) |*command| {
|
|
||||||
switch (command.*) {
|
|
||||||
.add_resource => |*resource| resource.deinit(controller.alloc),
|
|
||||||
.queue_system => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
controller.clear();
|
|
||||||
controller.setDuds(
|
|
||||||
i * self.duds.items.len / self.controllers.items.len,
|
|
||||||
(i + 1) * self.duds.items.len / self.controllers.items.len,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
self.system_queue.clearRetainingCapacity();
|
|
||||||
// Duds cleanup
|
|
||||||
for (self.duds.items) |*dud| {
|
|
||||||
dud.required_count = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enqueueSystem(self: *Self, system: System) !void {
|
|
||||||
try self.system_queue.append(self.alloc, system);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn runAllSystems(self: *Self) GraphError!void {
|
|
||||||
while (self.system_queue.items.len > 0) {
|
|
||||||
var swap_with = self.system_queue.items.len - 1;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const system = &self.system_queue.items[self.system_queue.items.len - 1];
|
|
||||||
|
|
||||||
if (system.requires_dud) |dud_id| {
|
|
||||||
if (self.duds.items[dud_id].required_count == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else break;
|
|
||||||
if (swap_with > 0) {
|
|
||||||
swap_with -= 1;
|
|
||||||
std.mem.swap(
|
|
||||||
System,
|
|
||||||
&self.system_queue.items[self.system_queue.items.len - 1],
|
|
||||||
&self.system_queue.items[swap_with],
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return GraphError.SystemDeadlock;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const next_system = self.system_queue.pop().?;
|
|
||||||
|
|
||||||
self.runSystem(next_system) catch |err| {
|
|
||||||
std.debug.print("System run error: {} while running {s}\n", .{ err, next_system.label });
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Does not deallocate the system
|
|
||||||
fn runSystem(self: *Self, system: System) GraphError!void {
|
|
||||||
if (system.requires_dud) |dud_id| {
|
|
||||||
std.debug.assert(self.duds.items[dud_id].required_count == 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
var buffer: [MAX_SYSTEM_REQUESTS]*anyopaque = undefined;
|
|
||||||
var controller: ?Controller = null;
|
|
||||||
errdefer if (controller) |*c| c.deinit();
|
|
||||||
|
|
||||||
var buffer_len: usize = 0;
|
|
||||||
for (system.requested_types) |request| {
|
|
||||||
switch (request) {
|
|
||||||
.resource => |resource| {
|
|
||||||
buffer[buffer_len] = self.getAnyopaqueResource(resource) orelse return GraphError.MissingResource;
|
|
||||||
},
|
|
||||||
.controller => {
|
|
||||||
controller = try self.getController();
|
|
||||||
controller.?.submit_dud = system.submit_dud;
|
|
||||||
buffer[buffer_len] = @ptrCast(&controller.?);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
buffer_len += 1;
|
|
||||||
}
|
|
||||||
system.function_runner(buffer[0..buffer_len]);
|
|
||||||
if (system.submit_dud) |dud_id| {
|
|
||||||
self.duds.items[dud_id].required_count -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (controller) |c| {
|
|
||||||
defer controller = null;
|
|
||||||
try self.freeController(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn applyCommands(self: *Self, commands: []const Controller.Command) !void {
|
|
||||||
for (commands) |command| {
|
|
||||||
switch (command) {
|
|
||||||
.add_resource => |r| try self.addResource(r),
|
|
||||||
.queue_system => |s| {
|
|
||||||
if (s.submit_dud) |submit_id| self.duds.items[submit_id].required_count += 1;
|
|
||||||
try self.enqueueSystem(s);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn getController(self: *Self) !Controller {
|
|
||||||
if (self.controllers.pop()) |c| {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
const next_dud_id = self.duds.items.len;
|
|
||||||
for (try self.duds.addManyAsSlice(self.alloc, DEFAULT_DUDS_PER_CONTROLLER)) |*dud| {
|
|
||||||
dud.required_count = 0;
|
|
||||||
}
|
|
||||||
errdefer self.duds.shrinkRetainingCapacity(self.duds.items.len - DEFAULT_DUDS_PER_CONTROLLER);
|
|
||||||
|
|
||||||
var controller = try Controller.create(self.alloc);
|
|
||||||
controller.setDuds(@intCast(next_dud_id), @intCast(next_dud_id + DEFAULT_DUDS_PER_CONTROLLER));
|
|
||||||
return controller;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Evaluates and clears the controller (even if errors out)
|
|
||||||
pub fn freeController(self: *Self, controller: Controller) !void {
|
|
||||||
var c = controller;
|
|
||||||
try self.applyCommands(c.commands());
|
|
||||||
c.clear();
|
|
||||||
try self.controllers.append(self.alloc, c);
|
|
||||||
// TODO: Handle controller error state
|
|
||||||
}
|
|
||||||
|
|
||||||
pub inline fn getResource(self: *Self, comptime resource: type) ?*resource {
|
|
||||||
utils.validateResource(resource);
|
|
||||||
if (getAnyopaqueResource(self, utils.hashType(resource))) |ptr| {
|
|
||||||
return @alignCast(@ptrCast(ptr));
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getAnyopaqueResource(self: *Self, resource_hash: utils.Hash) ?*anyopaque {
|
|
||||||
if (self.resources.get(resource_hash)) |resource| {
|
|
||||||
return resource.pointer;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Discards any previous resource data, resource is assumed to be allocated with `self.alloc`
|
|
||||||
pub inline fn addResource(self: *Self, resource: Resource) !void {
|
|
||||||
var previous = try self.resources.fetchPut(self.alloc, resource.hash, resource);
|
|
||||||
if (previous) |*p| {
|
|
||||||
p.value.deinit(self.alloc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const GraphError = error{
|
|
||||||
MissingResource,
|
|
||||||
OutOfMemory,
|
|
||||||
SystemDeadlock,
|
|
||||||
};
|
|
||||||
|
|
||||||
test "simple graph smoke test" {
|
|
||||||
const Graph = @This();
|
|
||||||
const TestResource = struct {
|
|
||||||
number: u32,
|
|
||||||
|
|
||||||
fn addOne(rsc: *@This()) void {
|
|
||||||
rsc.number += 1;
|
|
||||||
}
|
|
||||||
fn addTen(rsc: *@This()) void {
|
|
||||||
rsc.number += 10;
|
|
||||||
}
|
|
||||||
fn addThousand(rsc: *@This()) void {
|
|
||||||
rsc.number += 1000;
|
|
||||||
}
|
|
||||||
fn subThousand(rsc: *@This()) void {
|
|
||||||
rsc.number -= 1000;
|
|
||||||
}
|
|
||||||
fn addEleven(cmd: *Controller) void {
|
|
||||||
cmd.queue(addTen);
|
|
||||||
cmd.queue(addOne);
|
|
||||||
|
|
||||||
cmd.queue(.{
|
|
||||||
.{
|
|
||||||
addThousand,
|
|
||||||
addThousand,
|
|
||||||
addThousand,
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
subThousand,
|
|
||||||
subThousand,
|
|
||||||
subThousand,
|
|
||||||
},
|
|
||||||
Controller.Option.ordered,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var graph = try Graph.init(std.testing.allocator);
|
|
||||||
defer graph.deinit();
|
|
||||||
|
|
||||||
var controller = try graph.getController();
|
|
||||||
controller.addResource(TestResource{ .number = 100 });
|
|
||||||
|
|
||||||
controller.queue(TestResource.addOne);
|
|
||||||
controller.queue(TestResource.addOne);
|
|
||||||
|
|
||||||
controller.queue(TestResource.addTen);
|
|
||||||
|
|
||||||
controller.queue(TestResource.addEleven);
|
|
||||||
|
|
||||||
try graph.freeController(controller);
|
|
||||||
|
|
||||||
try graph.runAllSystems();
|
|
||||||
|
|
||||||
const result = graph.getResource(TestResource);
|
|
||||||
try std.testing.expectEqual(result.?.number, 123);
|
|
||||||
}
|
|
||||||
|
|
||||||
test "complex queue graph smoke test" {
|
|
||||||
const Graph = @This();
|
|
||||||
const TestResource = struct {
|
|
||||||
const Rsc = @This();
|
|
||||||
|
|
||||||
data1: isize,
|
|
||||||
data2: isize,
|
|
||||||
|
|
||||||
fn queueManySystems(cmd: *Controller) void {
|
|
||||||
cmd.queue(.{
|
|
||||||
.{
|
|
||||||
addTen,
|
|
||||||
addTen,
|
|
||||||
addTen,
|
|
||||||
addTen,
|
|
||||||
subTwenty,
|
|
||||||
},
|
|
||||||
// `data1` = 20
|
|
||||||
// `data2` = 5
|
|
||||||
.{
|
|
||||||
mulTen,
|
|
||||||
mulTen,
|
|
||||||
mulTwo,
|
|
||||||
mulTwo,
|
|
||||||
},
|
|
||||||
// `data1` = 8000
|
|
||||||
// `data2` = 9
|
|
||||||
.{
|
|
||||||
subTwenty,
|
|
||||||
},
|
|
||||||
// `data1` = 7980
|
|
||||||
// `data2` = 10
|
|
||||||
Controller.Option.ordered,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
fn addTen(rsc: *Rsc) void {
|
|
||||||
rsc.data1 += 10;
|
|
||||||
rsc.data2 += 1;
|
|
||||||
}
|
|
||||||
fn subTwenty(rsc: *Rsc) void {
|
|
||||||
rsc.data1 -= 20;
|
|
||||||
rsc.data2 += 1;
|
|
||||||
}
|
|
||||||
fn mulTen(rsc: *Rsc) void {
|
|
||||||
rsc.data1 *= 10;
|
|
||||||
rsc.data2 += 1;
|
|
||||||
}
|
|
||||||
fn mulTwo(rsc: *Rsc) void {
|
|
||||||
rsc.data1 *= 2;
|
|
||||||
rsc.data2 += 1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var graph = try Graph.init(std.testing.allocator);
|
|
||||||
defer graph.deinit();
|
|
||||||
|
|
||||||
var controller = try graph.getController();
|
|
||||||
|
|
||||||
controller.addResource(TestResource{ .data1 = 0, .data2 = 0 });
|
|
||||||
controller.queue(TestResource.queueManySystems);
|
|
||||||
|
|
||||||
try graph.freeController(controller);
|
|
||||||
|
|
||||||
try graph.runAllSystems();
|
|
||||||
|
|
||||||
const result = graph.getResource(TestResource).?;
|
|
||||||
try std.testing.expectEqual(7980, result.data1);
|
|
||||||
try std.testing.expectEqual(10, result.data2);
|
|
||||||
}
|
|
@@ -1,200 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const utils = @import("utils.zig");
|
|
||||||
const System = @import("system.zig");
|
|
||||||
const Resource = @import("resource.zig");
|
|
||||||
const Controller = @This();
|
|
||||||
|
|
||||||
pub const Option = utils.SystemSetOption;
|
|
||||||
|
|
||||||
const DEFAULT_CONTROLLER_CAPACITY = 8;
|
|
||||||
|
|
||||||
alloc: std.mem.Allocator,
|
|
||||||
command_buffer: std.ArrayListUnmanaged(Command),
|
|
||||||
error_state: ErrorState,
|
|
||||||
dud_range: struct { System.Dud.Id, System.Dud.Id },
|
|
||||||
submit_dud: ?System.Dud.Id,
|
|
||||||
|
|
||||||
pub const Command = union(enum) {
|
|
||||||
add_resource: Resource,
|
|
||||||
queue_system: System,
|
|
||||||
};
|
|
||||||
pub const ErrorState = union(enum) {
|
|
||||||
ok: void,
|
|
||||||
recoverable: []const u8,
|
|
||||||
unrecoverable: void,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn create(alloc: std.mem.Allocator) !Controller {
|
|
||||||
return .{
|
|
||||||
.alloc = alloc,
|
|
||||||
.command_buffer = try std.ArrayListUnmanaged(Command).initCapacity(alloc, DEFAULT_CONTROLLER_CAPACITY),
|
|
||||||
.error_state = .ok,
|
|
||||||
.dud_range = .{ 0, 0 },
|
|
||||||
.submit_dud = null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns command queue, caller is responsible for freeing it's data
|
|
||||||
/// Call `clean()` afterwards, to clear the command queue
|
|
||||||
pub fn commands(self: *Controller) []Command {
|
|
||||||
return self.command_buffer.items;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setDuds(self: *Controller, start_id: System.Dud.Id, end_id: System.Dud.Id) void {
|
|
||||||
self.dud_range = .{ start_id, end_id };
|
|
||||||
}
|
|
||||||
|
|
||||||
fn acquireDud(self: *Controller) ?System.Dud.Id {
|
|
||||||
if (self.dud_range[0] == self.dud_range[1]) return null;
|
|
||||||
|
|
||||||
defer self.dud_range[0] += 1;
|
|
||||||
return self.dud_range[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears the command buffer for the next use (does not deallocate it's contents)
|
|
||||||
pub fn clear(self: *Controller) void {
|
|
||||||
self.command_buffer.clearRetainingCapacity();
|
|
||||||
switch (self.error_state) {
|
|
||||||
.ok, .unrecoverable => {},
|
|
||||||
.recoverable => |msg| self.alloc.free(msg),
|
|
||||||
}
|
|
||||||
self.error_state = .ok;
|
|
||||||
self.submit_dud = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds resource to the global storage, discarding any previously existing data
|
|
||||||
pub inline fn addResource(self: *Controller, resource: anytype) void {
|
|
||||||
utils.validateResource(@TypeOf(resource));
|
|
||||||
|
|
||||||
self.addAnyopaqueResource(
|
|
||||||
@ptrCast(&resource),
|
|
||||||
utils.hashType(@TypeOf(resource)),
|
|
||||||
@sizeOf(@TypeOf(resource)),
|
|
||||||
@alignOf(@TypeOf(resource)),
|
|
||||||
) catch |err| self.fail(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Queues a multitude of functions to be executed either in parallel or in ordered manner
|
|
||||||
/// `system_set` can be either a `System`-like function or a tuple which may contain other system sets
|
|
||||||
///
|
|
||||||
/// Optional tuple fields that control the execution behavior of functions:
|
|
||||||
///
|
|
||||||
/// `ordered` - ensures that all systems specified in the tuple are executed in provided order
|
|
||||||
pub fn queue(self: *Controller, comptime system_set: anytype) void {
|
|
||||||
utils.validateSystemSet(system_set);
|
|
||||||
|
|
||||||
self.queueInternal(system_set) catch |err| self.fail(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn queueInternal(self: *Controller, comptime system_set: anytype) !void {
|
|
||||||
const prev_count = self.command_buffer.items.len;
|
|
||||||
|
|
||||||
const command_buffer = try self.command_buffer.addManyAsSlice(self.alloc, utils.countSystems(system_set));
|
|
||||||
errdefer self.command_buffer.shrinkRetainingCapacity(prev_count);
|
|
||||||
|
|
||||||
const commands_created = try self.createQueueCommands(utils.SystemSet.fromAny(system_set), command_buffer, null, self.submit_dud);
|
|
||||||
std.debug.assert(commands_created == command_buffer.len);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn createQueueCommands(
|
|
||||||
self: *Controller,
|
|
||||||
comptime system_set: utils.SystemSet,
|
|
||||||
command_buffer: []Command,
|
|
||||||
requires_dud: ?System.Dud.Id,
|
|
||||||
submit_dud: ?System.Dud.Id,
|
|
||||||
) !usize {
|
|
||||||
switch (system_set) {
|
|
||||||
.single => |single| {
|
|
||||||
command_buffer[0] = .{ .queue_system = .{
|
|
||||||
.function_runner = single.runner,
|
|
||||||
.requested_types = single.requests,
|
|
||||||
.requires_dud = requires_dud,
|
|
||||||
.submit_dud = submit_dud,
|
|
||||||
.label = single.label,
|
|
||||||
} };
|
|
||||||
return 1;
|
|
||||||
},
|
|
||||||
.set => |set| {
|
|
||||||
var queued_total: usize = 0;
|
|
||||||
var prev_dud = requires_dud;
|
|
||||||
var next_dud = submit_dud;
|
|
||||||
|
|
||||||
if (set.ordered) {
|
|
||||||
next_dud = requires_dud;
|
|
||||||
}
|
|
||||||
|
|
||||||
var queued_sets: usize = 0;
|
|
||||||
const total_sets: usize = set.subsets.len;
|
|
||||||
|
|
||||||
inline for (set.subsets) |subset| {
|
|
||||||
if (set.ordered) {
|
|
||||||
prev_dud = next_dud;
|
|
||||||
if (queued_sets == total_sets - 1) {
|
|
||||||
next_dud = submit_dud;
|
|
||||||
} else {
|
|
||||||
// TODO: Soft fail
|
|
||||||
next_dud = self.acquireDud().?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
queued_total += try self.createQueueCommands(
|
|
||||||
subset,
|
|
||||||
command_buffer[queued_total..],
|
|
||||||
prev_dud,
|
|
||||||
next_dud,
|
|
||||||
);
|
|
||||||
queued_sets += 1;
|
|
||||||
}
|
|
||||||
return queued_total;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `previous_output` is expected to be aligned accordingly
|
|
||||||
fn addAnyopaqueResource(
|
|
||||||
self: *Controller,
|
|
||||||
resource: *const anyopaque,
|
|
||||||
hash: utils.Hash,
|
|
||||||
size: usize,
|
|
||||||
align_to: u29,
|
|
||||||
) !void {
|
|
||||||
// TODO: Review this shady function
|
|
||||||
const resource_buffer = try self.alloc.alloc(u8, size + align_to - 1);
|
|
||||||
errdefer self.alloc.free(resource_buffer);
|
|
||||||
|
|
||||||
const align_offset = std.mem.alignPointerOffset(
|
|
||||||
@as([*]u8, @ptrCast(resource_buffer)),
|
|
||||||
align_to,
|
|
||||||
) orelse unreachable;
|
|
||||||
|
|
||||||
@memcpy(
|
|
||||||
resource_buffer[align_offset..size],
|
|
||||||
@as([*]const u8, @ptrCast(resource))[0..size],
|
|
||||||
);
|
|
||||||
|
|
||||||
try self.command_buffer.append(self.alloc, .{ .add_resource = .{
|
|
||||||
.pointer = @ptrCast(resource_buffer[align_offset..]),
|
|
||||||
.buffer = resource_buffer,
|
|
||||||
.alignment = align_to,
|
|
||||||
.hash = hash,
|
|
||||||
} });
|
|
||||||
}
|
|
||||||
|
|
||||||
const ControllerError = std.mem.Allocator.Error;
|
|
||||||
fn fail(self: *Controller, err: ControllerError) void {
|
|
||||||
if (self.error_state == .unrecoverable) return;
|
|
||||||
if (self.error_state == .recoverable) self.alloc.free(self.error_state.recoverable);
|
|
||||||
switch (err) {
|
|
||||||
error.OutOfMemory => self.error_state = .unrecoverable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Controller) void {
|
|
||||||
for (self.command_buffer.items) |*command| {
|
|
||||||
switch (command.*) {
|
|
||||||
.add_resource => |*resource| resource.deinit(self.alloc),
|
|
||||||
.queue_system => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.clear();
|
|
||||||
self.command_buffer.deinit(self.alloc);
|
|
||||||
}
|
|
@@ -1,14 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const utils = @import("utils.zig");
|
|
||||||
const Resource = @This();
|
|
||||||
|
|
||||||
/// Resource data
|
|
||||||
pointer: *anyopaque,
|
|
||||||
/// Pointer to the memory allocted for this resource
|
|
||||||
buffer: []u8,
|
|
||||||
alignment: u29,
|
|
||||||
hash: utils.Hash,
|
|
||||||
|
|
||||||
pub fn deinit(self: *Resource, alloc: std.mem.Allocator) void {
|
|
||||||
alloc.free(self.buffer);
|
|
||||||
}
|
|
@@ -1,15 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const utils = @import("utils.zig");
|
|
||||||
const Controller = @import("controller.zig");
|
|
||||||
|
|
||||||
function_runner: *const fn ([]const *anyopaque) void,
|
|
||||||
requested_types: []const utils.SystemRequest,
|
|
||||||
requires_dud: ?Dud.Id,
|
|
||||||
submit_dud: ?Dud.Id,
|
|
||||||
label: []const u8,
|
|
||||||
|
|
||||||
pub const Dud = struct {
|
|
||||||
pub const Id = usize;
|
|
||||||
|
|
||||||
required_count: u16 = 0,
|
|
||||||
};
|
|
@@ -1,204 +0,0 @@
|
|||||||
const std = @import("std");
|
|
||||||
const Resource = @import("resource.zig");
|
|
||||||
const Controller = @import("controller.zig");
|
|
||||||
const System = @import("system.zig");
|
|
||||||
|
|
||||||
pub const Hash = u32;
|
|
||||||
const HashAlgorithm = std.crypto.hash.blake2.Blake2s(@bitSizeOf(Hash));
|
|
||||||
|
|
||||||
pub const SystemSetOption = enum {
|
|
||||||
ordered,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SystemRequest = union(enum) {
|
|
||||||
resource: Hash,
|
|
||||||
controller: void,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SystemSet = union(enum) {
|
|
||||||
single: struct {
|
|
||||||
runner: *const fn ([]const *anyopaque) void,
|
|
||||||
requests: []const SystemRequest,
|
|
||||||
label: []const u8,
|
|
||||||
},
|
|
||||||
set: struct {
|
|
||||||
subsets: []const SystemSet,
|
|
||||||
ordered: bool,
|
|
||||||
},
|
|
||||||
|
|
||||||
pub fn fromAny(comptime any: anytype) SystemSet {
|
|
||||||
return comptime switch (@typeInfo(@TypeOf(any))) {
|
|
||||||
.@"struct" => fromStruct(any),
|
|
||||||
.@"fn" => fromFunction(any),
|
|
||||||
else => @compileError("System set must be either a tuple or a function, got " ++ @typeName(@TypeOf(any))),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn fromStruct(comptime set: anytype) SystemSet {
|
|
||||||
return comptime blk: {
|
|
||||||
var subset_count = 0;
|
|
||||||
for (@typeInfo(@TypeOf(set)).@"struct".fields) |field| {
|
|
||||||
const info = @typeInfo(field.type);
|
|
||||||
if (info == .@"fn" or info == .@"struct") {
|
|
||||||
subset_count += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var subsets: [subset_count]SystemSet = undefined;
|
|
||||||
var ordered = false;
|
|
||||||
var i = 0;
|
|
||||||
for (@typeInfo(@TypeOf(set)).@"struct".fields) |field| {
|
|
||||||
const info = @typeInfo(field.type);
|
|
||||||
if (info == .@"fn" or info == .@"struct") {
|
|
||||||
subsets[i] = SystemSet.fromAny(@field(set, field.name));
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (field.type == SystemSetOption) {
|
|
||||||
switch (@field(set, field.name)) {
|
|
||||||
SystemSetOption.ordered => ordered = true,
|
|
||||||
}
|
|
||||||
i += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
@compileError("System set contains extraneous elements");
|
|
||||||
}
|
|
||||||
const subsets_const = subsets;
|
|
||||||
break :blk SystemSet{ .set = .{
|
|
||||||
.subsets = &subsets_const,
|
|
||||||
.ordered = ordered,
|
|
||||||
} };
|
|
||||||
};
|
|
||||||
}
|
|
||||||
pub fn fromFunction(comptime function: anytype) SystemSet {
|
|
||||||
return comptime SystemSet{ .single = .{
|
|
||||||
.runner = generateRunner(function),
|
|
||||||
.requests = generateRequests(function),
|
|
||||||
.label = @typeName(@TypeOf(function)),
|
|
||||||
} };
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub inline fn hashType(comptime h_type: type) Hash {
|
|
||||||
return hashString(@typeName(h_type));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn hashString(comptime name: []const u8) Hash {
|
|
||||||
@setEvalBranchQuota(100000);
|
|
||||||
var output: [@divExact(@bitSizeOf(Hash), 8)]u8 = undefined;
|
|
||||||
|
|
||||||
HashAlgorithm.hash(name, &output, .{});
|
|
||||||
return std.mem.readInt(
|
|
||||||
Hash,
|
|
||||||
output[0..],
|
|
||||||
@import("builtin").cpu.arch.endian(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validateResource(comptime resource_type: type) void {
|
|
||||||
switch (@typeInfo(resource_type)) {
|
|
||||||
.@"struct", .@"enum", .@"union" => return,
|
|
||||||
else => @compileError("Invalid resource type \"" ++ @typeName(resource_type) ++ "\""),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Make validators print helpful errors so I don't have to check reference trace all the time
|
|
||||||
pub fn validateSystem(comptime system: anytype) void {
|
|
||||||
const info = @typeInfo(@TypeOf(system));
|
|
||||||
if (info != .@"fn") @compileError("System can only be a function, got " ++ @typeName(system));
|
|
||||||
if (@typeInfo(info.@"fn".return_type.?) != .void and
|
|
||||||
@typeInfo(info.@"fn".return_type.?) != .error_union) @compileError("Systems are not allowed to return any value (" ++ @typeName(info.@"fn".return_type.?) ++ " returned)");
|
|
||||||
if (info.@"fn".is_var_args) @compileError("System cannot be variadic");
|
|
||||||
if (info.@"fn".is_generic) @compileError("System cannot be generic");
|
|
||||||
|
|
||||||
comptime {
|
|
||||||
var controller_requests: usize = 0;
|
|
||||||
for (info.@"fn".params) |param| {
|
|
||||||
if (@typeInfo(param.type.?) != .pointer) @compileError("Systems can only have pointer parameters");
|
|
||||||
switch (@typeInfo(param.type.?).pointer.child) {
|
|
||||||
Controller => {
|
|
||||||
controller_requests += 1;
|
|
||||||
},
|
|
||||||
else => |t| validateResource(t),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (controller_requests > 1) @compileError("A system cannot request controller more than once");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validateSystemSet(comptime system_set: anytype) void {
|
|
||||||
comptime {
|
|
||||||
@setEvalBranchQuota(1000);
|
|
||||||
switch (@typeInfo(@TypeOf(system_set))) {
|
|
||||||
.@"fn" => validateSystem(system_set),
|
|
||||||
.@"struct" => |struct_info| {
|
|
||||||
for (struct_info.fields) |field_info| {
|
|
||||||
switch (@typeInfo(field_info.type)) {
|
|
||||||
.@"fn", .@"struct" => validateSystemSet(@field(system_set, field_info.name)),
|
|
||||||
else => {
|
|
||||||
if (field_info.type == SystemSetOption) continue;
|
|
||||||
@compileError("Invalid field \"" ++
|
|
||||||
field_info.name ++
|
|
||||||
"\" of type `" ++
|
|
||||||
@typeName(field_info.type) ++
|
|
||||||
"` in system set");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => {
|
|
||||||
@compileError("System set must be either a function or a tuple (got `" ++ @typeName(@TypeOf(system_set)) ++ "`)");
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generateRunner(comptime system: anytype) fn ([]const *anyopaque) void {
|
|
||||||
const RunnerImpl = struct {
|
|
||||||
fn runner(resources: []const *anyopaque) void {
|
|
||||||
var args: std.meta.ArgsTuple(@TypeOf(system)) = undefined;
|
|
||||||
inline for (0..@typeInfo(@TypeOf(system)).@"fn".params.len) |index| {
|
|
||||||
args[index] = @alignCast(@ptrCast(resources[index]));
|
|
||||||
}
|
|
||||||
switch (@typeInfo(@typeInfo(@TypeOf(system)).@"fn".return_type.?)) {
|
|
||||||
.void => @call(.always_inline, system, args),
|
|
||||||
.error_union => @call(.always_inline, system, args) catch |err| {
|
|
||||||
std.debug.print("System error: {s}\n", .{@errorName(err)});
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return RunnerImpl.runner;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generateRequests(comptime function: anytype) []const SystemRequest {
|
|
||||||
return comptime blk: {
|
|
||||||
var requests: [@typeInfo(@TypeOf(function)).@"fn".params.len]SystemRequest = undefined;
|
|
||||||
for (0.., @typeInfo(@TypeOf(function)).@"fn".params) |i, param| {
|
|
||||||
switch (@typeInfo(param.type.?).pointer.child) {
|
|
||||||
Controller => requests[i] = .controller,
|
|
||||||
else => |resource_type| requests[i] = .{ .resource = hashType(resource_type) },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const requests_const = requests;
|
|
||||||
break :blk &requests_const;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn countSystems(comptime tuple: anytype) usize {
|
|
||||||
return comptime blk: {
|
|
||||||
var total: usize = 0;
|
|
||||||
switch (@typeInfo(@TypeOf(tuple))) {
|
|
||||||
.@"fn" => total += 1,
|
|
||||||
.@"struct" => |struct_info| {
|
|
||||||
for (struct_info.fields) |field| {
|
|
||||||
switch (@typeInfo(field.type)) {
|
|
||||||
.@"fn", .@"struct" => total += countSystems(@field(tuple, field.name)),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
break :blk total;
|
|
||||||
};
|
|
||||||
}
|
|
368
src/graphics.zig
368
src/graphics.zig
@@ -1,7 +1,7 @@
|
|||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
const sdl = @import("sdl");
|
const sdl = @import("sdl");
|
||||||
|
const err = @import("error.zig");
|
||||||
const presets = @import("graphics/presets.zig");
|
const presets = @import("graphics/presets.zig");
|
||||||
const GameError = @import("game.zig").GameError;
|
|
||||||
|
|
||||||
pub const Transform = @import("graphics/transform.zig");
|
pub const Transform = @import("graphics/transform.zig");
|
||||||
pub const Camera = @import("graphics/camera.zig");
|
pub const Camera = @import("graphics/camera.zig");
|
||||||
@@ -16,74 +16,67 @@ pub const Texture = struct {
|
|||||||
sampler: *sdl.GPUSampler,
|
sampler: *sdl.GPUSampler,
|
||||||
};
|
};
|
||||||
|
|
||||||
window: *sdl.Window,
|
var window: *sdl.Window = undefined;
|
||||||
renderer: *sdl.Renderer,
|
var renderer: *sdl.Renderer = undefined;
|
||||||
device: *sdl.GPUDevice,
|
var device: *sdl.GPUDevice = undefined;
|
||||||
/// Only available while drawing
|
/// Only available while drawing
|
||||||
command_buffer: ?*sdl.GPUCommandBuffer,
|
var command_buffer: ?*sdl.GPUCommandBuffer = null;
|
||||||
render_pass: ?*sdl.GPURenderPass,
|
var render_pass: ?*sdl.GPURenderPass = null;
|
||||||
|
|
||||||
shader_vert: *sdl.GPUShader,
|
var shader_vert: *sdl.GPUShader = undefined;
|
||||||
shader_frag: *sdl.GPUShader,
|
var shader_frag: *sdl.GPUShader = undefined;
|
||||||
|
|
||||||
vertex_buffer: *sdl.GPUBuffer,
|
var vertex_buffer: *sdl.GPUBuffer = undefined;
|
||||||
vertex_buffer_capacity: usize,
|
var vertex_buffer_capacity: usize = undefined;
|
||||||
vertex_buffer_used: usize,
|
var vertex_buffer_used: usize = undefined;
|
||||||
|
|
||||||
transfer_buffer: *sdl.GPUTransferBuffer,
|
var transfer_buffer: *sdl.GPUTransferBuffer = undefined;
|
||||||
transfer_buffer_capacity: usize,
|
var transfer_buffer_capacity: usize = undefined;
|
||||||
|
|
||||||
depth_texture: *sdl.GPUTexture,
|
var depth_texture: *sdl.GPUTexture = undefined;
|
||||||
msaa_resolve: *sdl.GPUTexture,
|
var msaa_resolve: *sdl.GPUTexture = undefined;
|
||||||
pipeline: *sdl.GPUGraphicsPipeline,
|
var pipeline: *sdl.GPUGraphicsPipeline = undefined;
|
||||||
|
|
||||||
window_size: [2]u32,
|
var window_size: [2]u32 = undefined;
|
||||||
|
|
||||||
camera: Camera,
|
pub var camera: Camera = undefined;
|
||||||
|
|
||||||
to_resize: ?[2]u32 = null,
|
var to_resize: ?[2]u32 = null;
|
||||||
|
|
||||||
const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024;
|
const VERTEX_BUFFER_DEFAULT_CAPACITY = 1024;
|
||||||
const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2;
|
const VERTEX_BUFFER_GROWTH_MULTIPLIER = 2;
|
||||||
const TRANSFER_BUFFER_DEFAULT_CAPACITY = 1024;
|
const TRANSFER_BUFFER_DEFAULT_CAPACITY = 1024;
|
||||||
const BYTES_PER_VERTEX = 5 * 4;
|
const BYTES_PER_VERTEX = 5 * 4;
|
||||||
|
|
||||||
const Self = @This();
|
const Graphics = @This();
|
||||||
pub fn create() GameError!Self {
|
pub fn create() void {
|
||||||
// Init
|
// Init
|
||||||
if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) return GameError.SdlError;
|
if (!sdl.Init(sdl.INIT_VIDEO | sdl.INIT_EVENTS)) err.sdl();
|
||||||
|
|
||||||
// Window and Renderer
|
// Window and Renderer
|
||||||
var renderer: ?*sdl.Renderer = null;
|
|
||||||
var window: ?*sdl.Window = null;
|
|
||||||
|
|
||||||
if (!sdl.CreateWindowAndRenderer(
|
if (!sdl.CreateWindowAndRenderer(
|
||||||
"",
|
"",
|
||||||
1600,
|
1600,
|
||||||
900,
|
900,
|
||||||
sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE,
|
sdl.WINDOW_VULKAN | sdl.WINDOW_RESIZABLE,
|
||||||
&window,
|
@ptrCast(&Graphics.window),
|
||||||
&renderer,
|
@ptrCast(&Graphics.renderer),
|
||||||
)) return GameError.SdlError;
|
)) err.sdl();
|
||||||
errdefer sdl.DestroyRenderer(renderer);
|
Graphics.window_size = .{ 1600, 900 };
|
||||||
errdefer sdl.DestroyWindow(window);
|
|
||||||
|
|
||||||
if (!sdl.SetRenderVSync(renderer, sdl.RENDERER_VSYNC_ADAPTIVE)) return GameError.SdlError;
|
if (!sdl.SetRenderVSync(renderer, sdl.RENDERER_VSYNC_ADAPTIVE)) err.sdl();
|
||||||
|
|
||||||
// Device
|
// Device
|
||||||
const device = sdl.CreateGPUDevice(
|
Graphics.device = sdl.CreateGPUDevice(
|
||||||
sdl.GPU_SHADERFORMAT_SPIRV,
|
sdl.GPU_SHADERFORMAT_SPIRV,
|
||||||
@import("builtin").mode == .Debug,
|
@import("builtin").mode == .Debug,
|
||||||
null,
|
null,
|
||||||
) orelse return GameError.SdlError;
|
) orelse err.sdl();
|
||||||
errdefer sdl.DestroyGPUDevice(device);
|
|
||||||
|
|
||||||
// Claim
|
// Claim
|
||||||
if (!sdl.ClaimWindowForGPUDevice(device, window)) return GameError.SdlError;
|
if (!sdl.ClaimWindowForGPUDevice(Graphics.device, Graphics.window)) err.sdl();
|
||||||
errdefer sdl.ReleaseWindowFromGPUDevice(device, window);
|
|
||||||
|
|
||||||
const shader_vert = try loadShader(
|
Graphics.shader_vert = loadShader(
|
||||||
device,
|
|
||||||
"data/shaders/basic.vert",
|
"data/shaders/basic.vert",
|
||||||
.{
|
.{
|
||||||
.entrypoint = "main",
|
.entrypoint = "main",
|
||||||
@@ -92,10 +85,8 @@ pub fn create() GameError!Self {
|
|||||||
.num_uniform_buffers = 2,
|
.num_uniform_buffers = 2,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
errdefer sdl.ReleaseGPUShader(device, shader_vert);
|
|
||||||
|
|
||||||
const shader_frag = try loadShader(
|
Graphics.shader_frag = loadShader(
|
||||||
device,
|
|
||||||
"data/shaders/basic.frag",
|
"data/shaders/basic.frag",
|
||||||
.{
|
.{
|
||||||
.entrypoint = "main",
|
.entrypoint = "main",
|
||||||
@@ -104,37 +95,34 @@ pub fn create() GameError!Self {
|
|||||||
.num_samplers = 1,
|
.num_samplers = 1,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
errdefer sdl.ReleaseGPUShader(device, shader_frag);
|
|
||||||
|
|
||||||
const vertex_buffer = sdl.CreateGPUBuffer(device, &.{
|
Graphics.vertex_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
|
||||||
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
|
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
|
||||||
.size = VERTEX_BUFFER_DEFAULT_CAPACITY,
|
.size = VERTEX_BUFFER_DEFAULT_CAPACITY,
|
||||||
}) orelse return GameError.SdlError;
|
}) orelse err.sdl();
|
||||||
errdefer sdl.ReleaseGPUBuffer(device, vertex_buffer);
|
Graphics.vertex_buffer_capacity = VERTEX_BUFFER_DEFAULT_CAPACITY;
|
||||||
|
Graphics.vertex_buffer_used = 0;
|
||||||
|
|
||||||
const transfer_buffer = sdl.CreateGPUTransferBuffer(device, &.{
|
Graphics.transfer_buffer = sdl.CreateGPUTransferBuffer(Graphics.device, &.{
|
||||||
.size = TRANSFER_BUFFER_DEFAULT_CAPACITY,
|
.size = TRANSFER_BUFFER_DEFAULT_CAPACITY,
|
||||||
.usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD | sdl.GPU_TRANSFERBUFFERUSAGE_DOWNLOAD,
|
.usage = sdl.GPU_TRANSFERBUFFERUSAGE_UPLOAD | sdl.GPU_TRANSFERBUFFERUSAGE_DOWNLOAD,
|
||||||
}) orelse return GameError.SdlError;
|
}) orelse err.sdl();
|
||||||
errdefer sdl.ReleaseGPUTransferBuffer(device, transfer_buffer);
|
Graphics.transfer_buffer_capacity = TRANSFER_BUFFER_DEFAULT_CAPACITY;
|
||||||
|
|
||||||
const target_format = sdl.GetGPUSwapchainTextureFormat(device, window);
|
const target_format = sdl.GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
|
||||||
if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) return GameError.SdlError;
|
if (target_format == sdl.GPU_TEXTUREFORMAT_INVALID) err.sdl();
|
||||||
|
|
||||||
// TODO: Clean
|
// TODO: Clean
|
||||||
var window_width: c_int = 1;
|
var window_width: c_int = 1;
|
||||||
var window_height: c_int = 1;
|
var window_height: c_int = 1;
|
||||||
if (!sdl.GetWindowSizeInPixels(window, &window_width, &window_height)) return GameError.SdlError;
|
if (!sdl.GetWindowSizeInPixels(Graphics.window, &window_width, &window_height)) err.sdl();
|
||||||
|
|
||||||
const depth_texture = try createDepthTexture(device, @intCast(window_width), @intCast(window_height));
|
Graphics.depth_texture = createDepthTexture(@intCast(window_width), @intCast(window_height));
|
||||||
errdefer sdl.ReleaseGPUTexture(device, depth_texture);
|
Graphics.msaa_resolve = createTexture(@intCast(window_width), @intCast(window_height), target_format);
|
||||||
|
|
||||||
const msaa_resolve = try createTexture(device, @intCast(window_width), @intCast(window_height), target_format);
|
Graphics.pipeline = sdl.CreateGPUGraphicsPipeline(Graphics.device, &.{
|
||||||
errdefer sdl.ReleaseGPUTexture(device, msaa_resolve);
|
.vertex_shader = Graphics.shader_vert,
|
||||||
|
.fragment_shader = Graphics.shader_frag,
|
||||||
const pipeline = sdl.CreateGPUGraphicsPipeline(device, &.{
|
|
||||||
.vertex_shader = shader_vert,
|
|
||||||
.fragment_shader = shader_frag,
|
|
||||||
.vertex_input_state = .{
|
.vertex_input_state = .{
|
||||||
.vertex_buffer_descriptions = &.{
|
.vertex_buffer_descriptions = &.{
|
||||||
.slot = 0,
|
.slot = 0,
|
||||||
@@ -173,68 +161,42 @@ pub fn create() GameError!Self {
|
|||||||
.num_color_targets = 1,
|
.num_color_targets = 1,
|
||||||
.has_depth_stencil_target = true,
|
.has_depth_stencil_target = true,
|
||||||
},
|
},
|
||||||
}) orelse return GameError.SdlError;
|
}) orelse err.sdl();
|
||||||
errdefer sdl.ReleaseGPUGraphicsPipeline(pipeline);
|
|
||||||
|
|
||||||
return .{
|
Graphics.camera = Camera{
|
||||||
.window = window.?,
|
.transform = .{},
|
||||||
.renderer = renderer.?,
|
.near = 1.0,
|
||||||
.device = device,
|
.far = 1024.0,
|
||||||
|
.lens = 1.5,
|
||||||
.command_buffer = null,
|
.aspect = 16.0 / 9.0,
|
||||||
.render_pass = null,
|
|
||||||
|
|
||||||
.shader_vert = shader_vert,
|
|
||||||
.shader_frag = shader_frag,
|
|
||||||
|
|
||||||
.vertex_buffer = vertex_buffer,
|
|
||||||
.vertex_buffer_capacity = VERTEX_BUFFER_DEFAULT_CAPACITY,
|
|
||||||
.vertex_buffer_used = 0,
|
|
||||||
|
|
||||||
.transfer_buffer = transfer_buffer,
|
|
||||||
.transfer_buffer_capacity = TRANSFER_BUFFER_DEFAULT_CAPACITY,
|
|
||||||
|
|
||||||
.depth_texture = depth_texture,
|
|
||||||
.msaa_resolve = msaa_resolve,
|
|
||||||
.pipeline = pipeline,
|
|
||||||
|
|
||||||
.window_size = .{ 1600, 900 },
|
|
||||||
|
|
||||||
.camera = Camera{
|
|
||||||
.transform = .{},
|
|
||||||
.near = 1.0,
|
|
||||||
.far = 1024.0,
|
|
||||||
.lens = 1.5,
|
|
||||||
.aspect = 16.0 / 9.0,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(self: *Self) void {
|
pub fn destroy() void {
|
||||||
sdl.ReleaseWindowFromGPUDevice(self.device, self.window);
|
sdl.ReleaseWindowFromGPUDevice(Graphics.device, Graphics.window);
|
||||||
sdl.DestroyRenderer(self.renderer);
|
sdl.DestroyRenderer(Graphics.renderer);
|
||||||
sdl.DestroyWindow(self.window);
|
sdl.DestroyWindow(Graphics.window);
|
||||||
|
|
||||||
sdl.ReleaseGPUGraphicsPipeline(self.device, self.pipeline);
|
sdl.ReleaseGPUGraphicsPipeline(Graphics.device, Graphics.pipeline);
|
||||||
sdl.ReleaseGPUTexture(self.device, self.msaa_resolve);
|
sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve);
|
||||||
sdl.ReleaseGPUTexture(self.device, self.depth_texture);
|
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
|
||||||
sdl.ReleaseGPUBuffer(self.device, self.vertex_buffer);
|
sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer);
|
||||||
sdl.ReleaseGPUTransferBuffer(self.device, self.transfer_buffer);
|
sdl.ReleaseGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
|
||||||
|
|
||||||
sdl.ReleaseGPUShader(self.device, self.shader_vert);
|
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_vert);
|
||||||
sdl.ReleaseGPUShader(self.device, self.shader_frag);
|
sdl.ReleaseGPUShader(Graphics.device, Graphics.shader_frag);
|
||||||
|
|
||||||
if (self.command_buffer != null) {
|
if (Graphics.command_buffer != null) {
|
||||||
_ = sdl.CancelGPUCommandBuffer(self.command_buffer);
|
_ = sdl.CancelGPUCommandBuffer(Graphics.command_buffer);
|
||||||
self.command_buffer = null;
|
Graphics.command_buffer = null;
|
||||||
}
|
}
|
||||||
sdl.DestroyGPUDevice(self.device);
|
sdl.DestroyGPUDevice(Graphics.device);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const u8) GameError!Texture {
|
pub fn loadTexture(width: u32, height: u32, texture_bytes: []const u8) Texture {
|
||||||
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(self.device, self.window);
|
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
|
||||||
|
|
||||||
const texture = sdl.CreateGPUTexture(self.device, &sdl.GPUTextureCreateInfo{
|
const texture = sdl.CreateGPUTexture(Graphics.device, &sdl.GPUTextureCreateInfo{
|
||||||
.format = target_format,
|
.format = target_format,
|
||||||
.layer_count_or_depth = 1,
|
.layer_count_or_depth = 1,
|
||||||
.width = width,
|
.width = width,
|
||||||
@@ -242,25 +204,22 @@ pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const
|
|||||||
.num_levels = 1,
|
.num_levels = 1,
|
||||||
.sample_count = sdl.GPU_SAMPLECOUNT_1,
|
.sample_count = sdl.GPU_SAMPLECOUNT_1,
|
||||||
.usage = sdl.GPU_TEXTUREUSAGE_SAMPLER,
|
.usage = sdl.GPU_TEXTUREUSAGE_SAMPLER,
|
||||||
}) orelse return GameError.SdlError;
|
}) orelse err.sdl();
|
||||||
errdefer sdl.ReleaseGPUTexture(self.device, texture);
|
|
||||||
|
|
||||||
const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError;
|
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
|
||||||
{
|
{
|
||||||
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
|
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl();
|
||||||
|
|
||||||
const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError;
|
|
||||||
defer sdl.EndGPUCopyPass(copy_pass);
|
defer sdl.EndGPUCopyPass(copy_pass);
|
||||||
|
|
||||||
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(self.device, self.transfer_buffer, false) orelse return GameError.SdlError);
|
const map: [*]u8 = @ptrCast(sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl());
|
||||||
@memcpy(map, texture_bytes);
|
@memcpy(map, texture_bytes);
|
||||||
sdl.UnmapGPUTransferBuffer(self.device, self.transfer_buffer);
|
sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
|
||||||
|
|
||||||
sdl.UploadToGPUTexture(copy_pass, &sdl.GPUTextureTransferInfo{
|
sdl.UploadToGPUTexture(copy_pass, &sdl.GPUTextureTransferInfo{
|
||||||
.offset = 0,
|
.offset = 0,
|
||||||
.pixels_per_row = width,
|
.pixels_per_row = width,
|
||||||
.rows_per_layer = height,
|
.rows_per_layer = height,
|
||||||
.transfer_buffer = self.transfer_buffer,
|
.transfer_buffer = Graphics.transfer_buffer,
|
||||||
}, &sdl.GPUTextureRegion{
|
}, &sdl.GPUTextureRegion{
|
||||||
.texture = texture,
|
.texture = texture,
|
||||||
.mip_level = 0,
|
.mip_level = 0,
|
||||||
@@ -273,15 +232,15 @@ pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const
|
|||||||
.d = 1,
|
.d = 1,
|
||||||
}, false);
|
}, false);
|
||||||
}
|
}
|
||||||
if (!sdl.SubmitGPUCommandBuffer(command_buffer)) return GameError.SdlError;
|
if (!sdl.SubmitGPUCommandBuffer(temp_command_buffer)) err.sdl();
|
||||||
|
|
||||||
const sampler = sdl.CreateGPUSampler(self.device, &sdl.GPUSamplerCreateInfo{
|
const sampler = sdl.CreateGPUSampler(Graphics.device, &sdl.GPUSamplerCreateInfo{
|
||||||
.address_mode_u = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
.address_mode_u = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
||||||
.address_mode_v = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
.address_mode_v = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
||||||
.address_mode_w = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
.address_mode_w = sdl.GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE,
|
||||||
.mag_filter = sdl.GPU_FILTER_NEAREST,
|
.mag_filter = sdl.GPU_FILTER_NEAREST,
|
||||||
.min_filter = sdl.GPU_FILTER_LINEAR,
|
.min_filter = sdl.GPU_FILTER_LINEAR,
|
||||||
}) orelse return GameError.SdlError;
|
}) orelse err.sdl();
|
||||||
|
|
||||||
return Texture{
|
return Texture{
|
||||||
.texture = texture,
|
.texture = texture,
|
||||||
@@ -289,44 +248,47 @@ pub fn loadTexture(self: *Self, width: u32, height: u32, texture_bytes: []const
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn loadMesh(self: *Self, mesh_bytes: []const u8) GameError!Mesh {
|
pub fn unloadTexture(texture: Texture) void {
|
||||||
std.debug.assert(mesh_bytes.len < self.transfer_buffer_capacity);
|
sdl.ReleaseGPUSampler(Graphics.device, texture.sampler);
|
||||||
|
sdl.ReleaseGPUTexture(Graphics.device, texture.texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn loadMesh(mesh_bytes: []const u8) Mesh {
|
||||||
|
std.debug.assert(mesh_bytes.len < Graphics.transfer_buffer_capacity);
|
||||||
|
|
||||||
var size_mult: usize = 1;
|
var size_mult: usize = 1;
|
||||||
while (self.vertex_buffer_used + mesh_bytes.len > self.vertex_buffer_capacity) {
|
while (Graphics.vertex_buffer_used + mesh_bytes.len > Graphics.vertex_buffer_capacity * size_mult) {
|
||||||
size_mult *= VERTEX_BUFFER_GROWTH_MULTIPLIER;
|
size_mult *= VERTEX_BUFFER_GROWTH_MULTIPLIER;
|
||||||
}
|
}
|
||||||
if (size_mult > 1) {
|
if (size_mult > 1) {
|
||||||
try self.growVertexBuffer(self.vertex_buffer_capacity * size_mult);
|
Graphics.growVertexBuffer(Graphics.vertex_buffer_capacity * size_mult);
|
||||||
}
|
}
|
||||||
|
|
||||||
const map = sdl.MapGPUTransferBuffer(self.device, self.transfer_buffer, false) orelse return GameError.SdlError;
|
const map = sdl.MapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer, false) orelse err.sdl();
|
||||||
@memcpy(@as([*]u8, @ptrCast(map)), mesh_bytes);
|
@memcpy(@as([*]u8, @ptrCast(map)), mesh_bytes);
|
||||||
sdl.UnmapGPUTransferBuffer(self.device, self.transfer_buffer);
|
sdl.UnmapGPUTransferBuffer(Graphics.device, Graphics.transfer_buffer);
|
||||||
|
|
||||||
const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError;
|
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
|
||||||
const fence = blk: {
|
const fence = blk: {
|
||||||
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
|
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer) orelse err.sdl();
|
||||||
|
|
||||||
const copy_pass = sdl.BeginGPUCopyPass(command_buffer) orelse return GameError.SdlError;
|
|
||||||
sdl.UploadToGPUBuffer(copy_pass, &.{
|
sdl.UploadToGPUBuffer(copy_pass, &.{
|
||||||
.transfer_buffer = self.transfer_buffer,
|
.transfer_buffer = Graphics.transfer_buffer,
|
||||||
.offset = 0,
|
.offset = 0,
|
||||||
}, &.{
|
}, &.{
|
||||||
.buffer = self.vertex_buffer,
|
.buffer = Graphics.vertex_buffer,
|
||||||
.offset = @intCast(self.vertex_buffer_used),
|
.offset = @intCast(Graphics.vertex_buffer_used),
|
||||||
.size = @intCast(mesh_bytes.len),
|
.size = @intCast(mesh_bytes.len),
|
||||||
}, false);
|
}, false);
|
||||||
sdl.EndGPUCopyPass(copy_pass);
|
sdl.EndGPUCopyPass(copy_pass);
|
||||||
|
|
||||||
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return GameError.SdlError;
|
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl();
|
||||||
};
|
};
|
||||||
defer sdl.ReleaseGPUFence(self.device, fence);
|
defer sdl.ReleaseGPUFence(Graphics.device, fence);
|
||||||
|
|
||||||
if (!sdl.WaitForGPUFences(self.device, true, &fence, 1)) return GameError.SdlError;
|
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl();
|
||||||
|
|
||||||
const vertex_start = self.vertex_buffer_used;
|
const vertex_start = Graphics.vertex_buffer_used;
|
||||||
self.vertex_buffer_used += mesh_bytes.len;
|
Graphics.vertex_buffer_used += mesh_bytes.len;
|
||||||
|
|
||||||
return Mesh{
|
return Mesh{
|
||||||
.vertex_start = vertex_start / BYTES_PER_VERTEX,
|
.vertex_start = vertex_start / BYTES_PER_VERTEX,
|
||||||
@@ -334,38 +296,34 @@ pub fn loadMesh(self: *Self, mesh_bytes: []const u8) GameError!Mesh {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unloadMesh(self: *Self, mesh: Mesh) void {
|
pub fn unloadMesh(mesh: Mesh) void {
|
||||||
// TODO: free some memory
|
// TODO: free some memory
|
||||||
_ = self;
|
|
||||||
_ = &mesh;
|
_ = &mesh;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn growVertexBuffer(self: *Self, new_size: usize) GameError!void {
|
fn growVertexBuffer(new_size: usize) void {
|
||||||
const new_buffer = sdl.CreateGPUBuffer(self.device, &.{
|
const new_buffer = sdl.CreateGPUBuffer(Graphics.device, &.{
|
||||||
.size = @intCast(new_size),
|
.size = @intCast(new_size),
|
||||||
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
|
.usage = sdl.GPU_BUFFERUSAGE_VERTEX,
|
||||||
}) orelse return GameError.SdlError;
|
}) orelse err.sdl();
|
||||||
errdefer sdl.ReleaseGPUBuffer(self.device, new_buffer);
|
|
||||||
|
|
||||||
const command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError;
|
const temp_command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
|
||||||
|
|
||||||
const fence = blk: {
|
const fence = blk: {
|
||||||
errdefer _ = sdl.CancelGPUCommandBuffer(command_buffer);
|
const copy_pass = sdl.BeginGPUCopyPass(temp_command_buffer);
|
||||||
|
|
||||||
const copy_pass = sdl.BeginGPUCopyPass(command_buffer);
|
|
||||||
var copied: usize = 0;
|
var copied: usize = 0;
|
||||||
while (copied < self.vertex_buffer_used) {
|
while (copied < Graphics.vertex_buffer_used) {
|
||||||
const to_transer = @min(self.vertex_buffer_used - copied, self.transfer_buffer_capacity);
|
const to_transer = @min(Graphics.vertex_buffer_used - copied, Graphics.transfer_buffer_capacity);
|
||||||
sdl.DownloadFromGPUBuffer(copy_pass, &.{
|
sdl.DownloadFromGPUBuffer(copy_pass, &.{
|
||||||
.buffer = self.vertex_buffer,
|
.buffer = Graphics.vertex_buffer,
|
||||||
.offset = @intCast(copied),
|
.offset = @intCast(copied),
|
||||||
.size = @intCast(to_transer),
|
.size = @intCast(to_transer),
|
||||||
}, &.{
|
}, &.{
|
||||||
.transfer_buffer = self.transfer_buffer,
|
.transfer_buffer = Graphics.transfer_buffer,
|
||||||
.offset = 0,
|
.offset = 0,
|
||||||
});
|
});
|
||||||
sdl.UploadToGPUBuffer(copy_pass, &.{
|
sdl.UploadToGPUBuffer(copy_pass, &.{
|
||||||
.transfer_buffer = self.transfer_buffer,
|
.transfer_buffer = Graphics.transfer_buffer,
|
||||||
.offset = 0,
|
.offset = 0,
|
||||||
}, &.{
|
}, &.{
|
||||||
.buffer = new_buffer,
|
.buffer = new_buffer,
|
||||||
@@ -376,36 +334,36 @@ fn growVertexBuffer(self: *Self, new_size: usize) GameError!void {
|
|||||||
}
|
}
|
||||||
sdl.EndGPUCopyPass(copy_pass);
|
sdl.EndGPUCopyPass(copy_pass);
|
||||||
|
|
||||||
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(command_buffer) orelse return GameError.SdlError;
|
break :blk sdl.SubmitGPUCommandBufferAndAcquireFence(temp_command_buffer) orelse err.sdl();
|
||||||
};
|
};
|
||||||
defer sdl.ReleaseGPUFence(self.device, fence);
|
defer sdl.ReleaseGPUFence(Graphics.device, fence);
|
||||||
|
|
||||||
if (!sdl.WaitForGPUFences(self.device, true, &fence, 1)) return GameError.SdlError;
|
if (!sdl.WaitForGPUFences(Graphics.device, true, &fence, 1)) err.sdl();
|
||||||
|
|
||||||
sdl.ReleaseGPUBuffer(self.device, self.vertex_buffer);
|
sdl.ReleaseGPUBuffer(Graphics.device, Graphics.vertex_buffer);
|
||||||
self.vertex_buffer = new_buffer;
|
Graphics.vertex_buffer = new_buffer;
|
||||||
self.vertex_buffer_capacity = new_size;
|
Graphics.vertex_buffer_capacity = new_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// If window is minimized returns `false`, `render_pass` remains null
|
/// If window is minimized returns `false`, `render_pass` remains null
|
||||||
/// Otherwise `command_buffer` and `render_pass` are both set
|
/// Otherwise `command_buffer` and `render_pass` are both set
|
||||||
pub fn beginDraw(self: *Self) GameError!bool {
|
pub fn beginDraw() bool {
|
||||||
self.command_buffer = sdl.AcquireGPUCommandBuffer(self.device) orelse return GameError.SdlError;
|
Graphics.command_buffer = sdl.AcquireGPUCommandBuffer(Graphics.device) orelse err.sdl();
|
||||||
if (self.to_resize) |new_size| {
|
if (Graphics.to_resize) |new_size| {
|
||||||
try self.resetTextures(new_size[0], new_size[1]);
|
Graphics.resetTextures(new_size[0], new_size[1]);
|
||||||
self.camera.aspect = @as(f32, @floatFromInt(new_size[0])) / @as(f32, @floatFromInt(new_size[1]));
|
Graphics.camera.aspect = @as(f32, @floatFromInt(new_size[0])) / @as(f32, @floatFromInt(new_size[1]));
|
||||||
self.window_size = new_size;
|
Graphics.window_size = new_size;
|
||||||
self.to_resize = null;
|
Graphics.to_resize = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
var render_target: ?*sdl.GPUTexture = null;
|
var render_target: ?*sdl.GPUTexture = null;
|
||||||
var width: u32 = 0;
|
var width: u32 = 0;
|
||||||
var height: u32 = 0;
|
var height: u32 = 0;
|
||||||
if (!sdl.WaitAndAcquireGPUSwapchainTexture(self.command_buffer, self.window, &render_target, &width, &height)) return GameError.SdlError;
|
if (!sdl.WaitAndAcquireGPUSwapchainTexture(Graphics.command_buffer, Graphics.window, &render_target, &width, &height)) err.sdl();
|
||||||
// Hidden
|
// Hidden
|
||||||
if (render_target == null) return false;
|
if (render_target == null) return false;
|
||||||
|
|
||||||
self.render_pass = sdl.BeginGPURenderPass(self.command_buffer, &.{
|
Graphics.render_pass = sdl.BeginGPURenderPass(Graphics.command_buffer, &.{
|
||||||
.clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
|
.clear_color = .{ .r = 0.0, .g = 0.0, .b = 0.0, .a = 1.0 },
|
||||||
.cycle = false,
|
.cycle = false,
|
||||||
.load_op = sdl.GPU_LOADOP_CLEAR,
|
.load_op = sdl.GPU_LOADOP_CLEAR,
|
||||||
@@ -413,57 +371,57 @@ pub fn beginDraw(self: *Self) GameError!bool {
|
|||||||
// .store_op = sdl.GPU_STOREOP_STORE,
|
// .store_op = sdl.GPU_STOREOP_STORE,
|
||||||
.resolve_texture = render_target,
|
.resolve_texture = render_target,
|
||||||
.mip_level = 0,
|
.mip_level = 0,
|
||||||
.texture = self.msaa_resolve,
|
.texture = Graphics.msaa_resolve,
|
||||||
}, 1, &.{
|
}, 1, &.{
|
||||||
.clear_depth = 1.0,
|
.clear_depth = 1.0,
|
||||||
.load_op = sdl.GPU_LOADOP_CLEAR,
|
.load_op = sdl.GPU_LOADOP_CLEAR,
|
||||||
.store_op = sdl.GPU_STOREOP_DONT_CARE,
|
.store_op = sdl.GPU_STOREOP_DONT_CARE,
|
||||||
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
|
.stencil_load_op = sdl.GPU_STOREOP_DONT_CARE,
|
||||||
.stencil_store_op = sdl.GPU_STOREOP_DONT_CARE,
|
.stencil_store_op = sdl.GPU_STOREOP_DONT_CARE,
|
||||||
.texture = self.depth_texture,
|
.texture = Graphics.depth_texture,
|
||||||
}) orelse return GameError.SdlError;
|
}) orelse err.sdl();
|
||||||
|
|
||||||
sdl.BindGPUGraphicsPipeline(self.render_pass, self.pipeline);
|
sdl.BindGPUGraphicsPipeline(Graphics.render_pass, Graphics.pipeline);
|
||||||
sdl.BindGPUVertexBuffers(self.render_pass, 0, &.{ .offset = 0, .buffer = self.vertex_buffer }, 1);
|
sdl.BindGPUVertexBuffers(Graphics.render_pass, 0, &.{ .offset = 0, .buffer = Graphics.vertex_buffer }, 1);
|
||||||
sdl.PushGPUVertexUniformData(self.command_buffer, 0, &self.camera.matrix(), 16 * 4);
|
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 0, &Graphics.camera.matrix(), 16 * 4);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn drawMesh(self: *Self, mesh: Mesh, texture: Texture, transform: Transform) GameError!void {
|
pub fn drawMesh(mesh: Mesh, texture: Texture, transform: Transform) void {
|
||||||
if (self.render_pass == null) return;
|
if (Graphics.render_pass == null) return;
|
||||||
|
|
||||||
sdl.PushGPUVertexUniformData(self.command_buffer, 1, &transform.matrix(), 16 * 4);
|
sdl.PushGPUVertexUniformData(Graphics.command_buffer, 1, &transform.matrix(), 16 * 4);
|
||||||
sdl.BindGPUFragmentSamplers(self.render_pass, 0, &sdl.GPUTextureSamplerBinding{
|
sdl.BindGPUFragmentSamplers(Graphics.render_pass, 0, &sdl.GPUTextureSamplerBinding{
|
||||||
.texture = texture.texture,
|
.texture = texture.texture,
|
||||||
.sampler = texture.sampler,
|
.sampler = texture.sampler,
|
||||||
}, 1);
|
}, 1);
|
||||||
sdl.DrawGPUPrimitives(self.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0);
|
sdl.DrawGPUPrimitives(Graphics.render_pass, @intCast(mesh.vertex_count), 1, @intCast(mesh.vertex_start), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn endDraw(self: *Self) GameError!void {
|
pub fn endDraw() void {
|
||||||
defer self.command_buffer = null;
|
defer Graphics.command_buffer = null;
|
||||||
defer self.render_pass = null;
|
defer Graphics.render_pass = null;
|
||||||
if (self.render_pass) |render_pass| {
|
if (Graphics.render_pass) |pass| {
|
||||||
sdl.EndGPURenderPass(render_pass);
|
sdl.EndGPURenderPass(pass);
|
||||||
}
|
}
|
||||||
if (!sdl.SubmitGPUCommandBuffer(self.command_buffer)) return GameError.SdlError;
|
if (!sdl.SubmitGPUCommandBuffer(Graphics.command_buffer)) err.sdl();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn loadShader(device: *sdl.GPUDevice, path: []const u8, info: sdl.GPUShaderCreateInfo) GameError!*sdl.GPUShader {
|
fn loadShader(path: []const u8, info: sdl.GPUShaderCreateInfo) *sdl.GPUShader {
|
||||||
const file = std.fs.cwd().openFile(path, .{}) catch return GameError.OSError;
|
const file = std.fs.cwd().openFile(path, .{}) catch |e| err.file(e, path);
|
||||||
defer file.close();
|
defer file.close();
|
||||||
|
|
||||||
const code = file.readToEndAllocOptions(std.heap.c_allocator, 1024 * 1024 * 1024, null, .@"1", 0) catch return GameError.OSError;
|
const code = file.readToEndAllocOptions(std.heap.c_allocator, std.math.maxInt(usize), null, .@"1", 0) catch |e| err.file(e, path);
|
||||||
defer std.heap.c_allocator.free(code);
|
defer std.heap.c_allocator.free(code);
|
||||||
|
|
||||||
var updated_info = info;
|
var updated_info = info;
|
||||||
updated_info.code = code;
|
updated_info.code = code;
|
||||||
updated_info.code_size = code.len;
|
updated_info.code_size = code.len;
|
||||||
return sdl.CreateGPUShader(device, &updated_info) orelse return GameError.SdlError;
|
return sdl.CreateGPUShader(device, &updated_info) orelse err.sdl();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createDepthTexture(device: *sdl.GPUDevice, width: u32, height: u32) GameError!*sdl.GPUTexture {
|
fn createDepthTexture(width: u32, height: u32) *sdl.GPUTexture {
|
||||||
return sdl.CreateGPUTexture(device, &.{
|
return sdl.CreateGPUTexture(device, &.{
|
||||||
.format = sdl.GPU_TEXTUREFORMAT_D16_UNORM,
|
.format = sdl.GPU_TEXTUREFORMAT_D16_UNORM,
|
||||||
.layer_count_or_depth = 1,
|
.layer_count_or_depth = 1,
|
||||||
@@ -472,10 +430,10 @@ fn createDepthTexture(device: *sdl.GPUDevice, width: u32, height: u32) GameError
|
|||||||
.num_levels = 1,
|
.num_levels = 1,
|
||||||
.sample_count = sdl.GPU_SAMPLECOUNT_4,
|
.sample_count = sdl.GPU_SAMPLECOUNT_4,
|
||||||
.usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
.usage = sdl.GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET,
|
||||||
}) orelse return GameError.SdlError;
|
}) orelse err.sdl();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn createTexture(device: *sdl.GPUDevice, width: u32, height: u32, format: c_uint) GameError!*sdl.GPUTexture {
|
fn createTexture(width: u32, height: u32, format: c_uint) *sdl.GPUTexture {
|
||||||
return sdl.CreateGPUTexture(device, &.{
|
return sdl.CreateGPUTexture(device, &.{
|
||||||
.format = format,
|
.format = format,
|
||||||
.layer_count_or_depth = 1,
|
.layer_count_or_depth = 1,
|
||||||
@@ -484,19 +442,23 @@ fn createTexture(device: *sdl.GPUDevice, width: u32, height: u32, format: c_uint
|
|||||||
.num_levels = 1,
|
.num_levels = 1,
|
||||||
.sample_count = sdl.GPU_SAMPLECOUNT_4,
|
.sample_count = sdl.GPU_SAMPLECOUNT_4,
|
||||||
.usage = sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
|
.usage = sdl.GPU_TEXTUREUSAGE_COLOR_TARGET,
|
||||||
}) orelse return GameError.SdlError;
|
}) orelse err.sdl();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resetTextures(self: *Self, width: u32, height: u32) GameError!void {
|
fn resetTextures(width: u32, height: u32) void {
|
||||||
sdl.ReleaseGPUTexture(self.device, self.depth_texture);
|
sdl.ReleaseGPUTexture(Graphics.device, Graphics.depth_texture);
|
||||||
self.depth_texture = try createDepthTexture(self.device, width, height);
|
Graphics.depth_texture = createDepthTexture(width, height);
|
||||||
|
|
||||||
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(self.device, self.window);
|
const target_format = sdl.SDL_GetGPUSwapchainTextureFormat(Graphics.device, Graphics.window);
|
||||||
|
|
||||||
sdl.ReleaseGPUTexture(self.device, self.msaa_resolve);
|
sdl.ReleaseGPUTexture(Graphics.device, Graphics.msaa_resolve);
|
||||||
self.msaa_resolve = try createTexture(self.device, width, height, target_format);
|
Graphics.msaa_resolve = createTexture(width, height, target_format);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(self: *Self, width: u32, height: u32) void {
|
pub fn resize(width: u32, height: u32) void {
|
||||||
self.to_resize = .{ width, height };
|
Graphics.to_resize = .{ width, height };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn windowId() sdl.WindowID {
|
||||||
|
return sdl.GetWindowID(Graphics.window);
|
||||||
}
|
}
|
||||||
|
20
src/main.zig
20
src/main.zig
@@ -3,7 +3,7 @@ const builtin = @import("builtin");
|
|||||||
const sdl = @import("sdl");
|
const sdl = @import("sdl");
|
||||||
const Game = @import("game.zig");
|
const Game = @import("game.zig");
|
||||||
|
|
||||||
pub fn runGame() !void {
|
pub fn main() void {
|
||||||
var allocator = switch (builtin.mode) {
|
var allocator = switch (builtin.mode) {
|
||||||
.ReleaseSafe, .Debug => std.heap.DebugAllocator(.{}).init,
|
.ReleaseSafe, .Debug => std.heap.DebugAllocator(.{}).init,
|
||||||
.ReleaseFast => std.heap.smp_allocator,
|
.ReleaseFast => std.heap.smp_allocator,
|
||||||
@@ -14,25 +14,13 @@ pub fn runGame() !void {
|
|||||||
else => {},
|
else => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
var game = try Game.init(
|
Game.init(
|
||||||
switch (builtin.mode) {
|
switch (builtin.mode) {
|
||||||
.ReleaseSafe, .Debug => allocator.allocator(),
|
.ReleaseSafe, .Debug => allocator.allocator(),
|
||||||
.ReleaseFast => allocator,
|
.ReleaseFast => allocator,
|
||||||
.ReleaseSmall => allocator,
|
.ReleaseSmall => allocator,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
defer game.deinit();
|
defer Game.deinit();
|
||||||
try game.run();
|
Game.run();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() !void {
|
|
||||||
runGame() catch |err| {
|
|
||||||
switch (err) {
|
|
||||||
error.SdlError => {
|
|
||||||
std.debug.print("SDL Error:\n---\n{s}\n---\n", .{sdl.SDL_GetError()});
|
|
||||||
},
|
|
||||||
else => unreachable,
|
|
||||||
}
|
|
||||||
return err;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
const sdl = @import("sdl");
|
const sdl = @import("sdl");
|
||||||
const key_store = @import("data/keystore.zig");
|
const key_store = @import("data/keystore.zig");
|
||||||
|
|
||||||
buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0),
|
buttons: key_store.KeyStore(@TypeOf(sdl.BUTTON_LEFT), 4, 0) = .{},
|
||||||
|
|
||||||
x: f32,
|
x: f32 = 0,
|
||||||
y: f32,
|
y: f32 = 0,
|
||||||
dx: f32,
|
dx: f32 = 0,
|
||||||
dy: f32,
|
dy: f32 = 0,
|
||||||
|
@@ -41,3 +41,11 @@ pub fn unitsFromDuration(duration: TimeType) f32 {
|
|||||||
pub fn durationFromUnits(units: f32) TimeType {
|
pub fn durationFromUnits(units: f32) TimeType {
|
||||||
return @intFromFloat(@as(f32, @floatFromInt(TIME_UNIT)) * units);
|
return @intFromFloat(@as(f32, @floatFromInt(TIME_UNIT)) * units);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn earliest(a: Time, b: Time) Time {
|
||||||
|
return .{ .clock = @min(a.clock, b.clock) };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plus(time: Time, ticks: TimeType) Time {
|
||||||
|
return .{ .clock = time.clock + ticks };
|
||||||
|
}
|
||||||
|
144
src/world.zig
Normal file
144
src/world.zig
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
const Graphics = @import("graphics.zig");
|
||||||
|
const Entity = @import("entity.zig");
|
||||||
|
const Time = @import("time.zig");
|
||||||
|
|
||||||
|
pub var time: Time = undefined;
|
||||||
|
var next_stop: Time = undefined;
|
||||||
|
var entities: [16]?Entity = undefined;
|
||||||
|
|
||||||
|
pub var plane_mesh: Graphics.Mesh = undefined;
|
||||||
|
pub var cube_mesh: Graphics.Mesh = undefined;
|
||||||
|
pub var texture: Graphics.Texture = undefined;
|
||||||
|
|
||||||
|
const World = @This();
|
||||||
|
pub fn initDebug() void {
|
||||||
|
entities = .{null} ** 16;
|
||||||
|
entities[0] = .{
|
||||||
|
.position = .{ 0, 0 },
|
||||||
|
.player = true,
|
||||||
|
};
|
||||||
|
entities[1] = .{
|
||||||
|
.position = .{ 2, 0 },
|
||||||
|
.enemy = true,
|
||||||
|
.controller = .{
|
||||||
|
.move_units = 0.25,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
entities[2] = .{
|
||||||
|
.position = .{ 3, 0 },
|
||||||
|
.enemy = true,
|
||||||
|
.controller = .{
|
||||||
|
.move_units = 0.25,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
time = Time.ZERO;
|
||||||
|
World.plane_mesh = Graphics.loadMesh(@ptrCast(&PLANE_MESH_DATA));
|
||||||
|
World.cube_mesh = Graphics.loadMesh(@ptrCast(&CUBE_MESH_DATA));
|
||||||
|
World.texture = Graphics.loadTexture(2, 2, @ptrCast(&TEXTURE_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit() void {
|
||||||
|
Graphics.unloadMesh(World.plane_mesh);
|
||||||
|
Graphics.unloadMesh(World.cube_mesh);
|
||||||
|
Graphics.unloadTexture(World.texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn updateReal(delta: f32) void {
|
||||||
|
const update_until = World.time.plus(Time.durationFromUnits(delta));
|
||||||
|
while (!World.time.past(update_until)) {
|
||||||
|
const current = Time.earliest(World.next_stop, update_until);
|
||||||
|
defer World.time = current;
|
||||||
|
|
||||||
|
for (&World.entities) |*entity| {
|
||||||
|
if (entity.*) |*e| e.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(delta: f32) void {
|
||||||
|
Graphics.drawMesh(World.plane_mesh, World.texture, .{ .scale = @splat(5) });
|
||||||
|
for (&World.entities) |*entity| {
|
||||||
|
if (entity.*) |*e| e.draw(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn requestUpdate(at: Time) void {
|
||||||
|
World.next_stop = Time.earliest(at, World.next_stop);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn entityAt(position: @Vector(2, i32)) ?*Entity {
|
||||||
|
for (&World.entities) |*maybe_entity| {
|
||||||
|
if (maybe_entity.*) |*entity| {
|
||||||
|
if (@reduce(.And, entity.position == position))
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn isFree(position: @Vector(2, i32)) bool {
|
||||||
|
return World.entityAt(position) == null;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn getPlayer() ?*Entity {
|
||||||
|
for (&World.entities) |*maybe_entity| {
|
||||||
|
if (maybe_entity.*) |*entity| {
|
||||||
|
if (entity.player)
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CUBE_MESH_DATA = [_]f32{
|
||||||
|
-0.5, 0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, 0.5, -0.5, 0.0, 0.0,
|
||||||
|
-0.5, -0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, -0.5, -0.5, 0.0, 0.0,
|
||||||
|
-0.5, -0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, 0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, 0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, 0.5, 0.5, 0.0, 0.0,
|
||||||
|
0.5, -0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, -0.5, 0.5, 0.0, 0.0,
|
||||||
|
0.5, -0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, 0.5, 0.5, 0.0, 0.0,
|
||||||
|
0.5, 0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, 0.5, 0.5, 0.0, 0.0,
|
||||||
|
0.5, -0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, -0.5, 0.5, 0.0, 0.0,
|
||||||
|
0.5, -0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, 0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, 0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, 0.5, -0.5, 0.0, 0.0,
|
||||||
|
-0.5, -0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, -0.5, -0.5, 0.0, 0.0,
|
||||||
|
-0.5, -0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, 0.5, -0.5, 0.0, 0.0,
|
||||||
|
-0.5, 0.5, 0.5, 0.0, 0.0,
|
||||||
|
0.5, 0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, 0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, 0.5, -0.5, 0.0, 0.0,
|
||||||
|
-0.5, 0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, 0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, -0.5, -0.5, 0.0, 0.0,
|
||||||
|
0.5, -0.5, -0.5, 0.0, 0.0,
|
||||||
|
-0.5, -0.5, 0.5, 0.0, 0.0,
|
||||||
|
0.5, -0.5, 0.5, 0.0, 0.0,
|
||||||
|
-0.5, -0.5, 0.5, 0.0, 0.0,
|
||||||
|
0.5, -0.5, -0.5, 0.0, 0.0,
|
||||||
|
};
|
||||||
|
const PLANE_MESH_DATA = [_]f32{
|
||||||
|
-0.5, -0.5, 0, 0.0, 1.0,
|
||||||
|
0.5, 0.5, 0, 1.0, 0.0,
|
||||||
|
-0.5, 0.5, 0, 0.0, 0.0,
|
||||||
|
0.5, 0.5, 0, 1.0, 0.0,
|
||||||
|
-0.5, -0.5, 0, 0.0, 1.0,
|
||||||
|
0.5, -0.5, 0, 1.0, 1.0,
|
||||||
|
};
|
||||||
|
const TEXTURE_DATA = [_]u8{
|
||||||
|
255, 64, 64, 255,
|
||||||
|
64, 255, 64, 255,
|
||||||
|
64, 64, 255, 255,
|
||||||
|
64, 64, 64, 255,
|
||||||
|
};
|
Reference in New Issue
Block a user