Backlog/projects/headless/main.zig

216 lines
6.4 KiB
Zig

allocator: std.mem.Allocator = undefined,
timeLeft: f64 = 5.0,
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "Headless");
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
};
return self;
}
pub fn prepare(self: *@This()) !void {
_ = self;
for(0..120_000) |i|
{
_ = i;
const e = try core.createEntity();
const x = e.addComponent(core.Scene).?;
x.setMobility(.moveable);
}
// try self.testThing();
}
pub fn tick(self: *@This(), dt: f64) void {
// std.Thread.sleep(1000_000);
self.timeLeft -= dt;
const start = core.getEngineTime();
core.parallelJob(UpdateWorldTransformsJob{}, false, 10) catch unreachable;
// wait 10 ms
while (core.getEngineTime() - start < 0.010) {
// std.Thread.sleep(10_000_000);
}
if (self.timeLeft < 0)
core.exitNow();
}
pub fn deinit(self: *@This()) void {
self.allocator.destroy(self);
}
const Src = std.builtin.SourceLocation;
pub fn Barrier(src: Src) type {
return struct {
pub const Src = src;
pub var count: u32 = 0;
pub var generation: u32 = 0;
pub var mutex: std.Thread.Mutex = .{};
pub var cv: std.Thread.Condition = .{};
pub fn sync(threadId: u32, barrierCount: u32) !void {
_ = threadId;
mutex.lock();
defer mutex.unlock();
count += 1;
if (count == barrierCount) {
// 2 second timeout
count = 0;
generation += 1;
cv.broadcast();
} else {
try cv.timedWait(&mutex, 1000 * 1000 * 1000 * 2);
}
}
};
}
const MultiJob = struct {
threadId: u32 = 0,
threadCount: u32 = 1,
threadName: []const u8,
pub fn func(ctx: @This(), job: *core.JobContext) void {
_ = job;
core.tracy.SetThreadName(@ptrCast(ctx.threadName.ptr));
ctx.loop() catch {};
if (core.getEngine().isShuttingDown()) {}
Barrier(@src()).sync(ctx.threadId, ctx.threadCount) catch unreachable;
}
pub fn loop(ctx: @This()) !void {
while (!core.getEngine().isShuttingDown()) {
try Barrier(@src()).sync(ctx.threadId, ctx.threadCount);
try ctx.tick();
if (ctx.threadId == 0) {
const z = core.tracy.ZoneN(@src(), "Thread 0 sleep");
defer z.End();
core.waitSeconds(0.01);
}
}
}
pub fn tick(ctx: @This()) !void {
const z = core.tracy.ZoneN(@src(), "MultiJob Tick");
defer z.End();
// std.Thread.sleep(100_000 * ctx.threadId);
try Barrier(@src()).sync(ctx.threadId, ctx.threadCount);
}
};
const UpdateWorldTransformsJob = struct {
world: ?*anyopaque = null,
pub fn func(self: @This(), thread: *core.ThreadContext) void {
const z = core.tracy.ZoneN(@src(), "Transform Hierarchy - wide");
defer z.End();
thread.barrier(@src()) catch unreachable;
var outputs: std.ArrayList(core.math.Transform) = .{};
var outputList: std.ArrayList(usize) = .{};
self.updateTransformsHierarchy(thread, &outputs, &outputList) catch |err| switch (err) {
error.OutOfMemory => {
unreachable;
},
// else => {
// thread.abort(@src(), "unknown error", err);
// return;
// },
};
// sync results of output List
thread.barrier(@src()) catch unreachable;
// write out all results, there likely is no issue with false sharing as transforms are exactly 64 bytes
// const dense = self.world.denseScenes();
// for (outputList.items) |i| {
// // dense[i].transform = outputs.items[i];
// }
}
pub fn updateTransformsHierarchy(self: @This(), thread: *core.ThreadContext, outputs: *std.ArrayList(core.math.Transform), outputList: *std.ArrayList(usize)) !void {
_ = outputList;
_ = self;
const z = core.tracy.ZoneN(@src(), "updateTransformsHierarchy");
defer z.End();
//const dense = self.world.denseScenes();
const densePosRot = core.Scene.SceneObjectContainer.denseItems(.posRot);
// everything allocated with thread.scratch is blown away when the thread is complete
try outputs.resize(thread.scratch(), densePosRot.len);
// scan and mark all root nodes for update
const split = thread.splitSlice(core.scene.ScenePosRot, densePosRot);
const locals:[]core.math.Transform = try thread.scratch().alloc(core.math.Transform, split.slice.len);
const z2 = core.tracy.ZoneN(@src(), "WalkAndResolve");
defer z2.End();
for (split.slice, 0..) |posRot, i| {
const index = split.startIndex + i;
_ = index;
locals[i] = posRot.toTransform();
}
//for (split.slice, 0..) |repr, i| {
//}
}
};
// new idea that im thinking i want to do...
//
// game is now responsible for scheduling order of work
//
// eg. prepare_game now has to return a struct that defines a list of phases. eg. the default list looks like,
//
// engine.setEngineTickPhases(&.{
// core.tick,
// // ecs.tick
// });
//
// core. comes with a bunch of prebuilt phases.
pub fn testThing(self: *@This()) !void {
_ = self;
const workerCount = 16;
core.setBarrierCount(workerCount);
for (0..workerCount) |i| {
const name = try std.fmt.allocPrintSentinel(std.heap.c_allocator, "MultiJob{d}", .{i}, 0);
try core.dispatchJob(MultiJob{ .threadName = name, .threadId = @intCast(i), .threadCount = workerCount });
}
// workerCountHint=0
// async==false
// will block until the job is complete, the active thread will also construct a threadContext that picks up one of the parallel tasks (todo implement workstealing)
// try core.dispatchMulti(UpdateWorldTransformsJob{ .world = self.mainWorld }, null, false); // null == use max workers,
}
pub fn main() anyerror!void {
var spec = try backlog.getSpec("headless");
// try spec.put("useGPA", .{ .boolean = false });
_ = backlog.startEngine(&NeonObjectTable, &spec);
}
const std = @import("std");
const backlog = @import("backlog");
const core = backlog.core;