diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..5bb7c0f --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,37 @@ +name: Build Projects + +on: + push: + branches: [ main, integration ] + pull_request: + branches: [ main, integration ] + +jobs: + build: + runs-on: ${{ matrix.target.os }} + strategy: + matrix: + target: + - zig: x86_64-linux-gnu + os: self-hosted-linux + - zig: x86_64-windows + os: self-hosted-windows + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Build projects + working-directory: ./projects + run: zig build install -Dtarget=${{ matrix.target.zig }} + + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + if: success() + with: + name: build-artifacts-${{ matrix.target.zig }} + path: | + projects/zig-out/ + retention-days: 7 diff --git a/build.zig b/build.zig index 951ff1f..e51d848 100644 --- a/build.zig +++ b/build.zig @@ -256,9 +256,9 @@ const DynamicDepList: []const struct { dep: []const u8, artifact: []const u8 } = .{ .dep = "sdl3", .artifact = "SDL3" }, .{ .dep = "spng", .artifact = "spng_c" }, .{ .dep = "lua", .artifact = "luac" }, - .{ .dep = "miniaudio", .artifact = "miniaudio_c" }, + // .{ .dep = "miniaudio", .artifact = "miniaudio_c" }, .{ .dep = "zphysics", .artifact = "joltc" }, - .{ .dep = "enet", .artifact = "enet_c" }, + // .{ .dep = "enet", .artifact = "enet_c" }, .{ .dep = "ozz", .artifact = "ozz_cpp" }, }; @@ -401,6 +401,7 @@ pub const Program = struct { for (self.gameModules.items) |*m| { if (std.mem.eql(u8, m.name, module)) { m.enabled = enable; + std.debug.print("enabling module {s} {any}\n", .{ module, enable }); return; } } diff --git a/engine/core/src/core.zig b/engine/core/src/core.zig index 8a84e80..63a3984 100644 --- a/engine/core/src/core.zig +++ b/engine/core/src/core.zig @@ -468,3 +468,36 @@ pub fn PatchOrCreateObject(comptime T: type, args: engine.NeonObjectParams) void logging.engine_err("Unable to create object", .{}); }; } + +pub const u32a = std.atomic.Value(u32); +pub const i32a = std.atomic.Value(u32); + +var gBarrierCount: u32a = u32a.init(0); + +pub fn setBarrierCount(count: u32) void { + gBarrierCount.store(count, .seq_cst); +} + +pub fn getBarrierCount() u32 { + return gBarrierCount.load(.acquire); +} + +pub fn jobCount() u32 { + return getEngine().jobManager.numWorkers; +} + +pub fn waitSeconds(wait: f64) void { + const start = getEngineTime(); + + while (getEngineTime() - start < wait) { + std.Thread.sleep(10_000_000); + } +} + +pub fn itof32(i: anytype) f32 { + return @as(f32, @floatFromInt(i)); +} + +pub fn itof64(i: anytype) f32 { + return @as(f32, @floatFromInt(i)); +} diff --git a/engine/core/src/engineObject.zig b/engine/core/src/engineObject.zig index c41a978..3185e6e 100644 --- a/engine/core/src/engineObject.zig +++ b/engine/core/src/engineObject.zig @@ -38,6 +38,7 @@ pub fn MakeTypeName(comptime TargetType: type) Name { pub const EngineDataEventError = error{ UnknownStatePanic, BadInit, + JobsAbortShutdown, UnknownError, OutOfMemory, }; diff --git a/engine/core/src/engineTime.zig b/engine/core/src/engineTime.zig index 18c37ab..c2c3741 100644 --- a/engine/core/src/engineTime.zig +++ b/engine/core/src/engineTime.zig @@ -1,6 +1,8 @@ const std = @import("std"); -// todo need a better timing system than this +const core = @import("core.zig"); + +// todo, replace with monotonic timer pub fn getEngineTime() f64 { return @as(f64, @floatFromInt(std.time.milliTimestamp())) / 1000; } diff --git a/engine/core/src/extern/externModule.zig b/engine/core/src/extern/externModule.zig index 7e94d58..44cf23e 100644 --- a/engine/core/src/extern/externModule.zig +++ b/engine/core/src/extern/externModule.zig @@ -68,8 +68,8 @@ pub const LoadedModule = struct { const stagedPath = self.stagedPaths.getLast(); var lib = try std.DynLib.open(stagedPath); var mod = ModuleInterface{}; - mod.startup = lib.lookup(@TypeOf(mod.startup), "startup") orelse return error.BadModule; - mod.shutdown = lib.lookup(@TypeOf(mod.shutdown), "shutdown") orelse return error.BadModule; + mod.startup = lib.lookup(@TypeOf(mod.startup), "startup_module") orelse return error.BadModule; + mod.shutdown = lib.lookup(@TypeOf(mod.shutdown), "shutdown_module") orelse return error.BadModule; try self.loaded.append(allocator, mod); } diff --git a/engine/core/src/gameObject.zig b/engine/core/src/gameObject.zig index 7cb53f9..3923d5c 100644 --- a/engine/core/src/gameObject.zig +++ b/engine/core/src/gameObject.zig @@ -88,12 +88,7 @@ pub const GameObjectSystem = struct { return self; } - pub fn spawnObject( - self: *@This(), - comptime T: type, - objectName: []const u8, - parameters: SpawnParameters, - ) !*T { + pub fn spawnObject(self: *@This(), comptime T: type, objectName: []const u8, parameters: SpawnParameters) !*T { var n = core.MakeName(objectName); return self.spawnObjectByName(T, &n, parameters); } diff --git a/engine/core/src/jobs.zig b/engine/core/src/jobs.zig index 5d4ed3e..9694823 100644 --- a/engine/core/src/jobs.zig +++ b/engine/core/src/jobs.zig @@ -16,6 +16,7 @@ pub const JobManager = struct { jobQueueConcurrent: core.ConcurrentQueueU(JobContext), workers: []*JobWorker, numCpus: usize, + numWorkers: u32 = 0, pub fn create(allocator: std.mem.Allocator) !*@This() { var self = try allocator.create(@This()); @@ -29,6 +30,7 @@ pub const JobManager = struct { }; self.workers = self.allocator.alloc(*JobWorker, @max(self.numCpus - 2, 4)) catch unreachable; + self.numWorkers = @intCast(self.workers.len); core.engine_log("allocating worker count : {d}", .{self.workers.len}); var i: usize = 0; diff --git a/engine/ui/src/sgpu/textRenderer.zig b/engine/ui/src/sgpu/textRenderer.zig index fb80544..0f54e35 100644 --- a/engine/ui/src/sgpu/textRenderer.zig +++ b/engine/ui/src/sgpu/textRenderer.zig @@ -129,8 +129,13 @@ pub const TextRenderer = struct { return meshBuffer; } + pub fn submitMeshBuffer(self: *@This(), meshBuffer: *TextMeshBuffer) void { + _ = self; + _ = meshBuffer; + } + pub fn updateMeshBuffer(self: *@This(), meshBuffer: *TextMeshBuffer, copyPass: *gpu.GPUCopyPass, text: papyrus.DrawCommand.PrimitiveText) !void { - var z = tracy.ZoneN(@src(), "meshBuffer updates"); + var z = tracy.ZoneN(@src(), "papyrus meshBuffer updates"); defer z.End(); const string = text.text.getRead(); if (string.len == 0) diff --git a/extras/doomplayer/src/c.zig b/extras/doomplayer/src/c.zig index 2918978..357a378 100644 --- a/extras/doomplayer/src/c.zig +++ b/extras/doomplayer/src/c.zig @@ -1,3 +1,3 @@ pub const c = @cImport({ - @cInclude("pureDOOM.h"); + @cInclude("PureDOOM.h"); }); diff --git a/extras/doomplayer/src/impl.c b/extras/doomplayer/src/impl.c index b930e31..9292a2c 100644 --- a/extras/doomplayer/src/impl.c +++ b/extras/doomplayer/src/impl.c @@ -4,7 +4,7 @@ #define DOOM_IMPLEMENT_PRINT #define DOOM_IMPLEMENT_MALLOC #define DOOM_IMPLEMENT_FILE_IO -#define DOOM_IMPLEMENT_SOCKETS +// #define DOOM_IMPLEMENT_SOCKETS #define DOOM_IMPLEMENT_GETTIME #define DOOM_IMPLEMENT_GETENV diff --git a/lib/enet/build.zig b/lib/enet/build.zig index 0a8502b..0eadf60 100644 --- a/lib/enet/build.zig +++ b/lib/enet/build.zig @@ -4,9 +4,10 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); const static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false; + _ = static_build; // am i good to always have enet as static? - const enet = if (static_build and false) b.addStaticLibrary(.{ + const enet = if (true) b.addStaticLibrary(.{ .name = "enet_c", .target = target, .optimize = optimize, @@ -28,8 +29,8 @@ pub fn build(b: *std.Build) void { "packet.c", "peer.c", "protocol.c", - "unix.c", "win32.c", + "unix.c", }, .flags = &.{"-DHAS_OFFSETOF=1"}, }); @@ -41,6 +42,7 @@ pub fn build(b: *std.Build) void { .windows => { enet.linkSystemLibrary("ws2_32"); enet.linkSystemLibrary("winmm"); + // Use .def file to control exports and avoid CRT symbol conflicts }, .linux, .macos => { // Unix platforms - no additional libraries needed diff --git a/lib/miniaudio/build.zig b/lib/miniaudio/build.zig index c8773cf..fd967f9 100644 --- a/lib/miniaudio/build.zig +++ b/lib/miniaudio/build.zig @@ -5,7 +5,7 @@ pub fn build(b: *std.Build) void { const optimize = b.standardOptimizeOption(.{}); const static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false; - const miniaudio_c = if (static_build) b.addStaticLibrary(.{ + const miniaudio_c = if (static_build or true) b.addStaticLibrary(.{ .name = "miniaudio_c", .target = target, .optimize = optimize, @@ -18,7 +18,7 @@ pub fn build(b: *std.Build) void { }); b.installArtifact(miniaudio_c); - miniaudio_c.addCSourceFile(.{ .file = b.path("src/miniaudio.cpp"), .flags = &.{"-fno-sanitize=all"} }); + miniaudio_c.addCSourceFile(.{ .file = b.path("src/miniaudio.cpp"), .flags = &.{"-fno-sanitize=all"}, .language = .c }); miniaudio_c.addIncludePath(b.path("include")); const mod = b.addModule("miniaudio", .{ .target = target, .optimize = optimize, .root_source_file = b.path("src/miniaudio.zig") }); diff --git a/lib/tracy/build.zig b/lib/tracy/build.zig index 22925af..8ef8368 100644 --- a/lib/tracy/build.zig +++ b/lib/tracy/build.zig @@ -11,7 +11,7 @@ pub fn build(b: *std.Build) void { //const tracy_enabled = b.option(bool, "tracy", "Enables tracy integration") orelse false; - const tracy_enabled: bool = false; //if (b.graph.env_map.hash_map.get("WITH_TRACY") != null) true else false; + const tracy_enabled: bool = true; //if (b.graph.env_map.hash_map.get("WITH_TRACY") != null) true else false; // if (b.graph.env_map.hash_map.get("WITH_TRACY")) |with_tracy| { // tracy_enabled = with_tracy; // } @@ -31,6 +31,7 @@ pub fn build(b: *std.Build) void { .file = b.path(tracy_path ++ "/TracyClient.cpp"), .flags = &[_][]const u8{ "-DTRACY_ENABLE", + "-DTRACY_NO_EXIT", // MinGW doesn't have all the newfangled windows features, // so we need to pretend to have an older windows version. // "-D_WIN32_WINNT=0x601", diff --git a/lib/tracy/tracy.zig b/lib/tracy/tracy.zig index 1f561d8..4290914 100644 --- a/lib/tracy/tracy.zig +++ b/lib/tracy/tracy.zig @@ -223,6 +223,7 @@ const tracy_stub = struct { const tracy_full = struct { const c = @cImport({ @cDefine("TRACY_ENABLE", ""); + @cDefine("TRACY_NO_EXIT", ""); @cInclude("TracyC.h"); }); diff --git a/projects/build.zig b/projects/build.zig index edae9e1..e08ebbb 100644 --- a/projects/build.zig +++ b/projects/build.zig @@ -60,6 +60,7 @@ pub fn build(b: *std.Build) void { toolbox.setModuleEnabled("imgui", true); toolbox.setModuleEnabled("audio", false); toolbox.setModuleEnabled("sys", true); + toolbox.setModuleEnabled("net", false); toolbox.addExtraModule("gameExtras"); const toolboxExe = toolbox.compileInstall(); @@ -76,9 +77,12 @@ pub fn build(b: *std.Build) void { .desc = "headless program, core-only", .root_source_file = b.path("headless/main.zig"), }); + std.debug.print("headless", .{}); headless.setModuleEnabled("assets", false); headless.setModuleEnabled("platform", false); + headless.setModuleEnabled("audio", false); + headless.setModuleEnabled("net", false); headless.setModuleEnabled("rend", false); _ = headless.compileInstall(); diff --git a/projects/content/bsp/testmap.map b/projects/content/bsp/testmap.map index 1463bd6..2b5ab15 100644 --- a/projects/content/bsp/testmap.map +++ b/projects/content/bsp/testmap.map @@ -1797,6 +1797,15 @@ ( 3808 11088 17264 ) ( 3809 11088 17264 ) ( 3808 11088 17265 ) mapping_defaults/ProtoDark 0 0 0 0.25 0.25 ( 3808 11088 17264 ) ( 3808 11088 17265 ) ( 3808 11089 17264 ) mapping_defaults/ProtoDark 0 0 0 0.25 0.25 } +// brush 200 +{ +( -6544 -240 -64 ) ( -6544 -239 -64 ) ( -6544 -240 -63 ) mapping_defaults/ProtoDarkBlue 0 -192 0 0.25 0.25 +( 192 -6992 -64 ) ( 192 -6992 -63 ) ( 193 -6992 -64 ) mapping_defaults/ProtoDarkBlue 0 -192 0 0.25 0.25 +( 192 -240 -64 ) ( 193 -240 -64 ) ( 192 -239 -64 ) mapping_defaults/ProtoDarkBlue 0 0 0 0.25 0.25 +( 400 -128 -48 ) ( 400 -127 -48 ) ( 401 -128 -48 ) mapping_defaults/ProtoDarkBlue 0 0 0 0.25 0.25 +( 400 4784 -48 ) ( 401 4784 -48 ) ( 400 4784 -47 ) mapping_defaults/ProtoDarkBlue 0 -192 0 0.25 0.25 +( 4240 -128 -48 ) ( 4240 -128 -47 ) ( 4240 -127 -48 ) mapping_defaults/ProtoDarkBlue 0 -192 0 0.25 0.25 +} } // entity 1 { diff --git a/projects/headless/main.zig b/projects/headless/main.zig index 5322bc3..24b6ab7 100644 --- a/projects/headless/main.zig +++ b/projects/headless/main.zig @@ -1,5 +1,6 @@ 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() { @@ -11,20 +12,110 @@ pub fn create(allocator: std.mem.Allocator) !*@This() { } pub fn prepare(self: *@This()) !void { - _ = self; + try self.testThing(); } pub fn tick(self: *@This(), dt: f64) void { - _ = self; - _ = dt; + // std.time.sleep(1000_000); + self.timeLeft -= dt; - core.exitNow(); + if (self.timeLeft < 0) + core.exitNow(); + + const start = core.getEngineTime(); + + // wait 10 ms + while (core.getEngineTime() - start < 0.010) { + std.Thread.sleep(10_000_000); + } } 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) !void { + _ = threadId; + + const barrierCount = core.getBarrierCount(); + + 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 { + pub var WorkGroupData = WorkGroupData.from(@This()); + + threadId: u32 = 0, + threadName: []const u8, + + pub var cv: std.Thread.Condition = .{}; + pub var mutex: std.Thread.Mutex = .{}; + + pub fn func(ctx: @This(), job: *core.JobContext) void { + _ = job; + core.tracy.SetThreadName(@ptrCast(ctx.threadName.ptr)); + ctx.loop() catch {}; + + if (core.getEngine().isShuttingDown()) {} + } + + pub fn loop(ctx: @This()) !void { + while (!core.getEngine().isShuttingDown()) { + try Barrier(@src()).sync(ctx.threadId); + 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.time.sleep(100_000 * ctx.threadId); + + try Barrier(@src()).sync(ctx.threadId); + } +}; + +pub fn testThing(self: *@This()) !void { + _ = self; + const workerCount = 16; + + core.setBarrierCount(workerCount); + + for (0..workerCount) |i| { + const name = try std.fmt.allocPrintZ(std.heap.c_allocator, "MultiJob{d}", .{i}); + try core.dispatchJob(MultiJob{ .threadName = name, .threadId = @intCast(i) }); + } +} + pub fn main() anyerror!void { var spec = try backlog.getSpec("headless"); // try spec.put("useGPA", .{ .boolean = false }); diff --git a/projects/minimal/src/main.zig b/projects/minimal/src/main.zig index 5f1da02..0fd2012 100644 --- a/projects/minimal/src/main.zig +++ b/projects/minimal/src/main.zig @@ -11,7 +11,7 @@ pub fn init(allocator: std.mem.Allocator) !*@This() { } pub fn prepare(self: *@This()) !void { - _ = self; + try self.fuckNazis(); core.engine_log("program ready", .{}); } diff --git a/projects/sampleGame/externGame/externGame.zig b/projects/sampleGame/externGame/externGame.zig index 2dbdd8d..4e180ab 100644 --- a/projects/sampleGame/externGame/externGame.zig +++ b/projects/sampleGame/externGame/externGame.zig @@ -1,4 +1,4 @@ -pub export fn startup(p_allocator: *anyopaque, p_a: ?*anyopaque) bool { +pub export fn startup_module(p_allocator: *anyopaque, p_a: ?*anyopaque) bool { const allocator = core.modulePreamble(p_allocator, p_a) catch return false; _ = allocator; imgui.setupFromModule(); @@ -118,7 +118,7 @@ pub const ExternGameObject = struct { reloadMapInput: ?*core.ActionBinding = null, volume: f32 = 100.0, - vo2lume: f32 = 100.0, + volume2: f32 = 100.0, a: core.Vector2f = .{ .x = 1, .y = 4 }, b: core.Vector2f = .{ .x = 4, .y = 1 }, @@ -145,6 +145,20 @@ pub const ExternGameObject = struct { pub fn _beginPlay(self: *@This()) !void { try self.setupInputs(); + + if (self.firstTick) { + self.loadMap2() catch unreachable; + self.firstTick = false; + } + + // change this into a parallel for + for (0..300) |i| { + _ = try core.get(core.GameObjectSystem).spawnObject(BoxObject, "Box", .{ + .posRot = .{ + .position = .{ .x = core.itof32((i % 10)) * 2, .y = 15.0, .z = core.itof32(@divFloor(i, 10) * 2) }, + }, + }); + } } pub fn onMapReload(ctx: ?*anyopaque, _: core.ActionEvent) void { @@ -477,11 +491,6 @@ pub const ExternGameObject = struct { self.drawVector2Test(); } - if (self.firstTick) { - self.loadMap2() catch unreachable; - self.firstTick = false; - } - if (self.recompiling) { if (ig.begin("recompiling", null, .{})) { ig.textf("recompiling...", .{}); @@ -490,7 +499,7 @@ pub const ExternGameObject = struct { } if (platform.context().isCursorEnabled()) { - // imgui.utils.structDebugWindow(self); + imgui.utils.structDebugWindow(self); // imgui.utils.structDebugWindow(core.get(rend.renderer.Renderer)); @@ -681,7 +690,7 @@ pub const ExternGameObject = struct { } }; -pub export fn shutdown() void {} +pub export fn shutdown_module() void {} const std = @import("std"); const backlog = @import("backlog"); diff --git a/projects/sampleGame/externGame/fpgame/fpgame.zig b/projects/sampleGame/externGame/fpgame/fpgame.zig new file mode 100644 index 0000000..511fd30 --- /dev/null +++ b/projects/sampleGame/externGame/fpgame/fpgame.zig @@ -0,0 +1,33 @@ +pub const Slack = core.SlackStruct(@This(), 1024); + +allocator: std.mem.Allocator = undefined, +entity: core.Entity = undefined, +camera: *extras.FpCamera = undefined, + +pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "extern.FpPlayer"); + +pub fn create(allocator: std.mem.Allocator) !*@This() { + const self = try Slack.create(allocator); + + self.* = .{ + .allocator = allocator, + .entity = try core.createEntity(), + .camera = try extras.FpCamera.create(allocator), + }; + + return self; +} + +pub fn objectReload(self: *@This()) void { + _ = self; +} + +pub fn destroy(self: *@This()) void { + self.camera.destroy(); + self.allocator.destroy(Slack.fromPtr(self)); +} + +const backlog = @import("backlog"); +const core = backlog.core; +const std = @import("std"); +const extras = @import("gameExtras");