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); } } pub fn tick(self: *@This(), dt: f64) void { self.timeLeft -= dt; const start = core.getEngineTime(); core.parallelJob(UpdateWorldTransformsJob{}, false, 1) catch unreachable; // wait 10 ms while (core.getEngineTime() - start < 0.010) {} if (self.timeLeft < 0) core.exitNow(); } pub fn deinit(self: *@This()) void { self.allocator.destroy(self); } 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 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.smp_allocator, "MultiJob{d}", .{i}, 0); _ = name; // 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;