278 lines
8.7 KiB
Zig
278 lines
8.7 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, 1) 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 BarrierCV(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);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn BarrierSpin(src: Src) type {
|
|
return struct {
|
|
pub const Src = src;
|
|
|
|
pub var count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0);
|
|
pub var park: std.atomic.Value(bool) = std.atomic.Value(bool).init(false);
|
|
|
|
pub fn sync(threadId: u32, barrierCount: u32) !void {
|
|
_ = threadId;
|
|
|
|
const c = count.fetchAdd(1, .release);
|
|
if (c + 1 == barrierCount) {
|
|
park.store(true, .release);
|
|
while (count.load(.acquire) > 1) {}
|
|
_ = count.fetchSub(1, .release);
|
|
park.store(false, .release);
|
|
} else {
|
|
while (park.load(.acquire) == false) {}
|
|
_ = count.fetchSub(1, .seq_cst);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
pub const Barrier = BarrierSpin;
|
|
|
|
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;
|
|
|
|
const outputs = core.get(core.SceneSystem).getOutputForWorker(thread.threadId) catch return;
|
|
const outputList = core.get(core.SceneSystem).getOutputList(thread.threadId) catch return;
|
|
|
|
self.updateTransformsHierarchy(thread, outputs, outputList) catch |err| switch (err) {
|
|
error.OutOfMemory => {
|
|
unreachable;
|
|
},
|
|
// else => {
|
|
// thread.abort(@src(), "unknown error", err);
|
|
// return;
|
|
// },
|
|
};
|
|
|
|
const denseRepr = core.Scene.SceneObjectContainer.denseItems(._repr);
|
|
|
|
const z3 = core.tracy.ZoneN(@src(), "Merge Outputs");
|
|
// merge outputs
|
|
// current a noop just iterate over the outputlist
|
|
for (outputList.items) |outIndex| {
|
|
if (denseRepr[outIndex].merge.cmpxchgStrong(false, true, .seq_cst, .acquire) != null) {
|
|
denseRepr[outIndex].transform = outputs[outIndex];
|
|
}
|
|
}
|
|
z3.End();
|
|
thread.barrier(@src()) catch unreachable;
|
|
}
|
|
|
|
fn updateTransform(
|
|
self: @This(),
|
|
thread: *core.ThreadContext,
|
|
index: usize,
|
|
densePosRot: []core.scene.ScenePosRot,
|
|
denseRepr: []core.scene.SceneObjectRepr,
|
|
outputs: []core.math.Transform,
|
|
outputList: *std.ArrayList(usize),
|
|
) void {
|
|
var final: core.Transform = core.zm.identity();
|
|
|
|
const repr = denseRepr[index];
|
|
|
|
if (repr.parent) |parent| {
|
|
if (core.Scene.SceneObjectContainer.sparseToDense(parent)) |parentIndex| {
|
|
self.updateTransform(thread, parentIndex, densePosRot, denseRepr, outputs, outputList);
|
|
const parentTransform = outputs[parentIndex];
|
|
final = core.zm.mul(parentTransform, final);
|
|
} else {}
|
|
}
|
|
|
|
const posRot = densePosRot[index];
|
|
|
|
outputs[index] = core.zm.mul(
|
|
core.zm.mul(
|
|
core.zm.mul(
|
|
core.zm.scalingV(posRot.scale.toZm()),
|
|
core.zm.matFromQuat(posRot.rotation.quat),
|
|
),
|
|
core.zm.translationV(posRot.position.toZm()),
|
|
),
|
|
final,
|
|
);
|
|
|
|
outputList.appendAssumeCapacity(index);
|
|
}
|
|
|
|
pub fn updateTransformsHierarchy(self: @This(), thread: *core.ThreadContext, outputs: []core.math.Transform, outputList: *std.ArrayList(usize)) !void {
|
|
const z = core.tracy.ZoneN(@src(), "updateTransformsHierarchy");
|
|
defer z.End();
|
|
//const dense = self.world.denseScenes();
|
|
const densePosRot = core.Scene.SceneObjectContainer.denseItems(.posRot);
|
|
const denseRepr = core.Scene.SceneObjectContainer.denseItems(._repr);
|
|
|
|
// 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.SceneObjectRepr, denseRepr);
|
|
|
|
// const locals:[]core.math.Transform = try thread.scratch().alloc(core.math.Transform, split.slice.len);
|
|
|
|
const z2 = core.tracy.ZoneN(@src(), "WalkAndResolve");
|
|
for (split.slice, 0..) |*repr, i| {
|
|
const index = split.startIndex + i;
|
|
self.updateTransform(thread, index, densePosRot, denseRepr, outputs, outputList);
|
|
//repr.cmpxchgStrong(false, true, .seq_cst, .acquire)
|
|
repr.merge.store(false, .seq_cst); // reset the merge big
|
|
}
|
|
z2.End();
|
|
}
|
|
};
|
|
|
|
// 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;
|