Compare commits

..

No commits in common. "dev/variant-engine" and "dev/upgrade-015" have entirely different histories.

302 changed files with 10562 additions and 6840 deletions

169
CLAUDE.md
View File

@ -1,169 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
Backlog is a game engine written in Zig (version 0.14) with a modular architecture. The engine supports cross-platform development with Windows, macOS, and Linux targets, and includes support for both static and dynamic module loading.
## Build System
The project uses Zig's build system with a custom `BuildSystem` wrapper:
### Key Build Commands
- `zig build` - Build the engine and default projects
- `zig build -Dcookshaders` - Build with shader compilation enabled
- `zig build -Dstatic_build` - Force static linking (required for Linux)
- `zig build tools` - Install development tools (gltf2ozz, spirv-reflect)
- `zig build gltf2ozz -- [args]` - Run GLTF animation converter
- `zig build spv-reflect -- [args]` - Run SPIRV reflection tool
- `python tools/scripts/cookShaders.py` - Compile shaders from HLSL to SPIR-V, MSL, and DXIL formats, must be run after editing shader files
### Project-Specific Commands
From the `projects/` directory:
- `zig build` - Build sample game and tools
- `zig build run-sampleGame` - Run the sample game
- `zig build run-newProject` - Run the project creation tool
### Testing
- `zig build test` - Run all tests (individual modules have their own test files in `tests/` subdirectories)
### Setup Commands
- `python tools/scripts/first-time-setup.py` - Initial setup script
## Architecture
### Engine Modules
The engine is organized into these core modules:
- **core** - Foundation systems (ECS, logging, memory tracking, jobs, scripting)
- **platform** - Cross-platform windowing and input handling
- **assets** - Asset loading and management system
- **rend** - 3D rendering system with mesh, camera, and material support
- **audio** - Sound engine integration
- **ui** - User interface rendering system
- **papyrus** - Text rendering and UI primitives
- **imgui** - Debug UI integration
- **physics** - Physics engine integration (Jolt)
- **sys** - System utilities and subprocess management
### Key Directories
- `engine/` - Core engine modules, each with their own `build.zig`
- `projects/` - Sample projects and games
- `lib/` - Third-party dependencies and wrappers
- `tools/` - Development utilities and scripts
- `content/` - Game assets and resources (located at `projects/content/`)
- `extras/` - Optional engine extensions
### Engine Initialization
The engine uses a spec-based initialization system where modules are conditionally enabled:
```zig
// Programs define which modules to enable
sampleGame.setModuleEnabled("imgui", true);
sampleGame.setModuleEnabled("physics", true);
```
### Dynamic Modules
The engine supports dynamic module loading for game-specific code:
```zig
const externGame = sampleGame.addDynamicModule("externGame", b.path("sampleGame/externGame/externGame.zig"));
```
### Shader Pipeline
Shaders are written in HLSL and compiled to multiple targets:
- SPIR-V for Vulkan
- MSL for Metal (macOS)
- DXIL for DirectX
The shader compilation system automatically discovers `.hlsl` files in `engine/*/shaders/` directories.
## Development Notes
- All memory allocations go through a centralized `MemoryTracker` for leak detection
- The engine uses Tracy for profiling when enabled
- Lua scripting is integrated for game logic
- The build system generates API wrappers automatically for enabled modules
- Content directory location is determined by `content.txt` file pointing to `projects/content/`
## Common Development Tasks
- Adding new engine modules: Create in `engine/` with `build.zig` and add to `engineDepList`
- Creating new projects: Use the project template in `projects/minimal/`
- Shader development: Add HLSL files to module `shaders/` directories, run `cookShaders.py`
- Asset pipeline: Assets go in `projects/content/` and use `.cook` extensions for processed assets
## Issue Tracking with git-bug
This project uses git-bug for distributed issue tracking. Bug data is stored directly in the git repository. While named "git-bug", it can track all types of development work including bugs, features, tasks, and documentation.
### Common Commands
- `git bug bug` - List all issues
- `git bug bug new -t "title" -m "message"` - Create a new issue with title and message
- `git bug bug show <id>` - Display issue details
- `git bug bug comment <id>` - Add a comment to an issue
- `git bug bug status <id>` - Display issue status
- `git bug bug label new <id> <label>` - Add a label to an issue
- `git bug bug label rm <id> <label>` - Remove a label from an issue
- `git bug bug label <id>` - Display labels for an issue
- `git bug bug status:open` - List only open issues
- `git bug pull` - Pull issue updates from remote
- `git bug push` - Push issue updates to remote
### Workflow
- Issues are stored in the repository and sync with `git bug pull`/`git bug push`
- Issue IDs can be abbreviated to the first few characters
- Use labels to categorize issues by both type and component
- Use `--non-interactive` flag for scripting
- Keep issue descriptions factual and clear
### Standard Labels
Use these standard labels to categorize issues:
- **Component labels**: `core`, `rendering`, `physics`, `build-system`, `platform`, `assets`, `audio`, `ui`, `documentation`
- **Issue type labels**:
- `bug` - Defects, errors, or incorrect behavior
- `feature` - New functionality to implement
- `enhancement` - Improvements to existing features
- `task` - General development work items
- `documentation` - Documentation improvements or additions
- `question` - Design decisions or technical discussions
- `refactoring` - Code cleanup and restructuring
- **Problem type labels** (for bugs):
- `memory` - Memory allocation, leaks, or performance issues
- `threading` - Job system, parallelization, race conditions, or deadlocks
- `crash` - Application crashes or critical failures
- `build` - Build system or compilation issues
### Examples
```bash
# Create a feature request
git bug bug new -t "Add procedural terrain generation" -m "Implement heightmap-based terrain system"
git bug bug label new <id> feature rendering
# Create a task
git bug bug new -t "Update to Zig 0.15" -m "Migrate codebase to latest Zig version"
git bug bug label new <id> task build-system
# Create a documentation issue
git bug bug new -t "Document shader pipeline" -m "Add comprehensive guide for shader development workflow"
git bug bug label new <id> documentation rendering
# Create a bug report
git bug bug new -t "Memory leak in asset loader" -m "Assets not freed when unloading scenes"
git bug bug label new <id> bug assets memory
```

View File

@ -1,100 +1,18 @@
# Backlog
zig version: 0.15.1
Backlog Labs Game Engine.
## Getting Started
run tools/scripts/first-time-setup.py
ffmpeg -i INPUT.mp4 -c:v libtheora -q:v 7 -c:a libvorbis -q:a 4 OUTPUT.ogv
## git-bug Cheat Sheet
This project uses git-bug for distributed issue tracking. Issues are stored directly in the repository.
### Common Commands
**Listing Issues**
```bash
git bug bug # List all issues
git bug bug status:open # List only open issues
git bug bug status:closed # List only closed issues
```
**Creating Issues**
```bash
git bug bug new -t "title" -m "message" # Create new issue
git bug bug new -t "Add feature X" -m "Description" # Example
```
**Viewing & Managing Issues**
```bash
git bug bug show <id> # Show issue details
git bug bug comment <id> # Add a comment to an issue
git bug bug close <id> # Close an issue
git bug bug open <id> # Reopen an issue
git bug bug status <id> # Show issue status
```
**Labels**
```bash
git bug bug label <id> # Show labels for an issue
git bug bug label new <id> <label> # Add a label
git bug bug label rm <id> <label> # Remove a label
```
**Syncing**
```bash
git bug pull # Pull issue updates from remote
git bug push # Push issue updates to remote
```
### Standard Labels
**Component Labels**: `core`, `rendering`, `physics`, `build-system`, `platform`, `assets`, `audio`, `ui`, `documentation`
**Type Labels**:
- `bug` - Defects or incorrect behavior
- `feature` - New functionality
- `enhancement` - Improvements to existing features
- `task` - General development work
- `documentation` - Documentation improvements
- `question` - Design decisions or discussions
- `refactoring` - Code cleanup
**Problem Type Labels** (for bugs):
- `memory` - Memory issues
- `threading` - Concurrency issues
- `crash` - Application crashes
- `build` - Build system issues
### Quick Examples
```bash
# Create a bug report
git bug bug new -t "Memory leak in asset loader" -m "Assets not freed when unloading scenes"
git bug bug label new <id> bug assets memory
# Create a feature request
git bug bug new -t "Add terrain generation" -m "Implement heightmap-based terrain"
git bug bug label new <id> feature rendering
# Create a task
git bug bug new -t "Update to Zig 0.15" -m "Migrate to latest Zig version"
git bug bug label new <id> task build-system
# View and comment on an issue
git bug bug show abc123
git bug bug comment abc123
```
### Tips
- Issue IDs can be abbreviated (first few characters)
- Use `--non-interactive` flag for scripting
- Issues sync with `git bug pull/push`
- Keep descriptions factual and clear
/// --------------------------------------------------------
void* malloc(size_t size); // gives you a pointer to a memory buffer of size
void free(void* ptr); // releases a pointer to memory
/// --------------------------------------------------------

View File

@ -1,662 +0,0 @@
// MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 -- use this if you're getting that odd crash on mac
b: *std.Build,
nw_builder: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
nw_mod: *std.Build.Module,
gltf2ozz: ozz.GltfToOzz,
options: *std.Build.Step.Options,
cookShaders: bool,
backlogRoot: []const u8,
// list of all shaders discovered under
// content/_shaders/def
reflectShaderPathList: [][]u8 = undefined,
staticBuild: bool = false,
nwdep: *std.Build.Dependency,
apigen: *std.Build.Step.Compile,
shaderEmbedGen: *std.Build.Step.Compile,
loadDynamicsGen: *std.Build.Step.Compile,
rcGen: *std.Build.Step.Compile,
generatedApis: std.StringHashMapUnmanaged(*std.Build.Module) = .{},
const engineDepList = [_][]const u8{
"assets",
"audio",
"core",
"net",
"papyrus",
"platform",
"rend",
"ui",
"imgui",
"physics",
"sys",
};
const BuildSystem = @This();
const std = @import("std");
const Build = std.Build;
const LazyPath = Build.LazyPath;
const ozz = @import("ozz");
pub const InitOptions = struct {
import_name: []const u8 = "Backlog",
backlogRoot: []const u8 = "./BacklogEngine",
staticBuild: bool = false,
target: Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
};
pub fn init(b: *std.Build, opts: InitOptions) BuildSystem {
var buildOpts = declareBuildOptions(b);
if (opts.target.result.os.tag == .linux) {
// using a static build for linux... object loading hell is not fun
std.debug.print("Important! only supporting static builds for linux", .{});
buildOpts.static_build = true;
}
const nwdep = b.dependency(opts.import_name, .{
.target = opts.target,
.optimize = opts.optimize,
.static_build = buildOpts.static_build,
});
var self = BuildSystem{
.b = b,
.nw_builder = nwdep.builder,
.target = opts.target,
.optimize = opts.optimize,
.nw_mod = nwdep.module("Backlog"),
.backlogRoot = opts.backlogRoot,
.options = createGameOptions(b, buildOpts),
.gltf2ozz = ozz.GltfToOzz.init(nwdep.builder, .{}),
.cookShaders = b.option(bool, "cookShaders", "generates shaders and updates .json files before running the build. (needs to be done whenever shaders are updated, this just runs tools/scripts/cook-shaders.py)") orelse false,
.staticBuild = buildOpts.static_build,
.nwdep = nwdep,
.apigen = nwdep.artifact("backlog-apigen"),
.loadDynamicsGen = nwdep.artifact("backlog-generate-loadDynamics"),
.rcGen = nwdep.artifact("backlog-generate-rc"),
.shaderEmbedGen = nwdep.artifact("backlog-shaderEmbedGen"),
};
const exeList = [1]*std.Build.Step.Compile{self.gltf2ozz.exe};
const install_tools = b.step("tools", "installs tools needed to generate outputs for the engine");
for (exeList) |exe| {
const toolsInstall = b.addInstallArtifact(exe, .{
.dest_dir = .{ .override = .{ .custom = "tools" } },
});
install_tools.dependOn(&toolsInstall.step);
}
{
const runArtifact = b.addRunArtifact(self.gltf2ozz.exe);
if (b.args) |args| {
runArtifact.addArgs(args);
}
const run_exe = b.step("gltf2ozz", "runs the gltf animation converter.");
run_exe.dependOn(&runArtifact.step);
}
self.addDependencyInstalls(self.b, .ReleaseFast);
return self;
}
pub const AddProgramOptions = struct {
name: []const u8,
desc: []const u8,
root_source_file: LazyPath,
imports: []const Build.Module.Import = &.{},
};
pub fn addProgram(self: *BuildSystem, opts: AddProgramOptions) *std.Build.Module {
const b = self.b;
const exe = self.nw_builder.addExecutable(.{
.name = opts.name,
.root_module = b.createModule(.{
.target = self.target,
.optimize = self.optimize,
.root_source_file = self.nw_builder.path("engine/main.zig"),
}),
});
b.installArtifact(exe);
const runArtifact = b.addRunArtifact(exe);
if (b.args) |args| {
runArtifact.addArgs(args);
}
const run_exe = b.step(self.b.fmt("run-{s}", .{opts.name}), opts.desc);
run_exe.dependOn(&runArtifact.step);
// main path = name/main.zig
const mod = b.addModule(opts.name, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = opts.root_source_file,
.imports = opts.imports,
});
exe.root_module.addImport("main", mod);
// todo.. remove this one and see what happens
exe.root_module.addImport("core", self.nwdep.module("core"));
// mod.addImport("Backlog", self.nw_mod);
exe.root_module.addOptions("BacklogOptions", self.options);
if (self.cookShaders) {
const cookShadersScript = b.fmt("{s}/tools/scripts/cookShaders.py", .{self.backlogRoot});
const cookShadersCommand = b.addSystemCommand(&[_][]const u8{"python"});
cookShadersCommand.addArg(cookShadersScript);
run_exe.dependOn(&cookShadersCommand.step);
}
b.getInstallStep().dependOn(self.nw_builder.getInstallStep());
// run_exe.dependOn(b.getInstallStep());
return mod;
}
pub const BuildOptions = struct {
mutex_job_queue: bool,
static_build: bool,
zero_logging: bool,
slow_logging: bool,
force_mailbox: bool,
};
pub fn declareBuildOptions(b: *std.Build) BuildOptions {
return .{
.mutex_job_queue = b.option(bool, "mutex_job_queue", "temporary test, reverts to old mutex based queue behaviour in jobs.zig:JobManager") orelse false,
.static_build = b.option(bool, "static_build", "builds the entire game as a single executable") orelse false,
.zero_logging = b.option(bool, "zero_logging", "disables all logging, only intended for use on job dispatch testing") orelse false,
.slow_logging = b.option(bool, "slow_logging", "Disables buffered logging, takes a hit to performance but gain timing information on logging") orelse false,
.force_mailbox = b.option(bool, "force_mailbox", "forces mailbox mode for present mode. unlocks framerate to irresponsible levels") orelse false,
};
}
pub fn createGameOptions(b: *std.Build, options: BuildOptions) *std.Build.Step.Options {
const opts = b.addOptions();
inline for (@typeInfo(BuildOptions).@"struct".fields) |field| {
opts.addOption(bool, field.name, @field(options, field.name));
}
return opts;
}
pub fn addExtraModule(self: *BuildSystem, mod: *std.Build.Module, moduleName: []const u8) void {
const dep = self.b.dependency(moduleName, .{ .target = self.target, .optimize = self.optimize, .static_build = self.staticBuild });
mod.addImport(moduleName, dep.module(moduleName));
}
pub fn addDependencyInstalls(self: *BuildSystem, b: *std.Build, optimize: std.builtin.OptimizeMode) void {
//if (self.staticBuild) {
//return;
// }
inline for (DynamicDepList) |d| {
b.installArtifact(b.dependency(
d.dep,
.{ .target = self.target, .optimize = optimize, .static_build = self.staticBuild },
).artifact(d.artifact));
}
// b.installArtifact(b.dependency(
// "sdl3",
// .{ .target = self.target, .optimize = optimize, .static_build = self.staticBuild },
// ).artifact("SDL3"));
}
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 = "zphysics", .artifact = "joltc" },
// .{ .dep = "enet", .artifact = "enet_c" },
.{ .dep = "ozz", .artifact = "ozz_cpp" },
};
// all other modules are disabled by default
pub const defaultEnabledModules: []const []const u8 = &.{
"core",
"assets",
"platform",
"rend",
};
pub const moduleOrder: []const []const u8 = &.{
"core",
"sys",
"assets",
"platform",
"net",
"physics",
"audio",
"rend",
"ui",
"imgui",
"papyrus",
};
pub const DynamicModule = struct {
name: []const u8,
allocator: std.mem.Allocator,
buildSystem: *BuildSystem,
programName: []const u8,
opts: AddProgramOptions,
extras: std.ArrayListUnmanaged(GameModule) = .{},
gameModules: std.ArrayListUnmanaged(GameModule) = .{},
library: ?*std.Build.Step.Compile = null,
pub fn addExtraModule(self: *@This(), module: []const u8) void {
self.extras.append(self.allocator, .{ .name = module, .enabled = true }) catch @panic("out of memory");
}
pub fn init(name: []const u8, p: *Program) *@This() {
var self: *@This() = p.allocator.create(@This()) catch @panic("out of memory");
self.* = .{
.opts = p.opts,
.programName = p.opts.name,
.buildSystem = p.buildSystem,
.allocator = p.allocator,
.name = name,
};
for (p.gameModules.items) |module| {
self.gameModules.append(self.allocator, module) catch @panic("out of memory");
}
for (p.extras.items) |module| {
self.extras.append(self.allocator, module) catch @panic("out of memory");
}
return self;
}
pub fn compileInstall(self: *@This()) *std.Build.Step.Compile {
if (self.library) |lib| {
return lib;
}
const b = self.buildSystem.b;
const static = self.buildSystem.staticBuild;
const lib = b.addLibrary(.{
.name = self.name,
.linkage = if (static) .static else .dynamic,
.root_module = b.createModule(.{
.root_source_file = self.opts.root_source_file,
.link_libc = true,
.optimize = self.buildSystem.optimize,
.target = self.buildSystem.target,
}),
});
const allocator = self.buildSystem.b.allocator;
var modlist = std.ArrayList([]const u8){};
for (self.gameModules.items) |mod| {
if (mod.enabled) {
modlist.append(allocator, mod.name) catch @panic("out of memory");
}
}
for (self.extras.items) |extra| {
self.buildSystem.addExtraModule(lib.root_module, extra.name);
}
lib.root_module.addImport("backlog", self.buildSystem.generateApi(self.name, modlist.items, null));
lib.root_module.addOptions("BacklogOptions", self.buildSystem.options);
if (static) {
// generateApi should create static callers for the main program
} else {
const installExtern = b.addInstallArtifact(lib, .{
.dest_dir = .{ .override = .{ .custom = "modules" } },
});
b.getInstallStep().dependOn(&installExtern.step);
}
return lib;
}
};
pub const GameModule = struct {
name: []const u8,
enabled: bool = true,
};
pub const Program = struct {
allocator: std.mem.Allocator,
opts: AddProgramOptions,
gameModules: std.ArrayListUnmanaged(GameModule) = .{},
extras: std.ArrayListUnmanaged(GameModule) = .{},
dynamicModules: std.ArrayListUnmanaged(*DynamicModule) = .{},
buildSystem: *BuildSystem,
iconPath: ?std.Build.LazyPath = null,
pub fn setIconPath(self: *@This(), path: []const u8) void {
if (self.iconPath != null) {
return;
}
self.iconPath = self.buildSystem.generateRc(self.opts.name, path) catch return;
}
pub fn setModuleEnabled(self: *@This(), module: []const u8, enable: bool) void {
for (self.gameModules.items) |*m| {
if (std.mem.eql(u8, m.name, module)) {
m.enabled = enable;
return;
}
}
// module not found, add it here.
self.gameModules.append(self.allocator, .{ .name = module, .enabled = enable }) catch @panic("out of memory");
}
pub fn addExtraModule(self: *@This(), module: []const u8) void {
self.extras.append(self.allocator, .{ .name = module, .enabled = true }) catch @panic("out of memory");
}
pub fn addDynamicModule(self: *@This(), module: []const u8, root_source_file: std.Build.LazyPath) *DynamicModule {
const dynamicModule = DynamicModule.init(module, self);
dynamicModule.opts.root_source_file = root_source_file;
self.dynamicModules.append(self.allocator, dynamicModule) catch @panic("out of memory");
return dynamicModule;
}
pub fn compileInstall(self: *@This()) *std.Build.Module {
const exe = self.buildSystem.addProgram(self.opts);
const allocator = self.buildSystem.b.allocator;
var modlist = std.ArrayList([]const u8){};
for (self.gameModules.items) |mod| {
if (mod.enabled) {
modlist.append(allocator, mod.name) catch @panic("out of memory");
}
}
for (self.extras.items) |extra| {
self.buildSystem.addExtraModule(exe, extra.name);
}
exe.addImport("backlog", self.buildSystem.generateApi(self.opts.name, modlist.items, self.dynamicModules.items));
if (self.buildSystem.target.result.os.tag == .windows) {
if (self.iconPath) |ip| {
exe.addWin32ResourceFile(.{
.file = ip,
.flags = &.{},
});
}
}
return exe;
}
};
pub fn program(self: *BuildSystem, opts: AddProgramOptions) *Program {
const prog = self.b.allocator.create(Program) catch unreachable;
prog.* = .{
.allocator = self.b.allocator,
.opts = opts,
.buildSystem = self,
};
for (moduleOrder) |module| {
prog.setModuleEnabled(module, false);
}
for (defaultEnabledModules) |module| {
prog.setModuleEnabled(module, true);
}
return prog;
}
// ========= standalone build instance =======
// maybe the default should be like an engine launcher/project launcher or something
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;
const fwdGeneratorExe = b.addExecutable(.{
.name = "backlog-generate-fwd",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateFwd.zig"),
}),
});
const generateExe = b.addExecutable(.{
.name = "backlog-apigen",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateApi.zig"),
}),
});
const generateShaders = b.addExecutable(.{
.name = "backlog-shaderEmbedGen",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateEmbeddedShaders.zig"),
}),
});
const generateRcExe = b.addExecutable(.{
.name = "backlog-generate-rc",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateRc.zig"),
}),
});
const generateLoadDynamicsExe = b.addExecutable(.{
.name = "backlog-generate-loadDynamics",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateLoadDynamics.zig"),
}),
});
b.installArtifact(generateLoadDynamicsExe);
b.installArtifact(generateExe);
b.installArtifact(generateShaders);
b.installArtifact(generateRcExe);
{
const loadDynamicsStub = b.addModule("loadDynamicsStub", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/loadDynamicsStub.zig"),
});
_ = loadDynamicsStub;
const mod = b.addModule("Backlog", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/backlog.zig"),
});
for (engineDepList) |depName| {
const dep = b.dependency(
depName,
.{
.target = target,
.optimize = optimize,
.static_build = static_build,
},
);
const run = b.addRunArtifact(fwdGeneratorExe);
const output = run.addOutputFileArg(b.fmt("{s}_fwd.zig", .{depName}));
const modFwd = b.addModule(depName, .{
.target = target,
.optimize = optimize,
.root_source_file = output,
});
mod.addImport(b.fmt("{s}", .{depName}), modFwd);
modFwd.addImport("module", dep.module(depName));
// mod.addImport(depName, dep.module(depName));
}
// link in large platform support functions... special case.
{
const dep = b.dependency("sdl3", .{
.target = target,
.optimize = optimize,
.static_build = static_build,
});
const lib = dep.module("SDL3");
mod.addImport("sdl3_fwd", lib);
}
}
}
pub fn generateApi(self: *@This(), programName: []const u8, moduleList: []const []const u8, dynamicModules: ?[]const *DynamicModule) *std.Build.Module {
// std.debug.print("GENERATE API:: {s}\n", .{programName});
for (moduleList) |mod| {
_ = mod;
// std.debug.print("{s}\n", .{mod});
}
//
if (self.generatedApis.get(programName)) |m| {
return m;
}
const run = self.b.addRunArtifact(self.apigen);
const pfile = self.b.fmt("{s}Api.zig", .{programName});
const output = run.addOutputFileArg(pfile);
for (moduleList) |modName| {
run.addArg(modName);
}
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
for (moduleList) |modName| {
mod.addImport(modName, self.nwdep.module(modName));
}
const loadStatics = self.generateInstallStaticResources(programName, "content/_shaders") catch unreachable;
mod.addImport("staticShaderLoader", loadStatics);
// deal with dynamics
if (dynamicModules) |dynamics| {
const loadDynamics = self.generateLoadDynamics(programName, dynamics) catch @panic("unknown");
mod.addImport("loadDynamics", loadDynamics);
} else {
mod.addImport("loadDynamics", self.nwdep.module("loadDynamicsStub"));
}
self.generatedApis.put(self.b.allocator, programName, mod) catch @panic("out of memory");
return mod;
}
pub fn generateRc(self: *@This(), programName: []const u8, iconPath: []const u8) !std.Build.LazyPath {
const run = self.b.addRunArtifact(self.rcGen);
const pfile = self.b.fmt("{s}.rc", .{programName});
const output = run.addOutputFileArg(pfile);
run.addArg(iconPath);
return output;
}
pub fn generateLoadDynamics(self: *@This(), programName: []const u8, dynamics: []const *DynamicModule) !*std.Build.Module {
const run = self.b.addRunArtifact(self.loadDynamicsGen);
const pfile = self.b.fmt("{s}LoadDynamics.zig", .{programName});
const output = run.addOutputFileArg(pfile);
if (self.staticBuild) {
run.addArg("static");
} else {
run.addArg("dynamic");
}
for (dynamics) |dyn| {
run.addArg(dyn.name);
}
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
mod.addImport("core", self.nwdep.module("core"));
for (dynamics) |dyn| {
const dynmod = dyn.compileInstall();
mod.addImport(dyn.name, dynmod.root_module);
}
return mod;
}
pub fn generateInstallStaticResources(self: *@This(), programName: []const u8, shadersPath: []const u8) !*std.Build.Module {
const run = self.b.addRunArtifact(self.shaderEmbedGen);
const pfile = self.b.fmt("{s}ShaderGen.zig", .{programName});
const output = run.addOutputFileArg(pfile);
run.addArg(shadersPath);
const b = self.b;
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
mod.addImport("core", self.nwdep.module("core"));
var dir = try std.fs.cwd().openDir("content/_shaders", .{ .iterate = true });
defer dir.close();
var walker = dir.iterate();
while (try walker.next()) |shaderType| {
if (shaderType.kind == .directory) {
var d2 = try dir.openDir(shaderType.name, .{ .iterate = true });
defer d2.close();
var w2 = d2.iterate();
while (try w2.next()) |shaderName| {
const shaderPath = b.fmt("content/_shaders/{s}/{s}", .{ shaderType.name, shaderName.name });
mod.addAnonymousImport(shaderName.name, .{
.root_source_file = b.path(shaderPath),
});
}
}
}
return mod;
}

View File

@ -1,696 +0,0 @@
// MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 -- use this if you're getting that odd crash on mac
b: *std.Build,
nw_builder: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.Mode,
nw_mod: *std.Build.Module,
spirvReflect: SpirvReflect.SpirvGenerator2,
gltf2ozz: ozz.GltfToOzz,
options: *std.Build.Step.Options,
cookShaders: bool,
backlogRoot: []const u8,
// list of all shaders discovered under
// content/_shaders/def
reflectShaderPathList: [][]u8 = undefined,
staticBuild: bool = false,
nwdep: *std.Build.Dependency,
apigen: *std.Build.Step.Compile,
shaderEmbedGen: *std.Build.Step.Compile,
loadDynamicsGen: *std.Build.Step.Compile,
rcGen: *std.Build.Step.Compile,
generatedApis: std.StringHashMapUnmanaged(*std.Build.Module) = .{},
const engineDepList = [_][]const u8{
"assets",
"audio",
"core",
"net",
"papyrus",
"platform",
"rend",
"ui",
"imgui",
"physics",
"sys",
};
const BuildSystem = @This();
const std = @import("std");
const Build = std.Build;
const LazyPath = Build.LazyPath;
const SpirvReflect = @import("SpirvReflect");
const ozz = @import("ozz");
pub const InitOptions = struct {
import_name: []const u8 = "Backlog",
backlogRoot: []const u8 = "./BacklogEngine",
staticBuild: bool = false,
target: Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
};
pub fn init(b: *std.Build, opts: InitOptions) BuildSystem {
var buildOpts = declareBuildOptions(b);
if (opts.target.result.os.tag == .linux) {
// using a static build for linux... object loading hell is not fun
std.debug.print("Important! only supporting static builds for linux", .{});
buildOpts.static_build = true;
}
const nwdep = b.dependency(opts.import_name, .{
.target = opts.target,
.optimize = opts.optimize,
.static_build = buildOpts.static_build,
});
var self = BuildSystem{
.b = b,
.nw_builder = nwdep.builder,
.target = opts.target,
.optimize = opts.optimize,
.nw_mod = nwdep.module("Backlog"),
.backlogRoot = opts.backlogRoot,
.spirvReflect = SpirvReflect.SpirvGenerator2.init(nwdep.builder, .{}),
.options = createGameOptions(b, buildOpts),
.gltf2ozz = ozz.GltfToOzz.init(nwdep.builder, .{}),
.cookShaders = b.option(bool, "cookShaders", "generates shaders and updates .json files before running the build. (needs to be done whenever shaders are updated, this just runs tools/scripts/cook-shaders.py)") orelse false,
.staticBuild = buildOpts.static_build,
.nwdep = nwdep,
.apigen = nwdep.artifact("backlog-apigen"),
.loadDynamicsGen = nwdep.artifact("backlog-generate-loadDynamics"),
.rcGen = nwdep.artifact("backlog-generate-rc"),
.shaderEmbedGen = nwdep.artifact("backlog-shaderEmbedGen"),
};
const exeList = [2]*std.Build.Step.Compile{ self.gltf2ozz.exe, self.spirvReflect.reflect };
const install_tools = b.step("tools", "installs tools needed to generate outputs for the engine");
for (exeList) |exe| {
const toolsInstall = b.addInstallArtifact(exe, .{
.dest_dir = .{ .override = .{ .custom = "tools" } },
});
install_tools.dependOn(&toolsInstall.step);
}
{
const runArtifact = b.addRunArtifact(self.gltf2ozz.exe);
if (b.args) |args| {
runArtifact.addArgs(args);
}
const run_exe = b.step("gltf2ozz", "runs the gltf animation converter.");
run_exe.dependOn(&runArtifact.step);
}
{
const runArtifact = b.addRunArtifact(self.spirvReflect.reflect);
if (b.args) |args| {
runArtifact.addArgs(args);
}
const run_exe = b.step("spv-reflect", "runs the gltf animation converter.");
run_exe.dependOn(&runArtifact.step);
}
self.addDependencyInstalls(self.b, .ReleaseFast);
return self;
}
pub const AddProgramOptions = struct {
name: []const u8,
desc: []const u8,
root_source_file: LazyPath,
imports: []const Build.Module.Import = &.{},
};
pub fn addProgram(self: *BuildSystem, opts: AddProgramOptions) *std.Build.Module {
const b = self.b;
const exe = self.nw_builder.addExecutable(.{
.name = opts.name,
.target = self.target,
.optimize = self.optimize,
.root_source_file = self.nw_builder.path("engine/main.zig"),
});
b.installArtifact(exe);
const runArtifact = b.addRunArtifact(exe);
if (b.args) |args| {
runArtifact.addArgs(args);
}
const run_exe = b.step(self.b.fmt("run-{s}", .{opts.name}), opts.desc);
run_exe.dependOn(&runArtifact.step);
// main path = name/main.zig
const mod = b.addModule(opts.name, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = opts.root_source_file,
.imports = opts.imports,
});
exe.root_module.addImport("main", mod);
// todo.. remove this one and see what happens
exe.root_module.addImport("core", self.nwdep.module("core"));
// mod.addImport("Backlog", self.nw_mod);
exe.root_module.addOptions("BacklogOptions", self.options);
if (self.cookShaders) {
const cookShadersScript = b.fmt("{s}/tools/scripts/cookShaders.py", .{self.backlogRoot});
const cookShadersCommand = b.addSystemCommand(&[_][]const u8{"python"});
cookShadersCommand.addArg(cookShadersScript);
run_exe.dependOn(&cookShadersCommand.step);
}
b.getInstallStep().dependOn(self.nw_builder.getInstallStep());
// run_exe.dependOn(b.getInstallStep());
return mod;
}
pub const BuildOptions = struct {
mutex_job_queue: bool,
static_build: bool,
zero_logging: bool,
slow_logging: bool,
force_mailbox: bool,
};
pub fn declareBuildOptions(b: *std.Build) BuildOptions {
return .{
.mutex_job_queue = b.option(bool, "mutex_job_queue", "temporary test, reverts to old mutex based queue behaviour in jobs.zig:JobManager") orelse false,
.static_build = b.option(bool, "static_build", "builds the entire game as a single executable") orelse false,
.zero_logging = b.option(bool, "zero_logging", "disables all logging, only intended for use on job dispatch testing") orelse false,
.slow_logging = b.option(bool, "slow_logging", "Disables buffered logging, takes a hit to performance but gain timing information on logging") orelse false,
.force_mailbox = b.option(bool, "force_mailbox", "forces mailbox mode for present mode. unlocks framerate to irresponsible levels") orelse false,
};
}
pub fn createGameOptions(b: *std.Build, options: BuildOptions) *std.Build.Step.Options {
const opts = b.addOptions();
inline for (@typeInfo(BuildOptions).@"struct".fields) |field| {
opts.addOption(bool, field.name, @field(options, field.name));
}
// build options for core.zig
// opts.addOption(
// bool,
// "mutex_job_queue",
// );
// opts.addOption(
// bool,
// "static_build",
// );
// opts.addOption(
// bool,
// "zero_logging",
// );
// opts.addOption(
// bool,
// "slow_logging",
// );
// opts.addOption(
// bool,
// "force_mailbox",
// );
return opts;
}
pub fn addExtraModule(self: *BuildSystem, mod: *std.Build.Module, moduleName: []const u8) void {
const dep = self.b.dependency(moduleName, .{ .target = self.target, .optimize = self.optimize, .static_build = self.staticBuild });
mod.addImport(moduleName, dep.module(moduleName));
}
pub fn addDependencyInstalls(self: *BuildSystem, b: *std.Build, optimize: std.builtin.OptimizeMode) void {
//if (self.staticBuild) {
//return;
// }
inline for (DynamicDepList) |d| {
b.installArtifact(b.dependency(
d.dep,
.{ .target = self.target, .optimize = optimize, .static_build = self.staticBuild },
).artifact(d.artifact));
}
// b.installArtifact(b.dependency(
// "sdl3",
// .{ .target = self.target, .optimize = optimize, .static_build = self.staticBuild },
// ).artifact("SDL3"));
}
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 = "zphysics", .artifact = "joltc" },
// .{ .dep = "enet", .artifact = "enet_c" },
.{ .dep = "ozz", .artifact = "ozz_cpp" },
};
// all other modules are disabled by default
pub const defaultEnabledModules: []const []const u8 = &.{
"core",
"assets",
"platform",
"rend",
};
pub const moduleOrder: []const []const u8 = &.{
"core",
"sys",
"assets",
"platform",
"net",
"physics",
"audio",
"rend",
"ui",
"imgui",
"papyrus",
};
pub const DynamicModule = struct {
name: []const u8,
allocator: std.mem.Allocator,
buildSystem: *BuildSystem,
programName: []const u8,
opts: AddProgramOptions,
extras: std.ArrayListUnmanaged(GameModule) = .{},
gameModules: std.ArrayListUnmanaged(GameModule) = .{},
library: ?*std.Build.Step.Compile = null,
pub fn addExtraModule(self: *@This(), module: []const u8) void {
self.extras.append(self.allocator, .{ .name = module, .enabled = true }) catch @panic("out of memory");
}
pub fn init(name: []const u8, p: *Program) *@This() {
var self: *@This() = p.allocator.create(@This()) catch @panic("out of memory");
self.* = .{
.opts = p.opts,
.programName = p.opts.name,
.buildSystem = p.buildSystem,
.allocator = p.allocator,
.name = name,
};
for (p.gameModules.items) |module| {
self.gameModules.append(self.allocator, module) catch @panic("out of memory");
}
for (p.extras.items) |module| {
self.extras.append(self.allocator, module) catch @panic("out of memory");
}
return self;
}
pub fn compileInstall(self: *@This()) *std.Build.Step.Compile {
if (self.library) |lib| {
return lib;
}
const b = self.buildSystem.b;
const static = self.buildSystem.staticBuild;
const lib = if (static)
b.addStaticLibrary(.{
.root_source_file = self.opts.root_source_file,
.link_libc = true,
.optimize = self.buildSystem.optimize,
.target = self.buildSystem.target,
.name = self.name,
})
else
b.addSharedLibrary(.{
.root_source_file = self.opts.root_source_file,
.link_libc = true,
.optimize = self.buildSystem.optimize,
.target = self.buildSystem.target,
.name = self.name,
});
var modlist = std.ArrayList([]const u8).init(self.buildSystem.b.allocator);
for (self.gameModules.items) |mod| {
if (mod.enabled) {
modlist.append(mod.name) catch @panic("out of memory");
}
}
for (self.extras.items) |extra| {
self.buildSystem.addExtraModule(lib.root_module, extra.name);
}
lib.root_module.addImport("backlog", self.buildSystem.generateApi(self.name, modlist.items, null));
lib.root_module.addOptions("BacklogOptions", self.buildSystem.options);
if (static) {
// generateApi should create static callers for the main program
} else {
const installExtern = b.addInstallArtifact(lib, .{
.dest_dir = .{ .override = .{ .custom = "modules" } },
});
b.getInstallStep().dependOn(&installExtern.step);
}
return lib;
}
};
pub const GameModule = struct {
name: []const u8,
enabled: bool = true,
};
pub const Program = struct {
allocator: std.mem.Allocator,
opts: AddProgramOptions,
gameModules: std.ArrayListUnmanaged(GameModule) = .{},
extras: std.ArrayListUnmanaged(GameModule) = .{},
dynamicModules: std.ArrayListUnmanaged(*DynamicModule) = .{},
buildSystem: *BuildSystem,
iconPath: ?std.Build.LazyPath = null,
pub fn setIconPath(self: *@This(), path: []const u8) void {
if (self.iconPath != null) {
return;
}
self.iconPath = self.buildSystem.generateRc(self.opts.name, path) catch return;
}
pub fn setModuleEnabled(self: *@This(), module: []const u8, enable: bool) void {
for (self.gameModules.items) |*m| {
if (std.mem.eql(u8, m.name, module)) {
m.enabled = enable;
return;
}
}
// module not found, add it here.
self.gameModules.append(self.allocator, .{ .name = module, .enabled = enable }) catch @panic("out of memory");
}
pub fn addExtraModule(self: *@This(), module: []const u8) void {
self.extras.append(self.allocator, .{ .name = module, .enabled = true }) catch @panic("out of memory");
}
pub fn addDynamicModule(self: *@This(), module: []const u8, root_source_file: std.Build.LazyPath) *DynamicModule {
const dynamicModule = DynamicModule.init(module, self);
dynamicModule.opts.root_source_file = root_source_file;
self.dynamicModules.append(self.allocator, dynamicModule) catch @panic("out of memory");
return dynamicModule;
}
pub fn compileInstall(self: *@This()) *std.Build.Module {
const exe = self.buildSystem.addProgram(self.opts);
var modlist = std.ArrayList([]const u8).init(self.buildSystem.b.allocator);
for (self.gameModules.items) |mod| {
if (mod.enabled) {
modlist.append(mod.name) catch @panic("out of memory");
}
}
for (self.extras.items) |extra| {
self.buildSystem.addExtraModule(exe, extra.name);
}
exe.addImport("backlog", self.buildSystem.generateApi(self.opts.name, modlist.items, self.dynamicModules.items));
if (self.buildSystem.target.result.os.tag == .windows) {
if (self.iconPath) |ip| {
exe.addWin32ResourceFile(.{
.file = ip,
.flags = &.{},
});
}
}
return exe;
}
};
pub fn program(self: *BuildSystem, opts: AddProgramOptions) *Program {
const prog = self.b.allocator.create(Program) catch unreachable;
prog.* = .{
.allocator = self.b.allocator,
.opts = opts,
.buildSystem = self,
};
for (moduleOrder) |module| {
prog.setModuleEnabled(module, false);
}
for (defaultEnabledModules) |module| {
prog.setModuleEnabled(module, true);
}
return prog;
}
// ========= standalone build instance =======
// maybe the default should be like an engine launcher/project launcher or something
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;
const spirvDep = b.dependency("SpirvReflect", .{
.target = target,
.optimize = optimize,
.static_build = static_build,
});
_ = spirvDep;
const fwdGeneratorExe = b.addExecutable(.{
.name = "backlog-generate-fwd",
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateFwd.zig"),
});
const generateExe = b.addExecutable(.{
.name = "backlog-apigen",
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateApi.zig"),
});
const generateShaders = b.addExecutable(.{
.name = "backlog-shaderEmbedGen",
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateEmbeddedShaders.zig"),
});
const generateRcExe = b.addExecutable(.{
.name = "backlog-generate-rc",
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateRc.zig"),
});
const generateLoadDynamicsExe = b.addExecutable(.{
.name = "backlog-generate-loadDynamics",
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateLoadDynamics.zig"),
});
b.installArtifact(generateLoadDynamicsExe);
b.installArtifact(generateExe);
b.installArtifact(generateShaders);
b.installArtifact(generateRcExe);
{
const loadDynamicsStub = b.addModule("loadDynamicsStub", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/loadDynamicsStub.zig"),
});
_ = loadDynamicsStub;
const mod = b.addModule("Backlog", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/backlog.zig"),
});
for (engineDepList) |depName| {
const dep = b.dependency(
depName,
.{
.target = target,
.optimize = optimize,
.static_build = static_build,
},
);
const run = b.addRunArtifact(fwdGeneratorExe);
const output = run.addOutputFileArg(b.fmt("{s}_fwd.zig", .{depName}));
const modFwd = b.addModule(depName, .{
.target = target,
.optimize = optimize,
.root_source_file = output,
});
mod.addImport(b.fmt("{s}", .{depName}), modFwd);
modFwd.addImport("module", dep.module(depName));
// mod.addImport(depName, dep.module(depName));
}
// link in large platform support functions... special case.
{
const dep = b.dependency("sdl3", .{
.target = target,
.optimize = optimize,
.static_build = static_build,
});
const lib = dep.module("SDL3");
mod.addImport("sdl3_fwd", lib);
}
}
}
pub fn generateApi(self: *@This(), programName: []const u8, moduleList: []const []const u8, dynamicModules: ?[]const *DynamicModule) *std.Build.Module {
// std.debug.print("GENERATE API:: {s}\n", .{programName});
for (moduleList) |mod| {
_ = mod;
// std.debug.print("{s}\n", .{mod});
}
//
if (self.generatedApis.get(programName)) |m| {
return m;
}
const run = self.b.addRunArtifact(self.apigen);
const pfile = self.b.fmt("{s}Api.zig", .{programName});
const output = run.addOutputFileArg(pfile);
for (moduleList) |modName| {
run.addArg(modName);
}
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
for (moduleList) |modName| {
mod.addImport(modName, self.nwdep.module(modName));
}
const loadStatics = self.generateInstallStaticResources(programName, "content/_shaders") catch unreachable;
mod.addImport("staticShaderLoader", loadStatics);
// deal with dynamics
if (dynamicModules) |dynamics| {
const loadDynamics = self.generateLoadDynamics(programName, dynamics) catch @panic("unknown");
mod.addImport("loadDynamics", loadDynamics);
} else {
mod.addImport("loadDynamics", self.nwdep.module("loadDynamicsStub"));
}
self.generatedApis.put(self.b.allocator, programName, mod) catch @panic("out of memory");
return mod;
}
pub fn generateRc(self: *@This(), programName: []const u8, iconPath: []const u8) !std.Build.LazyPath {
const run = self.b.addRunArtifact(self.rcGen);
const pfile = self.b.fmt("{s}.rc", .{programName});
const output = run.addOutputFileArg(pfile);
run.addArg(iconPath);
return output;
}
pub fn generateLoadDynamics(self: *@This(), programName: []const u8, dynamics: []const *DynamicModule) !*std.Build.Module {
const run = self.b.addRunArtifact(self.loadDynamicsGen);
const pfile = self.b.fmt("{s}LoadDynamics.zig", .{programName});
const output = run.addOutputFileArg(pfile);
if (self.staticBuild) {
run.addArg("static");
} else {
run.addArg("dynamic");
}
for (dynamics) |dyn| {
run.addArg(dyn.name);
}
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
mod.addImport("core", self.nwdep.module("core"));
for (dynamics) |dyn| {
const dynmod = dyn.compileInstall();
mod.addImport(dyn.name, dynmod.root_module);
}
return mod;
}
pub fn generateInstallStaticResources(self: *@This(), programName: []const u8, shadersPath: []const u8) !*std.Build.Module {
const run = self.b.addRunArtifact(self.shaderEmbedGen);
const pfile = self.b.fmt("{s}ShaderGen.zig", .{programName});
const output = run.addOutputFileArg(pfile);
run.addArg(shadersPath);
const b = self.b;
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
mod.addImport("core", self.nwdep.module("core"));
var dir = try std.fs.cwd().openDir("content/_shaders", .{ .iterate = true });
defer dir.close();
var walker = dir.iterate();
while (try walker.next()) |shaderType| {
if (shaderType.kind == .directory) {
var d2 = try dir.openDir(shaderType.name, .{ .iterate = true });
defer d2.close();
var w2 = d2.iterate();
while (try w2.next()) |shaderName| {
const shaderPath = b.fmt("content/_shaders/{s}/{s}", .{ shaderType.name, shaderName.name });
mod.addAnonymousImport(shaderName.name, .{
.root_source_file = b.path(shaderPath),
});
}
}
}
return mod;
}

584
build.zig
View File

@ -1,27 +1,42 @@
// MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS=1 -- use this if you're getting that odd crash on mac
b: *std.Build,
bEngine: *std.Build, // used to resolve executable paths from within the engine's root directory
opts: bh.Options,
nw_builder: *std.Build,
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
nw_mod: *std.Build.Module,
gltf2ozz: ozz.GltfToOzz,
options: *std.Build.Step.Options,
cookShaders: bool,
backlogRoot: []const u8,
// list of all shaders discovered under
// content/_shaders/def
reflectShaderPathList: [][]u8 = undefined,
staticBuild: bool = false,
nwdep: *std.Build.Dependency,
// apigen: *std.Build.Step.Compile,
apigen: *std.Build.Step.Compile,
shaderEmbedGen: *std.Build.Step.Compile,
loadDynamicsGen: *std.Build.Step.Compile,
rcGen: *std.Build.Step.Compile,
specGen: *std.Build.Step.Compile,
generatedApis: std.StringHashMapUnmanaged(*std.Build.Module) = .{},
pub const BuildSystem = @This();
const engineDepList = [_][]const u8{
"assets",
"audio",
"core",
"net",
"papyrus",
"platform",
"rend",
"ui",
"imgui",
"physics",
"sys",
};
const BuildSystem = @This();
const std = @import("std");
const Build = std.Build;
const LazyPath = Build.LazyPath;
@ -31,47 +46,41 @@ const ozz = @import("ozz");
pub const InitOptions = struct {
import_name: []const u8 = "Backlog",
backlogRoot: []const u8 = "./BacklogEngine",
target: std.Build.ResolvedTarget,
staticBuild: bool = false,
target: Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
};
pub fn init(b: *std.Build, initOptions: InitOptions) BuildSystem {
const buildOpts = declareBuildOptions(b);
const opts = bh.Options{
.target = initOptions.target,
.optimize = initOptions.optimize,
.static_build = b.option(bool, "static_build", "builds backlog dependencies for static linking") orelse false,
.tracy = b.option(bool, "tracy", "builds with tracy support") orelse false,
};
pub fn init(b: *std.Build, opts: InitOptions) BuildSystem {
var buildOpts = declareBuildOptions(b);
if (opts.target.result.os.tag == .linux) {
// using a static build for linux... object loading hell is not fun...
// i have skill issues in that realm right now
// std.debug.print("Important! only supporting static builds for linux", .{});
// opts.static_build = true;
// using a static build for linux... object loading hell is not fun
std.debug.print("Important! only supporting static builds for linux", .{});
buildOpts.static_build = true;
}
const nwdep = b.dependency(initOptions.import_name, .{
const nwdep = b.dependency(opts.import_name, .{
.target = opts.target,
.optimize = opts.optimize,
.static_build = opts.static_build,
.static_build = buildOpts.static_build,
});
var self = BuildSystem{
.b = b,
.bEngine = nwdep.builder,
.opts = opts,
.nw_builder = nwdep.builder,
.target = opts.target,
.optimize = opts.optimize,
.nw_mod = nwdep.module("Backlog"),
.backlogRoot = initOptions.backlogRoot,
.options = createGameOptions(b, buildOpts, opts),
.backlogRoot = opts.backlogRoot,
.options = createGameOptions(b, buildOpts),
.gltf2ozz = ozz.GltfToOzz.init(nwdep.builder, .{}),
.cookShaders = b.option(bool, "cookShaders", "generates shaders and updates .json files before running the build. (needs to be done whenever shaders are updated, this just runs tools/scripts/cook-shaders.py)") orelse false,
.staticBuild = buildOpts.static_build,
.nwdep = nwdep,
// .apigen = nwdep.artifact("backlog-apigen"),
.apigen = nwdep.artifact("backlog-apigen"),
.loadDynamicsGen = nwdep.artifact("backlog-generate-loadDynamics"),
.specGen = nwdep.artifact("backlog-generate-spec"),
.rcGen = nwdep.artifact("backlog-generate-rc"),
.shaderEmbedGen = nwdep.artifact("backlog-shaderEmbedGen"),
};
@ -101,55 +110,135 @@ pub fn init(b: *std.Build, initOptions: InitOptions) BuildSystem {
return self;
}
// Build Options for the engine,
// should generally be written as false = default
// true = enabling something
const BuildOptions = struct {
pub const AddProgramOptions = struct {
name: []const u8,
desc: []const u8,
root_source_file: LazyPath,
imports: []const Build.Module.Import = &.{},
};
pub fn addProgram(self: *BuildSystem, opts: AddProgramOptions) *std.Build.Module {
const b = self.b;
const exe = self.nw_builder.addExecutable(.{
.name = opts.name,
.root_module = b.createModule(.{
.target = self.target,
.optimize = self.optimize,
.root_source_file = self.nw_builder.path("engine/main.zig"),
}),
});
b.installArtifact(exe);
const runArtifact = b.addRunArtifact(exe);
if (b.args) |args| {
runArtifact.addArgs(args);
}
const run_exe = b.step(self.b.fmt("run-{s}", .{opts.name}), opts.desc);
run_exe.dependOn(&runArtifact.step);
// main path = name/main.zig
const mod = b.addModule(opts.name, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = opts.root_source_file,
.imports = opts.imports,
});
exe.root_module.addImport("main", mod);
// todo.. remove this one and see what happens
exe.root_module.addImport("core", self.nwdep.module("core"));
// mod.addImport("Backlog", self.nw_mod);
exe.root_module.addOptions("BacklogOptions", self.options);
if (self.cookShaders) {
const cookShadersScript = b.fmt("{s}/tools/scripts/cookShaders.py", .{self.backlogRoot});
const cookShadersCommand = b.addSystemCommand(&[_][]const u8{"python"});
cookShadersCommand.addArg(cookShadersScript);
run_exe.dependOn(&cookShadersCommand.step);
}
b.getInstallStep().dependOn(self.nw_builder.getInstallStep());
// run_exe.dependOn(b.getInstallStep());
return mod;
}
pub const BuildOptions = struct {
mutex_job_queue: bool,
static_build: bool,
zero_logging: bool,
slow_logging: bool,
force_mailbox: bool,
};
fn declareBuildOptions(b: *std.Build) BuildOptions {
pub fn declareBuildOptions(b: *std.Build) BuildOptions {
return .{
.mutex_job_queue = b.option(bool, "mutex_job_queue", "temporary test, reverts to old mutex based queue behaviour in jobs.zig:JobManager") orelse false,
.static_build = b.option(bool, "static_build", "builds the entire game as a single executable") orelse false,
.zero_logging = b.option(bool, "zero_logging", "disables all logging, only intended for use on job dispatch testing") orelse false,
.slow_logging = b.option(bool, "slow_logging", "Disables buffered logging, takes a hit to performance but gain timing information on logging") orelse false,
.force_mailbox = b.option(bool, "force_mailbox", "forces mailbox mode for present mode. unlocks framerate to irresponsible levels") orelse false,
};
}
fn createGameOptions(b: *std.Build, options: BuildOptions, bhopts: bh.Options) *std.Build.Step.Options {
pub fn createGameOptions(b: *std.Build, options: BuildOptions) *std.Build.Step.Options {
const opts = b.addOptions();
inline for (@typeInfo(BuildOptions).@"struct".fields) |field| {
opts.addOption(bool, field.name, @field(options, field.name));
}
// forward the options to the build options
opts.addOption(bool, "tracy", bhopts.tracy);
opts.addOption(bool, "static_build", bhopts.static_build);
// build options for core.zig
// opts.addOption(
// bool,
// "mutex_job_queue",
// );
// opts.addOption(
// bool,
// "static_build",
// );
// opts.addOption(
// bool,
// "zero_logging",
// );
// opts.addOption(
// bool,
// "slow_logging",
// );
// opts.addOption(
// bool,
// "force_mailbox",
// );
return opts;
}
pub fn addExtraModule(self: *BuildSystem, mod: *std.Build.Module, moduleName: []const u8) void {
const dep = self.b.dependency(moduleName, .{ .target = self.opts.target, .optimize = self.opts.optimize, .static_build = self.opts.static_build });
const dep = self.b.dependency(moduleName, .{ .target = self.target, .optimize = self.optimize, .static_build = self.staticBuild });
mod.addImport(moduleName, dep.module(moduleName));
}
fn addDependencyInstalls(self: *BuildSystem, b: *std.Build, optimize: std.builtin.OptimizeMode) void {
if (self.opts.static_build) {
return;
}
pub fn addDependencyInstalls(self: *BuildSystem, b: *std.Build, optimize: std.builtin.OptimizeMode) void {
//if (self.staticBuild) {
//return;
// }
inline for (DynamicDepList) |d| {
b.installArtifact(b.dependency(
d.dep,
.{ .target = self.opts.target, .optimize = optimize, .static_build = self.opts.static_build },
.{ .target = self.target, .optimize = optimize, .static_build = self.staticBuild },
).artifact(d.artifact));
}
// b.installArtifact(b.dependency(
// "sdl3",
// .{ .target = self.target, .optimize = optimize, .static_build = self.staticBuild },
// ).artifact("SDL3"));
}
const DynamicDepList: []const struct { dep: []const u8, artifact: []const u8 } = &.{
@ -162,19 +251,365 @@ const DynamicDepList: []const struct { dep: []const u8, artifact: []const u8 } =
.{ .dep = "ozz", .artifact = "ozz_cpp" },
};
pub fn addProgram(
self: *@This(),
// all other modules are disabled by default
pub const defaultEnabledModules: []const []const u8 = &.{
"core",
"assets",
"platform",
"rend",
};
pub const moduleOrder: []const []const u8 = &.{
"core",
"sys",
"assets",
"platform",
"net",
"physics",
"audio",
"rend",
"ui",
"imgui",
"papyrus",
};
pub const DynamicModule = struct {
name: []const u8,
) *Program {
const p = self.b.allocator.create(Program) catch unreachable;
p.* = .{
.name = name,
.opts = self.opts,
.buildSystem = self,
allocator: std.mem.Allocator,
buildSystem: *BuildSystem,
programName: []const u8,
opts: AddProgramOptions,
extras: std.ArrayListUnmanaged(GameModule) = .{},
gameModules: std.ArrayListUnmanaged(GameModule) = .{},
library: ?*std.Build.Step.Compile = null,
pub fn addExtraModule(self: *@This(), module: []const u8) void {
self.extras.append(self.allocator, .{ .name = module, .enabled = true }) catch @panic("out of memory");
}
pub fn init(name: []const u8, p: *Program) *@This() {
var self: *@This() = p.allocator.create(@This()) catch @panic("out of memory");
self.* = .{
.opts = p.opts,
.programName = p.opts.name,
.buildSystem = p.buildSystem,
.allocator = p.allocator,
.name = name,
};
for (p.gameModules.items) |module| {
self.gameModules.append(self.allocator, module) catch @panic("out of memory");
}
for (p.extras.items) |module| {
self.extras.append(self.allocator, module) catch @panic("out of memory");
}
return self;
}
pub fn compileInstall(self: *@This()) *std.Build.Step.Compile {
if (self.library) |lib| {
return lib;
}
const b = self.buildSystem.b;
const static = self.buildSystem.staticBuild;
const lib = b.addLibrary(.{
.name = self.name,
.linkage = if (static) .static else .dynamic,
.root_module = b.createModule(.{
.root_source_file = self.opts.root_source_file,
.link_libc = true,
.optimize = self.buildSystem.optimize,
.target = self.buildSystem.target,
}),
});
const allocator = self.buildSystem.b.allocator;
var modlist = std.ArrayList([]const u8){};
for (self.gameModules.items) |mod| {
if (mod.enabled) {
modlist.append(allocator, mod.name) catch @panic("out of memory");
}
}
for (self.extras.items) |extra| {
self.buildSystem.addExtraModule(lib.root_module, extra.name);
}
lib.root_module.addImport("backlog", self.buildSystem.generateApi(self.name, modlist.items, null));
lib.root_module.addOptions("BacklogOptions", self.buildSystem.options);
if (static) {
// generateApi should create static callers for the main program
} else {
const installExtern = b.addInstallArtifact(lib, .{
.dest_dir = .{ .override = .{ .custom = "modules" } },
});
b.getInstallStep().dependOn(&installExtern.step);
}
return lib;
}
};
pub const GameModule = struct {
name: []const u8,
enabled: bool = true,
};
pub const Program = struct {
allocator: std.mem.Allocator,
opts: AddProgramOptions,
gameModules: std.ArrayListUnmanaged(GameModule) = .{},
extras: std.ArrayListUnmanaged(GameModule) = .{},
dynamicModules: std.ArrayListUnmanaged(*DynamicModule) = .{},
buildSystem: *BuildSystem,
iconPath: ?std.Build.LazyPath = null,
pub fn setIconPath(self: *@This(), path: []const u8) void {
if (self.iconPath != null) {
return;
}
self.iconPath = self.buildSystem.generateRc(self.opts.name, path) catch return;
}
pub fn setModuleEnabled(self: *@This(), module: []const u8, enable: bool) void {
for (self.gameModules.items) |*m| {
if (std.mem.eql(u8, m.name, module)) {
m.enabled = enable;
return;
}
}
// module not found, add it here.
self.gameModules.append(self.allocator, .{ .name = module, .enabled = enable }) catch @panic("out of memory");
}
pub fn addExtraModule(self: *@This(), module: []const u8) void {
self.extras.append(self.allocator, .{ .name = module, .enabled = true }) catch @panic("out of memory");
}
pub fn addDynamicModule(self: *@This(), module: []const u8, root_source_file: std.Build.LazyPath) *DynamicModule {
const dynamicModule = DynamicModule.init(module, self);
dynamicModule.opts.root_source_file = root_source_file;
self.dynamicModules.append(self.allocator, dynamicModule) catch @panic("out of memory");
return dynamicModule;
}
pub fn compileInstall(self: *@This()) *std.Build.Module {
const exe = self.buildSystem.addProgram(self.opts);
const allocator = self.buildSystem.b.allocator;
var modlist = std.ArrayList([]const u8){};
for (self.gameModules.items) |mod| {
if (mod.enabled) {
modlist.append(allocator, mod.name) catch @panic("out of memory");
}
}
for (self.extras.items) |extra| {
self.buildSystem.addExtraModule(exe, extra.name);
}
exe.addImport("backlog", self.buildSystem.generateApi(self.opts.name, modlist.items, self.dynamicModules.items));
if (self.buildSystem.target.result.os.tag == .windows) {
if (self.iconPath) |ip| {
exe.addWin32ResourceFile(.{
.file = ip,
.flags = &.{},
});
}
}
return exe;
}
};
pub fn program(self: *BuildSystem, opts: AddProgramOptions) *Program {
const prog = self.b.allocator.create(Program) catch unreachable;
prog.* = .{
.allocator = self.b.allocator,
.opts = opts,
.buildSystem = self,
};
return p;
for (moduleOrder) |module| {
prog.setModuleEnabled(module, false);
}
for (defaultEnabledModules) |module| {
prog.setModuleEnabled(module, true);
}
return prog;
}
// ========= standalone build instance =======
// maybe the default should be like an engine launcher/project launcher or something
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;
const fwdGeneratorExe = b.addExecutable(.{
.name = "backlog-generate-fwd",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateFwd.zig"),
}),
});
const generateExe = b.addExecutable(.{
.name = "backlog-apigen",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateApi.zig"),
}),
});
const generateShaders = b.addExecutable(.{
.name = "backlog-shaderEmbedGen",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateEmbeddedShaders.zig"),
}),
});
const generateRcExe = b.addExecutable(.{
.name = "backlog-generate-rc",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateRc.zig"),
}),
});
const generateLoadDynamicsExe = b.addExecutable(.{
.name = "backlog-generate-loadDynamics",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("build/generateLoadDynamics.zig"),
}),
});
b.installArtifact(generateLoadDynamicsExe);
b.installArtifact(generateExe);
b.installArtifact(generateShaders);
b.installArtifact(generateRcExe);
{
const loadDynamicsStub = b.addModule("loadDynamicsStub", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/loadDynamicsStub.zig"),
});
_ = loadDynamicsStub;
const mod = b.addModule("Backlog", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/backlog.zig"),
});
for (engineDepList) |depName| {
const dep = b.dependency(
depName,
.{
.target = target,
.optimize = optimize,
.static_build = static_build,
},
);
const run = b.addRunArtifact(fwdGeneratorExe);
const output = run.addOutputFileArg(b.fmt("{s}_fwd.zig", .{depName}));
const modFwd = b.addModule(depName, .{
.target = target,
.optimize = optimize,
.root_source_file = output,
});
mod.addImport(b.fmt("{s}", .{depName}), modFwd);
modFwd.addImport("module", dep.module(depName));
// mod.addImport(depName, dep.module(depName));
}
// link in large platform support functions... special case.
{
const dep = b.dependency("sdl3", .{
.target = target,
.optimize = optimize,
.static_build = static_build,
});
const lib = dep.module("SDL3");
mod.addImport("sdl3_fwd", lib);
}
}
}
pub fn generateApi(self: *@This(), programName: []const u8, moduleList: []const []const u8, dynamicModules: ?[]const *DynamicModule) *std.Build.Module {
// std.debug.print("GENERATE API:: {s}\n", .{programName});
for (moduleList) |mod| {
_ = mod;
// std.debug.print("{s}\n", .{mod});
}
//
if (self.generatedApis.get(programName)) |m| {
return m;
}
const run = self.b.addRunArtifact(self.apigen);
const pfile = self.b.fmt("{s}Api.zig", .{programName});
const output = run.addOutputFileArg(pfile);
for (moduleList) |modName| {
run.addArg(modName);
}
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
for (moduleList) |modName| {
mod.addImport(modName, self.nwdep.module(modName));
}
const loadStatics = self.generateInstallStaticResources(programName, "content/_shaders") catch unreachable;
mod.addImport("staticShaderLoader", loadStatics);
// deal with dynamics
if (dynamicModules) |dynamics| {
const loadDynamics = self.generateLoadDynamics(programName, dynamics) catch @panic("unknown");
mod.addImport("loadDynamics", loadDynamics);
} else {
mod.addImport("loadDynamics", self.nwdep.module("loadDynamicsStub"));
}
self.generatedApis.put(self.b.allocator, programName, mod) catch @panic("out of memory");
return mod;
}
pub fn generateRc(self: *@This(), programName: []const u8, iconPath: []const u8) !std.Build.LazyPath {
@ -186,6 +621,35 @@ pub fn generateRc(self: *@This(), programName: []const u8, iconPath: []const u8)
return output;
}
pub fn generateLoadDynamics(self: *@This(), programName: []const u8, dynamics: []const *DynamicModule) !*std.Build.Module {
const run = self.b.addRunArtifact(self.loadDynamicsGen);
const pfile = self.b.fmt("{s}LoadDynamics.zig", .{programName});
const output = run.addOutputFileArg(pfile);
if (self.staticBuild) {
run.addArg("static");
} else {
run.addArg("dynamic");
}
for (dynamics) |dyn| {
run.addArg(dyn.name);
}
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
mod.addImport("core", self.nwdep.module("core"));
for (dynamics) |dyn| {
const dynmod = dyn.compileInstall();
mod.addImport(dyn.name, dynmod.root_module);
}
return mod;
}
pub fn generateInstallStaticResources(self: *@This(), programName: []const u8, shadersPath: []const u8) !*std.Build.Module {
const run = self.b.addRunArtifact(self.shaderEmbedGen);
const pfile = self.b.fmt("{s}ShaderGen.zig", .{programName});
@ -194,8 +658,8 @@ pub fn generateInstallStaticResources(self: *@This(), programName: []const u8, s
const b = self.b;
const mod = self.b.addModule(pfile, .{
.target = self.opts.target,
.optimize = self.opts.optimize,
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
mod.addImport("core", self.nwdep.module("core"));
@ -220,15 +684,3 @@ pub fn generateInstallStaticResources(self: *@This(), programName: []const u8, s
return mod;
}
// ========= standalone build instance =======
// maybe the default should be like an engine launcher/project launcher or something
pub fn build(b: *std.Build) void {
const opts = bh.declareOptions(b);
engineDeps.buildGenerators(b, opts);
}
const bh = @import("bh");
const engineDeps = @import("build/engineDeps.zig");
const program = @import("build/program.zig");
const Program = program.Program;

View File

@ -1,23 +1,28 @@
.{ .name = .Backlog, .version = "0.0.0", .dependencies = .{
.assets = .{ .path = "engine/assets" },
.audio = .{ .path = "engine/audio" },
.core = .{ .path = "engine/core" },
.net = .{ .path = "engine/net" },
.papyrus = .{ .path = "engine/papyrus" },
.physics = .{ .path = "engine/physics" },
.platform = .{ .path = "engine/platform" },
.imgui = .{ .path = "engine/imgui" },
.ui = .{ .path = "engine/ui" },
.sys = .{ .path = "engine/sys" },
.rend = .{ .path = "engine/rend" },
.{
.name = .Backlog,
.version = "0.0.0",
.dependencies = .{
.assets = .{ .path = "engine/assets" },
.audio = .{ .path = "engine/audio" },
.core = .{ .path = "engine/core" },
.net = .{ .path = "engine/net" },
.papyrus = .{ .path = "engine/papyrus" },
.physics = .{ .path = "engine/physics" },
.platform = .{ .path = "engine/platform" },
.imgui = .{ .path = "engine/imgui" },
.ui = .{ .path = "engine/ui" },
.sys = .{ .path = "engine/sys" },
.rend = .{.path = "engine/rend" },
.ozz = .{ .path = "lib/ozz" },
.spng = .{ .path = "lib/spng" },
.zphysics = .{ .path = "lib/zphysics" },
.ozz = .{ .path = "lib/ozz" },
.spng = .{ .path = "lib/spng" },
.zphysics = .{ .path = "lib/zphysics" },
.sdl3 = .{ .path = "lib/sdl3" },
.enet = .{ .path = "lib/enet" },
.bh = .{ .path = "lib/bh" },
}, .paths = .{
"",
}, .fingerprint = 0xcf9bab998abe37e3 }
.sdl3 = .{ .path = "lib/sdl3" },
.enet = .{ .path = "lib/enet" },
},
.paths = .{
"",
},
.fingerprint = 0xcf9bab998abe37e3
}

View File

@ -1,84 +0,0 @@
pub const AddProgramOptions = struct {
name: []const u8,
desc: []const u8,
root_source_file: LazyPath,
imports: []const Build.Module.Import = &.{},
};
pub const Program = struct {
allocator: std.mem.Allocator,
opts: AddProgramOptions,
gameModules: std.ArrayListUnmanaged(GameModule) = .{},
extras: std.ArrayListUnmanaged(GameModule) = .{},
dynamicModules: std.ArrayListUnmanaged(*DynamicModule) = .{},
buildSystem: *BuildSystem,
iconPath: ?std.Build.LazyPath = null,
pub fn setIconPath(self: *@This(), path: []const u8) void {
if (self.iconPath != null) {
return;
}
self.iconPath = self.buildSystem.generateRc(self.opts.name, path) catch return;
}
pub fn setModuleEnabled(self: *@This(), module: []const u8, enable: bool) void {
for (self.gameModules.items) |*m| {
if (std.mem.eql(u8, m.name, module)) {
m.enabled = enable;
return;
}
}
// module not found, add it here.
self.gameModules.append(self.allocator, .{ .name = module, .enabled = enable }) catch @panic("out of memory");
}
pub fn addExtraModule(self: *@This(), module: []const u8) void {
self.extras.append(self.allocator, .{ .name = module, .enabled = true }) catch @panic("out of memory");
}
pub fn addDynamicModule(self: *@This(), module: []const u8, root_source_file: std.Build.LazyPath) *DynamicModule {
const dynamicModule = DynamicModule.init(module, self);
dynamicModule.opts.root_source_file = root_source_file;
self.dynamicModules.append(self.allocator, dynamicModule) catch @panic("out of memory");
return dynamicModule;
}
pub fn compileInstall(self: *@This()) *std.Build.Module {
const exe = self.buildSystem.addProgram(self.opts);
const allocator = self.buildSystem.b.allocator;
var modlist = std.ArrayList([]const u8){};
for (self.gameModules.items) |mod| {
if (mod.enabled) {
modlist.append(allocator, mod.name) catch @panic("out of memory");
}
}
for (self.extras.items) |extra| {
self.buildSystem.addExtraModule(exe, extra.name);
}
exe.addImport("backlog", self.buildSystem.generateApi(self.opts.name, modlist.items, self.dynamicModules.items));
if (self.buildSystem.target.result.os.tag == .windows) {
if (self.iconPath) |ip| {
exe.addWin32ResourceFile(.{
.file = ip,
.flags = &.{},
});
}
}
return exe;
}
};
const std = @import("std");
const Build = std.Build;
const LazyPath = Build.LazyPath;

View File

@ -1,138 +0,0 @@
const engineDepList = [_][]const u8{
"assets",
"audio",
"core",
"net",
"papyrus",
"platform",
"rend",
"ui",
"imgui",
"physics",
"sys",
};
pub fn depList() []const []const u8 {
return &engineDepList;
}
pub fn buildGenerators(b: *std.Build, opts: bh.Options) void {
const target = opts.target;
const optimize = opts.optimize;
const static_build = opts.static_build;
const fwdGeneratorExe = b.addExecutable(.{
.name = "backlog-generate-fwd",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateFwd.zig"),
}),
});
const generateModApiExe = b.addExecutable(.{
.name = "backlog-generate-modapi",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateModApi.zig"),
}),
});
const generateProgramSpecExe = b.addExecutable(.{
.name = "backlog-generate-spec",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateProgramSpec.zig"),
}),
});
const generateShaders = b.addExecutable(.{
.name = "backlog-shaderEmbedGen",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateEmbeddedShaders.zig"),
}),
});
const generateRcExe = b.addExecutable(.{
.name = "backlog-generate-rc",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateRc.zig"),
}),
});
const generateLoadDynamicsExe = b.addExecutable(.{
.name = "backlog-generate-loadDynamics",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateLoadDynamics.zig"),
}),
});
b.installArtifact(fwdGeneratorExe);
b.installArtifact(generateLoadDynamicsExe);
b.installArtifact(generateProgramSpecExe);
b.installArtifact(generateShaders);
b.installArtifact(generateRcExe);
b.installArtifact(generateModApiExe);
{
const loadDynamicsStub = b.addModule("loadDynamicsStub", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/loadDynamicsStub.zig"),
});
_ = loadDynamicsStub;
const mod = b.addModule("Backlog", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/backlog.zig"),
});
for (depList()) |depName| {
const dep = b.dependency(
depName,
.{
.target = target,
.optimize = optimize,
.static_build = static_build,
},
);
const run = b.addRunArtifact(fwdGeneratorExe);
const output = run.addOutputFileArg(b.fmt("{s}_fwd.zig", .{depName}));
const modFwd = b.addModule(depName, .{
.target = target,
.optimize = optimize,
.root_source_file = output,
});
mod.addImport(b.fmt("{s}", .{depName}), modFwd);
modFwd.addImport("module", dep.module(depName));
}
// link in large platform support functions... special case.
{
const dep = b.dependency("sdl3", .{
.target = target,
.optimize = optimize,
.static_build = static_build,
});
const lib = dep.module("SDL3");
mod.addImport("sdl3_fwd", lib);
}
}
}
const std = @import("std");
const bh = @import("bh");

View File

@ -90,7 +90,7 @@ pub fn main() !void {
\\ pub fn startEngine(vtable: *core.EngineObjectVTable, spec: *core.SpecVariantMap) bool {
\\ const args = getArgs() catch return false;
\\
\\ var backingAllocator: std.mem.Allocator = std.heap.smp_allocator;
\\ var backingAllocator: std.mem.Allocator = std.heap.c_allocator;
\\ var gpa: std.heap.GeneralPurposeAllocator(.{
\\ .stack_trace_frames = 20,
\\ }) = .{};
@ -162,7 +162,7 @@ pub fn main() !void {
try writer.print(allocator, ".{s} = true,\n", .{mod});
}
try writer.print(allocator, "}}, std.heap.smp_allocator); }}\n", .{});
try writer.print(allocator, "}}, std.heap.c_allocator); }}\n", .{});
try writer.print(allocator, "const std = @import(\"std\");\n\n", .{});

View File

@ -73,10 +73,10 @@ pub fn main() !void {
var ast = try std.zig.Ast.parse(allocator, @as([:0]const u8, @ptrCast(writer.items[0 .. writer.items.len - 1])), .zig);
// std.debug.print("output=\n{s}", .{content.items[0 .. content.items.len - 1]});
defer ast.deinit(allocator);
const out = try ast.renderAlloc(allocator);
std.debug.print("output=\n{s}", .{out});
// Write the content to the file
try file.writeAll(out);
}

View File

@ -1,182 +0,0 @@
// legacy code, kept here as reference
pub const GameModule = struct {
name: []const u8,
enabled: bool = true,
};
pub fn program(self: *BuildSystem, opts: AddProgramOptions) *Program {
const prog = self.b.allocator.create(Program) catch unreachable;
prog.* = .{
.allocator = self.b.allocator,
.opts = opts,
.buildSystem = self,
};
for (moduleOrder) |module| {
prog.setModuleEnabled(module, false);
}
for (defaultEnabledModules) |module| {
prog.setModuleEnabled(module, true);
}
return prog;
}
pub const DynamicModule = struct {
name: []const u8,
allocator: std.mem.Allocator,
buildSystem: *BuildSystem,
programName: []const u8,
opts: Program.AddProgramOptions,
extras: std.ArrayListUnmanaged(Program.GameModule) = .{},
gameModules: std.ArrayListUnmanaged(Program.GameModule) = .{},
library: ?*std.Build.Step.Compile = null,
pub fn addExtraModule(self: *@This(), module: []const u8) void {
self.extras.append(self.allocator, .{ .name = module, .enabled = true }) catch @panic("out of memory");
}
pub fn init(name: []const u8, p: *Program) *@This() {
var self: *@This() = p.allocator.create(@This()) catch @panic("out of memory");
self.* = .{
.opts = p.opts,
.programName = p.opts.name,
.buildSystem = p.buildSystem,
.allocator = p.allocator,
.name = name,
};
for (p.gameModules.items) |module| {
self.gameModules.append(self.allocator, module) catch @panic("out of memory");
}
for (p.extras.items) |module| {
self.extras.append(self.allocator, module) catch @panic("out of memory");
}
return self;
}
pub fn compileInstall(self: *@This()) *std.Build.Step.Compile {
if (self.library) |lib| {
return lib;
}
const b = self.buildSystem.b;
const static = self.buildSystem.staticBuild;
const lib = b.addLibrary(.{
.name = self.name,
.linkage = if (static) .static else .dynamic,
.root_module = b.createModule(.{
.root_source_file = self.opts.root_source_file,
.link_libc = true,
.optimize = self.buildSystem.optimize,
.target = self.buildSystem.target,
}),
});
const allocator = self.buildSystem.b.allocator;
var modlist = std.ArrayList([]const u8){};
for (self.gameModules.items) |mod| {
if (mod.enabled) {
modlist.append(allocator, mod.name) catch @panic("out of memory");
}
}
for (self.extras.items) |extra| {
self.buildSystem.addExtraModule(lib.root_module, extra.name);
}
lib.root_module.addImport("backlog", self.buildSystem.generateApi(self.name, modlist.items, null));
lib.root_module.addOptions("BacklogOptions", self.buildSystem.options);
if (!static) {
const installExtern = b.addInstallArtifact(lib, .{
.dest_dir = .{ .override = .{ .custom = "modules" } },
});
b.getInstallStep().dependOn(&installExtern.step);
}
return lib;
}
};
pub fn generateApi(self: *@This(), programName: []const u8, moduleList: []const []const u8, dynamicModules: ?[]const *DynamicModule,) *std.Build.Module {
// std.debug.print("GENERATE API:: {s}\n", .{programName});
for (moduleList) |mod| {
_ = mod;
// std.debug.print("{s}\n", .{mod});
}
//
if (self.generatedApis.get(programName)) |m| {
return m;
}
const run = self.b.addRunArtifact(self.apigen);
const pfile = self.b.fmt("{s}Api.zig", .{programName});
const output = run.addOutputFileArg(pfile);
for (moduleList) |modName| {
run.addArg(modName);
}
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
for (moduleList) |modName| {
mod.addImport(modName, self.nwdep.module(modName));
}
const loadStatics = self.generateInstallStaticResources(programName, "content/_shaders") catch unreachable;
mod.addImport("staticShaderLoader", loadStatics);
// deal with dynamics
if (dynamicModules) |dynamics| {
const loadDynamics = self.generateLoadDynamics(programName, dynamics) catch @panic("unknown");
mod.addImport("loadDynamics", loadDynamics);
} else {
mod.addImport("loadDynamics", self.nwdep.module("loadDynamicsStub"));
}
self.generatedApis.put(self.b.allocator, programName, mod) catch @panic("out of memory");
return mod;
}
pub fn generateLoadDynamics(self: *@This(), programName: []const u8, dynamics: []const *DynamicModule) !*std.Build.Module {
const run = self.b.addRunArtifact(self.loadDynamicsGen);
const pfile = self.b.fmt("{s}LoadDynamics.zig", .{programName});
const output = run.addOutputFileArg(pfile);
if (self.staticBuild) {
run.addArg("static");
} else {
run.addArg("dynamic");
}
for (dynamics) |dyn| {
run.addArg(dyn.name);
}
const mod = self.b.addModule(pfile, .{
.target = self.target,
.optimize = self.optimize,
.root_source_file = output,
});
mod.addImport("core", self.nwdep.module("core"));
for (dynamics) |dyn| {
const dynmod = dyn.compileInstall();
mod.addImport(dyn.name, dynmod.root_module);
}
return mod;
}

View File

@ -1,227 +0,0 @@
pub const AddProgramOptions = struct {
name: []const u8,
opt: bh.Options,
root_source_file: std.Build.LazyPath,
};
// all other modules are disabled by default
pub const defaultEnabledModules: []const []const u8 = &.{
"core",
"assets",
"platform",
"rend",
};
pub const moduleOrder: []const []const u8 = &.{
"core",
"sys",
"assets",
"platform",
"net",
"physics",
"audio",
"rend",
"ui",
"imgui",
"papyrus",
};
pub const GameModule = struct {
name: []const u8,
enabled: bool = true,
};
pub const Program = struct {
name: []const u8,
opts: bh.Options,
// root_path: not used yet, we will create a second module that dynamically loads in to that root_path
gameModules: std.ArrayListUnmanaged(GameModule) = .{},
gameModulesMap: std.StringHashMapUnmanaged(bool) = .{},
buildSystem: *backlog.BuildSystem,
iconPath: ?std.Build.LazyPath = null,
allocator: std.mem.Allocator, // just set this to the build allocator
pub fn setModuleEnabled(self: *@This(), name: []const u8, enabled: bool) void {
const r = self.gameModulesMap.getOrPut(self.allocator, name) catch unreachable;
r.value_ptr.* = enabled;
}
fn finalizeModules(self: *@This()) void {
self.gameModules.clearRetainingCapacity();
for (moduleOrder) |mod| {
var enabled: bool = false;
for (defaultEnabledModules) |default| {
if (std.mem.eql(u8, default, mod)) {
enabled = true;
}
}
if (self.gameModulesMap.get(mod)) |value| {
enabled = value;
}
if (enabled) {
self.gameModules.append(self.allocator, .{ .name = mod, .enabled = enabled }) catch unreachable;
}
}
}
pub fn setIconPath(self: *@This(), path: []const u8) void {
if (self.iconPath != null) {
return;
}
self.iconPath = self.buildSystem.generateRc(self.name, path) catch return;
}
pub fn makeSpec(self: *@This()) *std.Build.Module {
const b = self.buildSystem.b;
const run = b.addRunArtifact(self.buildSystem.specGen);
const pfile = run.addOutputFileArg(b.fmt("{s}-spec.zig", .{self.name}));
run.addArg(self.name);
for (self.gameModules.items) |mod| {
if (mod.enabled)
run.addArg(mod.name);
}
const mod = b.createModule(.{
.target = self.opts.target,
.optimize = self.opts.optimize,
.root_source_file = pfile,
});
const core = self.buildSystem.nwdep.module("core");
mod.addImport("core", core);
return mod;
}
pub fn makeGameApi(self: *@This()) *std.Build.Module {
const b = self.buildSystem.b;
const apigen = self.buildSystem.nwdep.artifact("backlog-generate-modapi");
const run = b.addRunArtifact(apigen);
const pfile = run.addOutputFileArg(b.fmt("{s}-api.zig", .{self.name}));
run.addArg(self.name);
for (self.gameModules.items) |mod| {
if (mod.enabled) {
run.addArg(mod.name);
}
}
const api = b.createModule(.{
.target = self.opts.target,
.optimize = self.opts.optimize,
.root_source_file = pfile,
});
for (self.gameModules.items) |mod| {
if (mod.enabled) {
api.addImport(mod.name, self.buildSystem.nwdep.module(mod.name));
}
}
return api;
}
pub fn makeGameModule(self: *@This()) *std.Build.Step.Compile {
const b = self.buildSystem.b;
const bEngine = self.buildSystem.bEngine;
const opts = self.opts;
// 1. generate the main launch codeset.
const lib = b.addLibrary(.{
.name = b.fmt("{s}-mod", .{self.name}),
.linkage = if (opts.static_build) .static else .dynamic,
.root_module = b.createModule(.{
.target = opts.target,
.optimize = opts.optimize,
.root_source_file = bEngine.path("engine/modulelaunch.zig"),
}),
});
if (opts.static_build) {
// b.installArtifact(lib);
} else {
const installExtern = b.addInstallArtifact(lib, .{
.dest_dir = .{ .override = .{ .custom = "modules" } },
});
b.getInstallStep().dependOn(&installExtern.step);
}
return lib;
}
pub fn generateStaticShaders(self: *@This()) *std.Build.Module {
const loadStatics = self.buildSystem.generateInstallStaticResources(self.name, "content/_shaders") catch unreachable;
return loadStatics;
}
// spec is linked in both the base trampoline
pub fn compileInstall(self: *@This()) *std.Build.Step.Compile {
const bEngine = self.buildSystem.bEngine;
const b = self.buildSystem.b;
const core = self.buildSystem.nwdep.module("core");
self.finalizeModules();
// 1. create the loader
const exe = bEngine.addExecutable(.{
.name = self.name,
.root_module = bEngine.createModule(.{
.target = self.opts.target,
.optimize = self.opts.optimize,
.root_source_file = bEngine.path("engine/main2.zig"),
}),
});
// link core
const spec = self.makeSpec();
exe.root_module.addImport("gamespec", spec);
exe.root_module.addImport("core", core);
exe.root_module.addOptions("BacklogOptions", self.buildSystem.options);
if (!self.opts.static_build) {
const platform = self.buildSystem.nwdep.module("platform");
exe.root_module.addImport("platform", platform);
}
if (self.opts.static_build) {
exe.root_module.addImport("staticShaderLoader", self.generateStaticShaders());
}
const gameModule = self.makeGameModule();
const gameApi = self.makeGameApi();
gameModule.root_module.addImport("backlog", gameApi);
gameModule.root_module.addImport("gamespec", spec);
if (self.opts.static_build) {
exe.root_module.addImport("backlog", gameApi);
}
// self.buildSystem.b.installArtifact(exe);
const installed = self.buildSystem.b.addInstallArtifact(exe, .{});
{
const run = b.addSystemCommand(&.{b.fmt("zig-out/bin/{s}", .{self.name})});
run.step.dependOn(&installed.step);
if (b.args) |args| {
run.addArgs(args);
}
const run_exe = b.step(b.fmt("run-{s}", .{self.name}), "runs the program");
run_exe.dependOn(&run.step);
}
return exe;
}
};
const std = @import("std");
const bh = @import("bh");
const backlog = @import("../build.zig");

View File

@ -1,3 +0,0 @@
const std = @import("std");
const Build = std.Build;
const LazyPath = Build.LazyPath;

View File

@ -1,114 +1,19 @@
pub fn buildGenerators(b: *std.Build, opts: bh.Options) void {
const target = opts.target;
const optimize = opts.optimize;
const static_build = opts.static_build;
const fwdGeneratorExe = b.addExecutable(.{
.name = "backlog-generate-fwd",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateFwd.zig"),
}),
});
const generateExe = b.addExecutable(.{
.name = "backlog-apigen",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateApi.zig"),
}),
});
const generateShaders = b.addExecutable(.{
.name = "backlog-shaderEmbedGen",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateEmbeddedShaders.zig"),
}),
});
const generateRcExe = b.addExecutable(.{
.name = "backlog-generate-rc",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateRc.zig"),
}),
});
const generateLoadDynamicsExe = b.addExecutable(.{
.name = "backlog-generate-loadDynamics",
.root_module = b.createModule(.{
.target = b.graph.host,
.optimize = .Debug,
.root_source_file = b.path("buildgen/generateLoadDynamics.zig"),
}),
});
b.installArtifact(fwdGeneratorExe);
b.installArtifact(generateLoadDynamicsExe);
b.installArtifact(generateExe);
b.installArtifact(generateShaders);
b.installArtifact(generateRcExe);
{
const loadDynamicsStub = b.addModule("loadDynamicsStub", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/loadDynamicsStub.zig"),
});
_ = loadDynamicsStub;
const mod = b.addModule("Backlog", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("engine/backlog.zig"),
});
for (depList()) |depName| {
const dep = b.dependency(
depName,
.{
.target = target,
.optimize = optimize,
.static_build = static_build,
},
);
const run = b.addRunArtifact(fwdGeneratorExe);
const output = run.addOutputFileArg(b.fmt("{s}_fwd.zig", .{depName}));
const modFwd = b.addModule(depName, .{
.target = target,
.optimize = optimize,
.root_source_file = output,
});
mod.addImport(b.fmt("{s}", .{depName}), modFwd);
modFwd.addImport("module", dep.module(depName));
// mod.addImport(depName, dep.module(depName));
}
// link in large platform support functions... special case.
{
const dep = b.dependency("sdl3", .{
.target = target,
.optimize = optimize,
.static_build = static_build,
});
const lib = dep.module("SDL3");
mod.addImport("sdl3_fwd", lib);
}
}
pub fn loadFileAlloc(filename: []const u8, comptime alignment: usize, allocator: std.mem.Allocator) ![]u8 {
var file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const filesize = (try file.stat()).size + 1; // add null byte
const buffer: []align(alignment) u8 = try allocator.alignedAlloc(u8, alignment, filesize);
errdefer allocator.free(buffer);
try file.reader().readNoEof(buffer[0 .. buffer.len - 1]);
buffer[buffer.len - 1] = 0;
return buffer;
}
pub fn checkAndFormatAst(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
var ast = try std.zig.Ast.parse(allocator, @as([:0]const u8, @ptrCast(input)), .zig);
const out = try ast.render(allocator);
return out;
}
const std = @import("std");
const bh = @import("bh");

View File

@ -1,49 +0,0 @@
const std = @import("std");
pub fn usage() void {
std.debug.print("generates a loader module which calls the start_modules for all the modules that the game module depends on.\ngenerate-mod-api <output file name> <module_name> <module list>\n", .{});
}
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
// Get output filename from build system
const args = try std.process.argsAlloc(allocator);
if (args.len < 3) {
usage();
@panic("Missing output filename\n");
}
const out_path = args[1];
const module_name = args[2];
const module_list = args[3..];
var writer = std.ArrayList(u8){};
try writer.print(allocator, "pub const moduleName:[]const u8 = \"{s}\";\n", .{module_name});
try writer.print(allocator, "pub const moduleList:[]const []const u8 = &.{{\n", .{});
for (module_list) |mod| {
try writer.print(allocator, "\"{s}\",\n", .{mod});
}
try writer.print(allocator, "}};\n", .{});
for (module_list) |mod| {
try writer.print(allocator, "pub const {s} = @import(\"{s}\").module;\n", .{ mod, mod });
}
try writer.append(allocator, 0);
const file = try std.fs.cwd().createFile(out_path, .{});
defer file.close();
var ast = try std.zig.Ast.parse(allocator, @as([:0]const u8, @ptrCast(writer.items[0 .. writer.items.len - 1])), .zig);
// std.debug.print("output=\n{s}", .{writer.items[0 .. writer.items.len - 1]});
defer ast.deinit(allocator);
const out = try ast.renderAlloc(allocator);
// Write the content to the file
try file.writeAll(out);
}

View File

@ -1,67 +0,0 @@
const std = @import("std");
pub fn usage() void {
std.debug.print("generate-program-spec <output file name> <spec_name> <module list>\n", .{});
}
pub fn main() !void {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena.deinit();
const allocator = arena.allocator();
// Get output filename from build system
const args = try std.process.argsAlloc(allocator);
if (args.len < 3) {
usage();
@panic("Missing output filename\n");
}
const out_path = args[1];
const programName = args[2];
const modules = args[3..];
// Generate Zig code content
var writer = std.ArrayList(u8){};
_ = try writer.appendSlice(allocator,
\\const core = @import("core").module;
\\
\\ const std = @import("std");
\\ pub fn getSpec() !*core.SpecVariantMap {
\\ const spec = try std.heap.smp_allocator.create(core.SpecVariantMap);
\\ spec.* = try core.createSpecVariant(.{
\\ .useGPA = true,
);
try writer.print(allocator, ".name = \"{s}\",\n", .{programName});
try writer.print(allocator, ".moduleName = \"{s}-mod\",\n", .{programName});
for (modules) |mod| {
try writer.print(allocator, ".{s} = true,\n", .{mod});
}
_ = try writer.appendSlice(allocator,
\\ }, std.heap.smp_allocator);
\\
\\ return spec;
\\ }
\\
);
_ = try writer.print(allocator, "pub const programName = \"{s}\";", .{programName});
try writer.append(allocator, 0);
// Write to specified output file
// Open or create the file for writing (overwrites if it exists)
const file = try std.fs.cwd().createFile(out_path, .{});
defer file.close();
var ast = try std.zig.Ast.parse(allocator, @as([:0]const u8, @ptrCast(writer.items[0 .. writer.items.len - 1])), .zig);
// std.debug.print("output=\n{s}", .{content.items[0 .. content.items.len - 1]});
defer ast.deinit(allocator);
const out = try ast.renderAlloc(allocator);
// Write the content to the file
try file.writeAll(out);
}

View File

@ -1,11 +0,0 @@
// templated file
//
// should still be a formattable zig file
// even if it is not compile-able
//
// definitions marked with // TEMPLATE_DEFINITION
// are removed during the templating step
const programName = "__program_name"; // TEMPLATE_DEFINITION
export fn loadModule() void {}

View File

@ -1,26 +0,0 @@
// templated file
//
// should still be a formattable zig file
// even if it is not compile-able
//
// definitions marked with // TEMPLATE_DEFINITION
// are removed during the templating step
const core = @import("core");
const programName = "__program_name"; // TEMPLATE_DEFINITION
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();
// platform.setupFromModule();
// sys.setupFromModule();
// rend.setupFromModule();
// backlog.physics.setupFromModule();
core.engine_logs("creating externgame");
// start_module(core.startup_getArgs(p_a.?)) catch return false;
return true;
}

View File

@ -1,14 +0,0 @@
pub export fn startup_module(p_allocator: *anyopaque, p_a: ?*anyopaque) bool {
const allocator = core.modulePreamble(p_allocator, p_a) catch return false;
_ = allocator;
core.engine_logs("module started");
return true;
}
pub export fn shutdown_module() void {}
// const backlog = @import("backlog");
// const core = backlog.core;
const core = @import("core").module;

View File

@ -1,19 +0,0 @@
pub fn loadFileAlloc(filename: []const u8, comptime alignment: usize, allocator: std.mem.Allocator) ![]u8 {
var file = try std.fs.cwd().openFile(filename, .{});
defer file.close();
const filesize = (try file.stat()).size + 1; // add null byte
const buffer: []align(alignment) u8 = try allocator.alignedAlloc(u8, alignment, filesize);
errdefer allocator.free(buffer);
try file.reader().readNoEof(buffer[0 .. buffer.len - 1]);
buffer[buffer.len - 1] = 0;
return buffer;
}
pub fn checkAndFormatAst(allocator: std.mem.Allocator, input: []const u8) ![]u8 {
var ast = try std.zig.Ast.parse(allocator, @as([:0]const u8, @ptrCast(input)), .zig);
const out = try ast.render(allocator);
return out;
}
const std = @import("std");

2
depot/.gitignore vendored
View File

@ -1,2 +0,0 @@
.fontcache/
Saved/

View File

@ -1,35 +0,0 @@
const std = @import("std");
const Backlog = @import("Backlog");
pub fn build(b: *std.Build) void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
var blbuild = Backlog.init(b, .{
.target = target,
.optimize = optimize,
.backlogRoot = "../",
});
// blbuild.addDependencyInstalls(b, .ReleaseFast); //.ReleaseFast);
const zuck = blbuild.program(.{
.name = "zuck",
.desc = "A tiny shooter made with this engine.",
.root_source_file = b.path("zuck/main.zig"),
});
zuck.setModuleEnabled("imgui", true);
zuck.setModuleEnabled("audio", true);
zuck.setModuleEnabled("physics", true);
zuck.setModuleEnabled("ui", true);
zuck.setModuleEnabled("sys", true);
zuck.setModuleEnabled("net", true);
zuck.addExtraModule("gameExtras");
{
const zuckGame = zuck.addDynamicModule("zuckGame", b.path("zuck/game.zig"));
_ = zuckGame.compileInstall();
}
_ = zuck.compileInstall();
}

View File

@ -1,35 +0,0 @@
.{
.name = .MyProjects,
.version = "0.0.0",
.dependencies = .{
// Neonwood's /lib folder contains
// third party libraries.
//
// They can be directly accessed
//
// as well as any of the engine/ libraries
//
// in the general case though,
// engine libraries
.Backlog = .{ .path = "../" },
.SpirvReflect = .{ .path = "../lib/spirv-reflect-zig" },
.gameExtras = .{.path = "../extras/gameExtras"},
.zphysics = .{.path = "../lib/zphysics"},
.spng = .{ .path = "../lib/spng" },
.ozz = .{ .path = "../lib/ozz" },
.sdl3 = .{ .path = "../lib/sdl3" },
.miniaudio = .{ .path = "../lib/miniaudio" },
.lua = .{ .path = "../lib/lua" },
.enet = .{ .path = "../lib/enet" },
},
.paths = .{
"",
},
.fingerprint = 0x14099f73bcfb78d8,
}

View File

@ -1,198 +0,0 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct type_Uniforms
{
float4 viewPos;
float4 lightPosition;
float4 directionalLight;
float4 directionalLightColor;
float2 screenSize;
float lightPower;
float time;
};
struct Scene
{
float4x4 Model;
uint textureMode;
int animation;
uint flags;
float float0;
};
struct type_StructuredBuffer_Scene
{
Scene _m0[1];
};
constant float4 _92 = {};
constant float3 _94 = {};
struct main0_out
{
float4 out_var_SV_Target0 [[color(0)]];
float4 out_var_SV_Target1 [[color(1)]];
float4 out_var_SV_Target2 [[color(2)]];
float4 out_var_SV_Target3 [[color(3)]];
};
struct main0_in
{
float2 in_var_TEXCOORD0 [[user(locn0)]];
float3 in_var_TEXCOORD1 [[user(locn1)]];
float3 in_var_TEXCOORD2 [[user(locn2)]];
float4 in_var_TEXCOORD3 [[user(locn3)]];
uint in_var_TEXCOORD4 [[user(locn4)]];
float3 in_var_TEXCOORD5 [[user(locn5)]];
};
fragment main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniforms [[buffer(0)]], const device type_StructuredBuffer_Scene& scene [[buffer(1)]], texture2d<float> Texture0 [[texture(0)]], texture2d<float> Texture1 [[texture(1)]], texture2d<float> Texture2 [[texture(2)]], texture2d<float> DirectionalShadowDepthMap [[texture(3)]], sampler Sampler0 [[sampler(0)]], sampler Sampler1 [[sampler(1)]], sampler Sampler2 [[sampler(2)]], sampler DirectionalShadowSampler [[sampler(3)]])
{
main0_out out = {};
int _104 = int(scene._m0[in.in_var_TEXCOORD4].textureMode);
int _105 = _104 & 240;
float4 _139;
if (_105 == 16)
{
float3 _126 = float3(Texture0.sample(Sampler0, in.in_var_TEXCOORD0).x, Texture1.sample(Sampler1, in.in_var_TEXCOORD0).x, Texture2.sample(Sampler2, in.in_var_TEXCOORD0).x) + float3(-0.0625, -0.5, -0.5);
_139 = float4(dot(_126, float3(1.164000034332275390625, 0.0, 1.7929999828338623046875)), dot(_126, float3(1.164000034332275390625, -0.212999999523162841796875, -0.53299999237060546875)), dot(_126, float3(1.164000034332275390625, 2.111999988555908203125, 0.0)), 1.0);
}
else
{
float4 _138;
if (_105 == 0)
{
_138 = Texture0.sample(Sampler0, in.in_var_TEXCOORD0);
}
else
{
_138 = _92;
}
_139 = _138;
}
float4 _145;
if ((_104 & 1) == 1)
{
_145 = powr(_139, float4(2.2000000476837158203125));
}
else
{
_145 = _139;
}
if (_145.w < 0.00999999977648258209228515625)
{
discard_fragment();
}
float3 _155 = float3(0.800000011920928955078125, 0.800000011920928955078125, 0.5) * Uniforms.lightPower;
float3 _202;
do
{
float3 _159 = Uniforms.lightPosition.xyz - in.in_var_TEXCOORD1;
float _160 = length(_159);
float _161 = _160 * _160;
float _167;
if (_160 > 2.0)
{
_167 = 0.5 / _161;
}
else
{
_167 = 1.0 / _161;
}
float _172;
if (_160 > 4.0)
{
_172 = _167 * 0.5;
}
else
{
_172 = _167;
}
float _174 = (_160 > 15.0) ? 0.0 : _172;
float _176 = (_174 > 1.0) ? 1.0 : _174;
if (length(in.in_var_TEXCOORD2) < 0.100000001490116119384765625)
{
_202 = (in.in_var_TEXCOORD1 * _155) * _176;
break;
}
float3 _183 = fast::normalize(_159);
_202 = ((_155 * precise::max(dot(_183, in.in_var_TEXCOORD2), 0.0)) * _176) + (((_155 * powr(precise::max(dot(in.in_var_TEXCOORD2, fast::normalize(_183 + fast::normalize(in.in_var_TEXCOORD1 - Uniforms.viewPos.xyz))), 0.0), 30.0)) * 2.0) * _176);
break;
} while(false);
float3 _219 = ((in.in_var_TEXCOORD3.xyz / float3(in.in_var_TEXCOORD3.w)) * 0.5) + float3(0.5);
float3 _221;
_221.x = _219.x;
float2 _222 = _221.xy;
_222.y = -_219.y;
float _229;
int _232;
int _234;
_229 = 0.0;
_232 = 0;
_234 = -2;
float _230;
int _233;
for (; _234 <= 2; _229 = _230, _232 = _233, _234++)
{
_233 = _232;
_230 = _229;
for (int _243 = -2; _243 <= 2; )
{
_233++;
_230 += float((in.in_var_TEXCOORD3.z - 0.0030000000260770320892333984375) > DirectionalShadowDepthMap.sample(DirectionalShadowSampler, (_222 + float2(float(_243) * 0.000244140625, float(_234) * 0.000244140625))).x);
_243++;
continue;
}
}
float3 _272;
if ((_104 & 2) > 0)
{
_272 = powr(_145.xyz, float3(0.4545454680919647216796875));
}
else
{
_272 = _145.xyz * ((float3(0.100000001490116119384765625) + _202) + ((Uniforms.directionalLightColor.xyz * precise::max(dot(Uniforms.directionalLight.xyz, in.in_var_TEXCOORD2), 0.0)) * (1.0 - (_229 / float(_232)))));
}
float4 _289;
if (_272.x > 1.0)
{
float4 _288 = float4(0.0);
_288.x = _272.x;
_289 = _288;
}
else
{
_289 = float4(0.0);
}
float4 _294;
if (_272.y > 1.0)
{
float4 _293 = _289;
_293.y = _272.y;
_294 = _293;
}
else
{
_294 = _289;
}
float4 _299;
if (_272.z > 1.0)
{
float4 _298 = _294;
_298.z = _272.z;
_299 = _298;
}
else
{
_299 = _294;
}
out.out_var_SV_Target0 = float4(_272, _145.w);
out.out_var_SV_Target1 = _299;
out.out_var_SV_Target2 = float4(in.in_var_TEXCOORD1, 1.0);
out.out_var_SV_Target3 = float4(in.in_var_TEXCOORD5, 1.0);
return out;
}

View File

@ -1,119 +0,0 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct Scene
{
float4x4 Model;
uint textureMode;
int animation;
uint flags;
float float0;
};
struct type_StructuredBuffer_Scene
{
Scene _m0[1];
};
struct BoneTransform
{
float4x4 final;
};
struct type_StructuredBuffer_BoneTransform
{
BoneTransform _m0[1];
};
struct type_Uniforms
{
float4x4 ViewProjection;
float4x4 NoTranslateView;
float4x4 ViewTransform;
float4x4 ShadowMapProjection;
float4 cameraPosition;
float time;
};
struct main0_out
{
float2 out_var_TEXCOORD0 [[user(locn0)]];
float3 out_var_TEXCOORD1 [[user(locn1)]];
float3 out_var_TEXCOORD2 [[user(locn2)]];
float4 out_var_TEXCOORD3 [[user(locn3)]];
uint out_var_TEXCOORD4 [[user(locn4)]];
float3 out_var_TEXCOORD5 [[user(locn5)]];
float4 gl_Position [[position]];
};
struct main0_in
{
float3 in_var_TEXCOORD0 [[attribute(0)]];
float3 in_var_TEXCOORD1 [[attribute(1)]];
float2 in_var_TEXCOORD3 [[attribute(3)]];
uint in_var_TEXCOORD4 [[attribute(4)]];
uint in_var_TEXCOORD5 [[attribute(5)]];
};
vertex main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniforms [[buffer(0)]], const device type_StructuredBuffer_Scene& scene [[buffer(1)]], const device type_StructuredBuffer_BoneTransform& animationBuffer [[buffer(2)]], uint gl_InstanceIndex [[instance_id]])
{
main0_out out = {};
float3 _111;
if (scene._m0[gl_InstanceIndex].animation != (-1))
{
float3 _83;
_83 = float3(0.0);
for (int _86 = 0; _86 < 4; )
{
uint _92 = (8u * uint(_86)) & 31u;
_83 += ((animationBuffer._m0[uint(scene._m0[gl_InstanceIndex].animation) + ((in.in_var_TEXCOORD4 >> _92) & 255u)].final * float4(in.in_var_TEXCOORD0, 1.0)).xyz * (float((in.in_var_TEXCOORD5 >> _92) & 255u) * 0.0039215688593685626983642578125));
_86++;
continue;
}
_111 = _83;
}
else
{
_111 = in.in_var_TEXCOORD0;
}
float4 _115 = float4(_111, 1.0);
float3 _207;
float3 _208;
float3 _209;
float4 _210;
float4 _211;
if (int(scene._m0[gl_InstanceIndex].flags & 4u) > 0)
{
float3 _127 = (scene._m0[gl_InstanceIndex].Model * float4(0.0, 0.0, 0.0, 1.0)).xyz;
float3 _132 = fast::normalize(Uniforms.cameraPosition.xyz - _127);
float3 _134 = fast::normalize(cross(float3(0.0, 1.0, 0.0), _132));
float4 _152 = float4((_127 + ((_134 * (-in.in_var_TEXCOORD0.x)) * scene._m0[gl_InstanceIndex].float0)) + ((cross(_132, _134) * in.in_var_TEXCOORD0.y) * scene._m0[gl_InstanceIndex].float0), 1.0);
_207 = _152.xyz;
_208 = _132;
_209 = float3(0.0, 0.0, 1.0);
_210 = Uniforms.ViewProjection * _152;
_211 = _152;
}
else
{
float4 _159 = scene._m0[gl_InstanceIndex].Model * _115;
float4x4 _194 = float4x4(float4(fast::normalize(float3(scene._m0[gl_InstanceIndex].Model[0][0], scene._m0[gl_InstanceIndex].Model[1][0], scene._m0[gl_InstanceIndex].Model[2][0])), 0.0), float4(fast::normalize(float3(scene._m0[gl_InstanceIndex].Model[0][1], scene._m0[gl_InstanceIndex].Model[1][1], scene._m0[gl_InstanceIndex].Model[2][1])), 0.0), float4(fast::normalize(float3(scene._m0[gl_InstanceIndex].Model[0][2], scene._m0[gl_InstanceIndex].Model[1][2], scene._m0[gl_InstanceIndex].Model[2][2])), 0.0), float4(0.0, 0.0, 0.0, 1.0));
float4 _198 = float4(in.in_var_TEXCOORD1, 1.0);
_207 = _159.xyz;
_208 = fast::normalize(_198 * _194).xyz;
_209 = (_198 * (_194 * transpose(Uniforms.NoTranslateView))).xyz;
_210 = Uniforms.ViewProjection * (scene._m0[gl_InstanceIndex].Model * _115);
_211 = _159;
}
out.out_var_TEXCOORD0 = in.in_var_TEXCOORD3;
out.out_var_TEXCOORD1 = _207;
out.out_var_TEXCOORD2 = _208;
out.out_var_TEXCOORD3 = Uniforms.ShadowMapProjection * _211;
out.out_var_TEXCOORD4 = gl_InstanceIndex;
out.out_var_TEXCOORD5 = _209;
out.gl_Position = _210;
return out;
}

View File

@ -1,259 +0,0 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct Scene
{
float2 imagePosition;
float2 _imageSize;
float2 anchorPoint;
float2 scale;
float alpha;
float borderWidth;
uint flags;
float4 baseColor;
float4 rounding;
float4 edgeColor;
};
struct type_StructuredBuffer_Scene
{
Scene _m0[1];
};
struct main0_out
{
float4 out_var_SV_Target0 [[color(0)]];
};
struct main0_in
{
float4 in_var_TEXCOORD0 [[user(locn0)]];
float2 in_var_TEXCOORD1 [[user(locn1)]];
float2 in_var_TEXCOORD2 [[user(locn2)]];
uint in_var_TEXCOORD3 [[user(locn3)]];
};
fragment main0_out main0(main0_in in [[stage_in]], const device type_StructuredBuffer_Scene& scene [[buffer(0)]], texture2d<float> Texture0 [[texture(0)]], sampler Sampler0 [[sampler(0)]])
{
main0_out out = {};
bool _71 = in.in_var_TEXCOORD2.x < scene._m0[in.in_var_TEXCOORD3].rounding.x;
bool _78;
if (_71)
{
_78 = in.in_var_TEXCOORD2.y < scene._m0[in.in_var_TEXCOORD3].rounding.y;
}
else
{
_78 = false;
}
float _95;
float3 _96;
if (_78)
{
float _82 = distance(in.in_var_TEXCOORD2, float2(scene._m0[in.in_var_TEXCOORD3].rounding.x));
float _93;
float3 _94;
if (_82 > scene._m0[in.in_var_TEXCOORD3].rounding.x)
{
discard_fragment();
}
else
{
if (abs(_82 - scene._m0[in.in_var_TEXCOORD3].rounding.x) < 1.0)
{
_93 = scene._m0[in.in_var_TEXCOORD3].edgeColor.w;
_94 = scene._m0[in.in_var_TEXCOORD3].edgeColor.xyz;
}
else
{
_93 = scene._m0[in.in_var_TEXCOORD3].alpha;
_94 = in.in_var_TEXCOORD0.xyz;
}
}
_95 = _93;
_96 = _94;
}
else
{
_95 = scene._m0[in.in_var_TEXCOORD3].alpha;
_96 = in.in_var_TEXCOORD0.xyz;
}
float _99 = scene._m0[in.in_var_TEXCOORD3]._imageSize.x - scene._m0[in.in_var_TEXCOORD3].rounding.y;
bool _106;
if (in.in_var_TEXCOORD2.x > _99)
{
_106 = in.in_var_TEXCOORD2.y < scene._m0[in.in_var_TEXCOORD3].rounding.y;
}
else
{
_106 = false;
}
float _123;
float3 _124;
if (_106)
{
float _110 = distance(in.in_var_TEXCOORD2, float2(_99, scene._m0[in.in_var_TEXCOORD3].rounding.y));
float _121;
float3 _122;
if (_110 > scene._m0[in.in_var_TEXCOORD3].rounding.y)
{
discard_fragment();
}
else
{
if (abs(_110 - scene._m0[in.in_var_TEXCOORD3].rounding.y) < 1.0)
{
_121 = scene._m0[in.in_var_TEXCOORD3].edgeColor.w;
_122 = scene._m0[in.in_var_TEXCOORD3].edgeColor.xyz;
}
else
{
_121 = _95;
_122 = _96;
}
}
_123 = _121;
_124 = _122;
}
else
{
_123 = _95;
_124 = _96;
}
bool _132;
if (_71)
{
_132 = in.in_var_TEXCOORD2.y > (scene._m0[in.in_var_TEXCOORD3]._imageSize.y - scene._m0[in.in_var_TEXCOORD3].rounding.y);
}
else
{
_132 = false;
}
float _151;
float3 _152;
if (_132)
{
float _138 = distance(in.in_var_TEXCOORD2, float2(scene._m0[in.in_var_TEXCOORD3].rounding.x, scene._m0[in.in_var_TEXCOORD3]._imageSize.y - scene._m0[in.in_var_TEXCOORD3].rounding.y));
float _149;
float3 _150;
if (_138 > scene._m0[in.in_var_TEXCOORD3].rounding.y)
{
discard_fragment();
}
else
{
if (abs(_138 - scene._m0[in.in_var_TEXCOORD3].rounding.y) < 1.0)
{
_149 = scene._m0[in.in_var_TEXCOORD3].edgeColor.w;
_150 = scene._m0[in.in_var_TEXCOORD3].edgeColor.xyz;
}
else
{
_149 = _123;
_150 = _124;
}
}
_151 = _149;
_152 = _150;
}
else
{
_151 = _123;
_152 = _124;
}
bool _163;
if (in.in_var_TEXCOORD2.x > (scene._m0[in.in_var_TEXCOORD3]._imageSize.x - scene._m0[in.in_var_TEXCOORD3].rounding.w))
{
_163 = (scene._m0[in.in_var_TEXCOORD3]._imageSize.y - in.in_var_TEXCOORD2.y) < scene._m0[in.in_var_TEXCOORD3].rounding.w;
}
else
{
_163 = false;
}
float _183;
float3 _184;
if (_163)
{
float _170 = distance(in.in_var_TEXCOORD2, float2(scene._m0[in.in_var_TEXCOORD3]._imageSize.x - scene._m0[in.in_var_TEXCOORD3].rounding.x, scene._m0[in.in_var_TEXCOORD3]._imageSize.y - scene._m0[in.in_var_TEXCOORD3].rounding.y));
float _181;
float3 _182;
if (_170 > scene._m0[in.in_var_TEXCOORD3].rounding.y)
{
discard_fragment();
}
else
{
if (abs(_170 - scene._m0[in.in_var_TEXCOORD3].rounding.y) < 1.0)
{
_181 = scene._m0[in.in_var_TEXCOORD3].edgeColor.w;
_182 = scene._m0[in.in_var_TEXCOORD3].edgeColor.xyz;
}
else
{
_181 = _151;
_182 = _152;
}
}
_183 = _181;
_184 = _182;
}
else
{
_183 = _151;
_184 = _152;
}
bool _191;
if (!(in.in_var_TEXCOORD2.x < scene._m0[in.in_var_TEXCOORD3].borderWidth))
{
_191 = in.in_var_TEXCOORD2.x > (scene._m0[in.in_var_TEXCOORD3]._imageSize.x - scene._m0[in.in_var_TEXCOORD3].borderWidth);
}
else
{
_191 = true;
}
bool _198;
if (!_191)
{
_198 = in.in_var_TEXCOORD2.y < scene._m0[in.in_var_TEXCOORD3].borderWidth;
}
else
{
_198 = true;
}
bool _207;
if (!_198)
{
_207 = in.in_var_TEXCOORD2.y > (scene._m0[in.in_var_TEXCOORD3]._imageSize.y - scene._m0[in.in_var_TEXCOORD3].borderWidth);
}
else
{
_207 = true;
}
float _212;
float3 _213;
if (_207)
{
_212 = scene._m0[in.in_var_TEXCOORD3].edgeColor.w;
_213 = scene._m0[in.in_var_TEXCOORD3].edgeColor.xyz;
}
else
{
_212 = _183;
_213 = _184;
}
float4 _238;
if ((scene._m0[in.in_var_TEXCOORD3].flags & 1u) > 0u)
{
float4 _227 = Texture0.sample(Sampler0, float2(in.in_var_TEXCOORD1.x, 1.0 - in.in_var_TEXCOORD1.y));
_238 = float4(_227.xyz, _227.w * _212);
}
else
{
_238 = float4(_213, _212);
}
out.out_var_SV_Target0 = _238;
return out;
}

View File

@ -1,62 +0,0 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct Scene
{
float2 imagePosition;
float2 _imageSize;
float2 anchorPoint;
float2 scale;
float alpha;
float borderWidth;
uint flags;
float4 baseColor;
float4 rounding;
float4 edgeColor;
};
struct type_StructuredBuffer_Scene
{
Scene _m0[1];
};
struct type_Uniforms
{
float2 Extents;
};
struct main0_out
{
float4 out_var_TEXCOORD0 [[user(locn0)]];
float2 out_var_TEXCOORD1 [[user(locn1)]];
float2 out_var_TEXCOORD2 [[user(locn2)]];
uint out_var_TEXCOORD3 [[user(locn3)]];
float4 gl_Position [[position]];
};
struct main0_in
{
float3 in_var_TEXCOORD0 [[attribute(0)]];
float2 in_var_TEXCOORD3 [[attribute(3)]];
};
vertex main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniforms [[buffer(0)]], const device type_StructuredBuffer_Scene& scene [[buffer(1)]], uint gl_InstanceIndex [[instance_id]])
{
main0_out out = {};
float2 _63 = scene._m0[gl_InstanceIndex]._imageSize / Uniforms.Extents;
float2 _69 = (((scene._m0[gl_InstanceIndex].imagePosition / Uniforms.Extents) * 2.0) - float2(1.0)) - ((scene._m0[gl_InstanceIndex].anchorPoint * _63) * scene._m0[gl_InstanceIndex].scale);
float _85 = _69.y + ((in.in_var_TEXCOORD0.y * _63.y) * scene._m0[gl_InstanceIndex].scale.y);
float4 _88 = float4(_69.x + ((in.in_var_TEXCOORD0.x * _63.x) * scene._m0[gl_InstanceIndex].scale.x), _85, in.in_var_TEXCOORD0.z, 1.0);
_88.y = -_85;
float2 _100 = ((in.in_var_TEXCOORD0.xy - scene._m0[gl_InstanceIndex].anchorPoint) * float2(0.5)) * scene._m0[gl_InstanceIndex]._imageSize;
_100.y = scene._m0[gl_InstanceIndex]._imageSize.y - _100.y;
out.out_var_TEXCOORD0 = scene._m0[gl_InstanceIndex].baseColor;
out.out_var_TEXCOORD1 = float2(1.0 - in.in_var_TEXCOORD3.x, in.in_var_TEXCOORD3.y);
out.out_var_TEXCOORD2 = _100;
out.out_var_TEXCOORD3 = gl_InstanceIndex;
out.gl_Position = _88;
return out;
}

View File

@ -1,102 +0,0 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct FontInfo
{
float2 position;
float2 size;
uint isSdf;
uint pad0;
};
struct type_StructuredBuffer_FontInfo
{
FontInfo _m0[1];
};
struct main0_out
{
float4 out_var_SV_Target0 [[color(0)]];
};
struct main0_in
{
uint in_var_TEXCOORD0 [[user(locn0)]];
float2 in_var_TEXCOORD1 [[user(locn1)]];
float2 in_var_TEXCOORD2 [[user(locn2)]];
uint in_var_TEXCOORD3 [[user(locn3)]];
};
fragment main0_out main0(main0_in in [[stage_in]], const device type_StructuredBuffer_FontInfo& fontBuffer [[buffer(0)]], texture2d<float> Texture0 [[texture(0)]], sampler Sampler0 [[sampler(0)]])
{
main0_out out = {};
float4 _67 = Texture0.sample(Sampler0, in.in_var_TEXCOORD1);
bool _104;
do
{
bool _85;
if (in.in_var_TEXCOORD2.x >= fontBuffer._m0[in.in_var_TEXCOORD3].position.x)
{
_85 = in.in_var_TEXCOORD2.x <= (fontBuffer._m0[in.in_var_TEXCOORD3].position.x + fontBuffer._m0[in.in_var_TEXCOORD3].size.x);
}
else
{
_85 = false;
}
bool _92;
if (_85)
{
_92 = in.in_var_TEXCOORD2.y >= fontBuffer._m0[in.in_var_TEXCOORD3].position.y;
}
else
{
_92 = false;
}
bool _101;
if (_92)
{
_101 = in.in_var_TEXCOORD2.y <= (fontBuffer._m0[in.in_var_TEXCOORD3].position.y + fontBuffer._m0[in.in_var_TEXCOORD3].size.y);
}
else
{
_101 = false;
}
if (_101)
{
_104 = true;
break;
}
_104 = false;
break;
} while(false);
if (!_104)
{
discard_fragment();
}
float _110 = float(in.in_var_TEXCOORD0 & 255u) * 0.0039215688593685626983642578125;
float _114 = float((in.in_var_TEXCOORD0 >> 8u) & 255u) * 0.0039215688593685626983642578125;
float _118 = float((in.in_var_TEXCOORD0 >> 16u) & 255u) * 0.0039215688593685626983642578125;
float _122 = float((in.in_var_TEXCOORD0 >> 24u) & 255u) * 0.0039215688593685626983642578125;
float4 _187;
if (fontBuffer._m0[in.in_var_TEXCOORD3].isSdf == 1u)
{
float _128 = _67.x;
float _129 = fwidth(_128);
float _130 = 0.529411792755126953125 - _129;
float _131 = 0.529411792755126953125 + _129;
float2 _137 = (dfdx(in.in_var_TEXCOORD1) + dfdy(in.in_var_TEXCOORD1)) * 0.3540000021457672119140625;
float4 _144 = float4(in.in_var_TEXCOORD1 - _137, in.in_var_TEXCOORD1 + _137);
float4 _176 = float4(_110, _114, _118, ((fast::clamp(smoothstep(_130, _131, _128), 0.0, 1.0) + (0.5 * (((fast::clamp(smoothstep(_130, _131, Texture0.sample(Sampler0, _144.xy).x), 0.0, 1.0) + fast::clamp(smoothstep(_130, _131, Texture0.sample(Sampler0, _144.zw).x), 0.0, 1.0)) + fast::clamp(smoothstep(_130, _131, Texture0.sample(Sampler0, _144.xw).x), 0.0, 1.0)) + fast::clamp(smoothstep(_130, _131, Texture0.sample(Sampler0, _144.zy).x), 0.0, 1.0)))) * 0.3333333432674407958984375) * _122);
float3 _178 = powr(_176.xyz, float3(2.2000000476837158203125));
_187 = float4(_178.x, _178.y, _178.z, _176.w);
}
else
{
_187 = float4(_110, _114, _118, powr(_67.x / dot(float4(_110, _114, _118, _122).xyz, float3(0.2125999927520751953125, 0.715200006961822509765625, 0.072200000286102294921875)), 0.4545454680919647216796875) * _122);
}
out.out_var_SV_Target0 = _187;
return out;
}

View File

@ -1,54 +0,0 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct FontInfo
{
float2 position;
float2 size;
uint isSdf;
uint pad0;
};
struct type_StructuredBuffer_FontInfo
{
FontInfo _m0[1];
};
struct type_Uniforms
{
float2 Extents;
};
struct main0_out
{
uint out_var_TEXCOORD0 [[user(locn0)]];
float2 out_var_TEXCOORD1 [[user(locn1)]];
float2 out_var_TEXCOORD2 [[user(locn2)]];
uint out_var_TEXCOORD3 [[user(locn3)]];
float4 gl_Position [[position]];
};
struct main0_in
{
float2 in_var_TEXCOORD0 [[attribute(0)]];
float2 in_var_TEXCOORD1 [[attribute(1)]];
uint in_var_TEXCOORD2 [[attribute(2)]];
};
vertex main0_out main0(main0_in in [[stage_in]], constant type_Uniforms& Uniforms [[buffer(0)]], const device type_StructuredBuffer_FontInfo& fontBuffer [[buffer(1)]], uint gl_InstanceIndex [[instance_id]])
{
main0_out out = {};
float2 _46 = in.in_var_TEXCOORD0 + fontBuffer._m0[gl_InstanceIndex].position;
float2 _51 = ((_46 / Uniforms.Extents) * 2.0) + float2(-1.0);
float4 _54 = float4(_51, 0.0, 1.0);
_54.y = -_51.y;
out.out_var_TEXCOORD0 = in.in_var_TEXCOORD2;
out.out_var_TEXCOORD1 = in.in_var_TEXCOORD1;
out.out_var_TEXCOORD2 = _46;
out.out_var_TEXCOORD3 = gl_InstanceIndex;
out.gl_Position = _54;
return out;
}

View File

@ -1,3 +0,0 @@
# studio games depot
!! not for open source.

View File

@ -1,53 +0,0 @@
pub const BoxObject = struct {
playing: bool = false,
pub fn create(allocator: std.mem.Allocator, entity: core.Entity, params: core.SpawnParameters) !*@This() {
const position = params.posRot.position;
const box2 = entity;
const scene = box2.addComponent(core.Scene).?;
const mesh = box2.addComponent(rend.MeshComponent).?;
mesh.setMesh("m_crate");
mesh.setTexture("t_crate");
scene.setPosition(position);
scene.setScaleV(params.posRot.scale);
scene.setMobility(.moveable);
const collider = box2.addComponent(physics.PhysicsCollider).?;
try collider.setupByShapeName(core.MakeName("ph_small_box"), .{
.motion_type = .dynamic,
.object_layer = physics.ObjectLayers.moving,
.friction = 0.8,
.mass_properties_override = .{ .mass = 12 },
.override_mass_properties = .calc_inertia,
});
return try allocator.create(@This());
}
pub fn createSmoky(allocator: std.mem.Allocator, entity: core.Entity, params: core.SpawnParameters) !*@This() {
const rv = try create(allocator, entity, params);
const particle = entity.addComponent(rend.ParticleEmitter).?;
particle.start();
particle.gravity = .{ .y = 1.0 };
particle.particleVelocity = .{ .min = 0.6, .max = 1.2 };
particle.particleLife = .{ .min = 5, .max = 6 };
var name = core.MakeName("t_pixelSmoke");
particle.texture = rend.getTexture(&name).?;
return rv;
}
pub fn destroy(self: *@This(), alloc: std.mem.Allocator) void {
alloc.destroy(self);
}
};
const std = @import("std");
const Backlog = @import("Backlog");
const core = Backlog.core;
const rend = Backlog.rend;
const physics = Backlog.physics;

View File

@ -1,37 +0,0 @@
const backlog = @import("backlog");
const core = backlog.core;
const imgui = backlog.imgui;
const physics = backlog.physics;
const rend = backlog.rend;
const ig = imgui.api;
const sys = backlog.sys;
const ui = backlog.ui;
const net = backlog.net;
const audio = backlog.audio;
const platform = backlog.platform;
pub export fn shutdown_module() void {}
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();
platform.setupFromModule();
sys.setupFromModule();
rend.setupFromModule();
backlog.physics.setupFromModule();
start_module(core.startup_getArgs(p_a.?)) catch return false;
return true;
}
pub fn start_module(args: core.ModuleLoaderArgs) !void {
_ = args;
core.PatchOrCreateObject(ZuckGame, .{});
// core.PatchOrCreateObject(ZuckEditor, .{});
}
const ZuckGame = @import("games/ZuckGame.zig");
const ZuckEditor = @import("games/ZuckEditor.zig");
const ZuckPlayer = @import("actors/ZuckPlayer.zig");

View File

@ -1,29 +0,0 @@
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "zuck.game.Editor");
allocator: std.mem.Allocator = undefined,
pub const Slack = core.SlackStruct(@This(), 8192);
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try Slack.create(allocator);
self.* = .{
.allocator = allocator,
};
return self;
}
pub fn tick(self: *@This(), dt: f64) void {
_ = self;
_ = dt;
}
pub fn destroy(self: *@This()) void {
self.allocator.destroy(Slack.fromPtr(self));
}
const backlog = @import("backlog");
const std = @import("std");
const extras = @import("gameExtras");
const core = backlog.core;

View File

@ -1,29 +0,0 @@
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "zuck.game.Root");
allocator: std.mem.Allocator = undefined,
pub const Slack = core.SlackStruct(@This(), 1024);
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try Slack.create(allocator);
self.* = .{
.allocator = allocator,
};
return self;
}
pub fn tick(self: *@This(), dt: f64) void {
_ = self;
_ = dt;
}
pub fn destroy(self: *@This()) void {
self.allocator.destroy(Slack.fromPtr(self));
}
const backlog = @import("backlog");
const std = @import("std");
const extras = @import("gameExtras");
const core = backlog.core;

View File

@ -1,35 +0,0 @@
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "main");
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
};
return self;
}
pub fn prepare(self: *@This()) !void {
_ = self;
try api.loadDynamicModules();
}
pub fn tick(self: *@This(), dt: f64) void {
_ = self;
_ = dt;
}
pub fn deinit(self: *@This()) void {
// Engine handles memory deallocation
_ = self;
}
pub fn main() anyerror!void {
var spec = try api.getSpec("minimal");
_ = api.startEngine(&NeonObjectTable, &spec);
}
const std = @import("std");
const api = @import("backlog");
const core = api.core;

View File

@ -1,199 +0,0 @@
# Backlog Engine Build Options
This document provides comprehensive documentation for all `core.BuildOption` flags available in the Backlog game engine.
## Overview
Build options in Backlog are boolean flags that control various compilation behaviors and engine features. They are accessed at compile-time using `core.BuildOption("option_name")` and can be set via Zig's build system command line arguments.
## Usage
Build options are set when building the project:
```bash
zig build -Doption_name
# or
zig build -Doption_name=true
```
To disable an option explicitly:
```bash
zig build -Doption_name=false
```
## Core Engine Build Options
### Performance & Development Options
#### `mutex_job_queue`
- **Type**: `bool`
- **Default**: `false`
- **Description**: Temporary test option that reverts to old mutex-based queue behavior in `jobs.zig:JobManager`
- **Usage**: `zig build -Dmutex_job_queue`
- **Impact**: Changes job system threading behavior for debugging/testing purposes
- **Files**: `engine/core/src/jobs.zig:8`
#### `force_mailbox`
- **Type**: `bool`
- **Default**: `false`
- **Description**: Forces mailbox mode for present mode, unlocking framerate to very high levels
- **Usage**: `zig build -Dforce_mailbox`
- **Impact**: Disables VSync and allows unlimited framerate rendering
- **Warning**: Can cause excessive GPU usage and heat generation
### Logging Options
#### `zero_logging`
- **Type**: `bool`
- **Default**: `false`
- **Description**: Disables all logging completely, intended primarily for job dispatch performance testing
- **Usage**: `zig build -Dzero_logging`
- **Impact**: Removes all log output, improving performance but eliminating debug information
- **Files**: `engine/core/src/logging.zig:7`
#### `slow_logging`
- **Type**: `bool`
- **Default**: `false`
- **Description**: Disables buffered logging, taking a performance hit but providing accurate timing information
- **Usage**: `zig build -Dslow_logging`
- **Impact**: Each log call is immediately flushed, providing precise timing but reducing performance
- **Files**: `engine/core/src/logging.zig:6`
### Build & Deployment Options
#### `static_build`
- **Type**: `bool`
- **Default**: `false`
- **Description**: Builds the entire game as a single executable with static linking
- **Usage**: `zig build -Dstatic_build`
- **Impact**:
- Required for Linux builds
- Disables dynamic module loading
- Creates self-contained executable
- **Files**:
- `build.zig:190`
- `engine/core/src/extern/externModule.zig:139`
- `build/generateApi.zig:118`
- `engine/rend/src/sgpu/renderer.zig:1126`
#### `RootDeploymentOnly`
- **Type**: `bool`
- **Default**: Not explicitly set (appears to default to `false`)
- **Description**: Controls whether only root deployment operations are performed
- **Usage**: `zig build -DRootDeploymentOnly`
- **Impact**: Appears to limit engine initialization to root-level deployment only
- **Files**: `engine/main.zig:28`
### Development & Shader Options
#### `cookShaders`
- **Type**: `bool`
- **Default**: `false`
- **Description**: Generates shaders and updates `.json` files before running the build
- **Usage**: `zig build -DcookShaders`
- **Impact**:
- Automatically runs `tools/scripts/cookShaders.py`
- Compiles HLSL shaders to SPIR-V, MSL, and DXIL formats
- Must be run whenever shader files are modified
- **Files**: `build.zig:80`
### Third-Party Integration Options
#### `tracy` (Tracy Profiler)
- **Type**: `bool`
- **Default**: `false`
- **Description**: Enables Tracy profiler integration for performance analysis
- **Usage**: `zig build -Dtracy`
- **Impact**:
- Adds Tracy profiling instrumentation
- Enables real-time performance monitoring
- Increases binary size and may affect performance
- **Files**: `lib/tracy/build_tracy.zig:37`
## Build Option Categories
### By Purpose
**Performance Testing**:
- `mutex_job_queue` - Job system behavior testing
- `zero_logging` - Maximum performance logging
- `force_mailbox` - Unlimited framerate testing
**Development & Debugging**:
- `slow_logging` - Precise timing information
- `tracy` - Performance profiling
- `cookShaders` - Shader development workflow
**Deployment & Distribution**:
- `static_build` - Self-contained executable creation
- `RootDeploymentOnly` - Deployment scope control
### By Impact Level
**High Impact** (affects core engine behavior):
- `static_build`
- `zero_logging`
- `mutex_job_queue`
**Medium Impact** (affects specific subsystems):
- `slow_logging`
- `force_mailbox`
- `cookShaders`
**Low Impact** (development/profiling tools):
- `tracy`
- `RootDeploymentOnly`
## Common Build Configurations
### Development Build
```bash
zig build -Dslow_logging -Dtracy -DcookShaders
```
### Performance Testing Build
```bash
zig build -Dzero_logging -Dforce_mailbox -Dmutex_job_queue
```
### Production/Release Build
```bash
zig build -Dstatic_build
```
### Linux Build (Required)
```bash
zig build -Dstatic_build
```
## Implementation Details
Build options are implemented through Zig's compile-time options system:
1. **Definition**: Options are defined in `build.zig` in the `BuildOptions` struct
2. **Access**: Runtime code accesses options via `core.BuildOption("option_name")`
3. **Compilation**: The `core.BuildOption()` function checks for the option at compile-time
4. **Fallback**: If an option is not defined, it defaults to `false`
The `core.BuildOption()` function implementation:
```zig
pub fn BuildOption(comptime option: []const u8) bool {
if (@hasDecl(@import("root"), "options")) {
const r = @import("root").options;
if (@hasDecl(r, option)) {
return @field(r, option);
} else {
return false;
}
}
return false;
}
```
## Notes
- All build options are boolean flags
- Options not explicitly set default to `false`
- Some combinations may be incompatible (e.g., `zero_logging` + `slow_logging`)
- Linux builds require `static_build=true`
- Shader compilation via `cookShaders` should be run after modifying any `.hlsl` files

View File

@ -1,152 +0,0 @@
# Backlog Game Engine Overview
The Backlog Game Engine is a modern, modular game engine built with Zig, designed for cross-platform game development. It provides a comprehensive set of tools and systems for creating interactive 3D applications and games.
### Prerequisites
- **Zig 0.14** or later
- **Python 3** for build scripts
## Running Examples
The engine comes with several examples to demonstrate its capabilities:
### Sample Game
The main example showcasing most engine features including:
- 3D rendering with meshes and textures
- Physics simulation
- ImGui debug interface
- Input handling
- Dynamic module loading
**To run the sample game:**
```bash
cd projects
zig build run-sampleGame
```
To compile and target linux with cross compiling,
```
zig build -Dtarget=x86_64-linux-gnu
```
The linux build is always compiled as a static executable.
**Sample Game Controls:**
- `WASD` - Move camera
- `Mouse` - Look around (toggle menu with `T`)
- `E/Q` - Move up/down
- `R` - Reload shaders
- `Escape` - Exit
### Minimal Example
A basic template demonstrating the minimal setup required for a Backlog application:
```bash
cd projects/minimal
zig build
zig build run-minimal
```
This example shows:
- Basic engine initialization
- ImGui integration
- Simple game loop structure
## Creating New Projects
The engine includes a project creation tool to help you get started with new projects quickly.
### Using the New Project Tool
1. Navigate to the projects directory:
```bash
cd projects
```
2. Run the project creator:
```bash
zig build run-newProject
```
3. Click "Create Project" to generate your new project structure
### Manual Project Creation
Alternatively, you can create projects manually by copying the minimal template:
1. Copy the `projects/minimal` directory:
```bash
cp -r projects/minimal projects/my-new-project
```
2. Edit `projects/my-new-project/build.zig` and update:
- Project name
- Root source file path
- Enabled modules as needed
3. Modify `src/main.zig` to implement your game logic
### Project Structure
A typical Backlog project includes:
```
my-project/
├── build.zig # Build configuration
├── build.zig.zon # Dependencies
├── src/
│ └── main.zig # Main game code
├── content/ # Game assets (optional)
└── Saved/ # Runtime generated files
```
## Engine Modules
When creating projects, you can enable these modules:
- **core** - Essential engine systems (always enabled)
- **platform** - Windowing and input (always enabled)
- **assets** - Asset loading and management (always enabled)
- **rend** - 3D rendering system (always enabled)
- **imgui** - Debug UI and developer tools
- **audio** - Sound and music playback
- **physics** - Physics simulation (Jolt Physics)
- **ui** - User interface rendering
- **papyrus** - Text rendering and UI primitives
- **sys** - System utilities
## Asset Pipeline
Assets are placed in the `content/` directory within your project:
all shaders are cooked to the target system's native format. DXIL for dx12, msl for metal and spv for vulkan.
These cooked format shaders are placed under content/_shaders
## Building and Development
### Building Projects
From your project directory:
```bash
zig build # Build project
zig build run-[projectname] # Build and run
```
### Testing
Run tests for the entire engine:
```bash
zig build test
```
Or test individual modules:
```bash
cd engine/core
zig build test
```

View File

@ -120,6 +120,11 @@ pub const AssetLoaderInterface = struct {
unreachable;
}
if (!@hasDecl(TargetType, "destroy")) {
@compileLog("Tried to generate AssetLoaderInterface for type ", TargetType, "but it's missing func destroy.");
unreachable;
}
const self = @This(){
.typeName = @typeName(TargetType),
.typeSize = @sizeOf(TargetType),
@ -154,15 +159,15 @@ pub const AssetReferenceSys = struct {
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "AssetReference");
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first)
return;
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = @This(){
.loaders = .{},
.allocator = allocator,
.outstandingAssetJobs = std.atomic.Value(i32).init(0),
};
return self;
}
pub fn registerLoader(self: *@This(), loader: anytype) !void {
@ -205,5 +210,6 @@ pub const AssetReferenceSys = struct {
// i.destroy(self.allocator);
// }
self.loaders.deinit(self.allocator);
self.allocator.destroy(self);
}
};

View File

@ -73,10 +73,8 @@ pub const SoundEngine = struct {
allocator: std.mem.Allocator,
volume: f32 = 1.0,
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first)
return;
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = @This(){
.engine = allocator.create(ma.ma_engine) catch unreachable,
.sounds = .{},
@ -85,6 +83,8 @@ pub const SoundEngine = struct {
};
_ = ma.ma_engine_init(null, self.engine);
return self;
}
pub fn shutdown(self: *@This()) void {

View File

@ -156,7 +156,7 @@ pub const createSpecVariant = core.createSpecVariant;
pub export fn initAndRun(vtable: *core.EngineObjectVTable, spec: *core.SpecVariantMap) bool {
const args = getArgs() catch return false;
var backingAllocator: std.mem.Allocator = std.heap.smp_allocator;
var backingAllocator: std.mem.Allocator = std.heap.c_allocator;
var gpa: std.heap.GeneralPurposeAllocator(.{
.stack_trace_frames = 20,
}) = .{};

View File

@ -1,7 +0,0 @@
pub const std = @import("std");
const core = @import("core");
const impl = core;
pub const inputs = struct {
pub const getInputStack = inputs.getInputStack;
};

View File

@ -2,9 +2,14 @@ const std = @import("std");
const dependencyList = [_][]const u8{
"p2",
"tracy", // usually doesnt have C deps... unless we're compiled with it on, in which case we only support static builds
"tracy",
"zmath",
"packer", // packer no longer has C deps.
"packer", // packer might need to be split into another setup that doesn't have C deps.
// these ones use c deps, but if i move them out to platform... then core will have zero c dependencies.
"lua",
"spng",
// "nfd",
};
pub fn build(b: *std.Build) void {
@ -28,10 +33,8 @@ pub fn build(b: *std.Build) void {
.root_module = b.createModule(.{
.target = target,
.optimize = optimize,
.link_libc = true,
.root_source_file = b.path("tests/tests.zig"),
}),
.use_llvm = true,
});
const sampleGameExtern = b.addLibrary(.{
@ -42,10 +45,8 @@ pub fn build(b: *std.Build) void {
.target = target,
}),
.linkage = .dynamic,
.use_llvm = true,
.name = "external",
});
sampleGameExtern.root_module.addImport("core", mod);
const installExtern = b.addInstallArtifact(sampleGameExtern, .{
.dest_dir = .{ .override = .{ .custom = "modules" } },

View File

@ -2,7 +2,7 @@
.name = .core,
.version = "0.0.0",
.dependencies = .{
// .spng = .{ .path = "../../lib/spng" },
.spng = .{ .path = "../../lib/spng" },
// .nfd = .{ .path = "../../lib/nfd" },
// with -Dtracy = false, this one pulls in no C dependencies
@ -12,7 +12,7 @@
.zmath = .{ .path = "../../lib/zmath" },
.packer = .{ .path = "../../lib/packer" },
.p2 = .{ .path = "../../lib/p2" },
// .lua = .{ .path = "../../lib/lua" },
.lua = .{ .path = "../../lib/lua" },
// .zgltf = .{ .path = "../../lib/zgltf" },
},
.paths = .{

View File

@ -169,12 +169,12 @@ pub fn init(backingAllocator: std.mem.Allocator, settings: SetupSettings) @This(
core.engine_logs("[dmt] Enabling Detailed Memory Tracking");
}
const stackCompactor = if (EnableMemoryTimeline) core.StackCompactor.create(std.heap.smp_allocator) catch null else null;
const stackCompactor = if (EnableMemoryTimeline) core.StackCompactor.create(std.heap.c_allocator) catch null else null;
return .{
.backingAllocator = backingAllocator,
.stackCompactor = stackCompactor,
.timeline = if (EnableMemoryTimeline) EventTimeline.init(std.heap.smp_allocator) catch null else null,
.timeline = if (EnableMemoryTimeline) EventTimeline.init(std.heap.c_allocator) catch null else null,
};
}

View File

@ -4,7 +4,7 @@ const std = @import("std");
pub fn ParseArgs(comptime T: type) !T {
// I think i am actually totally cool with leaking this one and leaving it around... just dont spam call this function
var iter = try std.process.argsWithAllocator(std.heap.smp_allocator);
var iter = try std.process.argsWithAllocator(std.heap.c_allocator);
var args: T = .{};
const shortBuf: [32]u8 = undefined;

View File

@ -1,16 +1,17 @@
pub const ConfigRegistry = struct {
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.ConfigRegistry");
allocator: std.mem.Allocator = undefined,
allocator: std.mem.Allocator,
configMap: ?ConfigMap = null,
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first)
return;
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
};
return self;
}
// by convention this should be in the root
@ -37,10 +38,11 @@ pub const ConfigRegistry = struct {
}
pub fn deinit(self: *@This()) void {
pub fn destroy(self: *@This()) void {
if (self.configMap) |*map| {
map.deinit();
}
self.allocator.destroy(self);
}
};

View File

@ -8,22 +8,21 @@ pub const ConsoleCommand = struct {
pub const Console = struct {
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.Console");
allocator: std.mem.Allocator = undefined,
arena: std.heap.ArenaAllocator = undefined,
allocator: std.mem.Allocator,
arena: std.heap.ArenaAllocator,
commandMap: std.StringHashMapUnmanaged(ConsoleCommand) = .{},
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first) {
return;
}
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.arena = std.heap.ArenaAllocator.init(allocator),
.allocator = allocator,
};
core.engine_logs("console system created");
return self;
}
pub fn addConsoleCommand(self: *@This(), funcName: []const u8, func: ConsoleFunc) !void {
@ -57,9 +56,10 @@ pub const Console = struct {
}
}
pub fn deinit(self: *@This()) void {
pub fn destroy(self: *@This()) void {
self.arena.deinit();
self.commandMap.deinit(self.allocator);
self.allocator.destroy(self);
}
};

View File

@ -1,8 +1,6 @@
// main public facing root file for core
const std = @import("std");
pub const machinery = @import("machinery/machinery.zig");
pub const gameObjectList = @import("utils/gameObjectList.zig");
pub const GameObjectList = gameObjectList.GameObjectList;
pub const GameObjectInterface = gameObjectList.GameObjectInterface;
@ -27,10 +25,9 @@ pub const debugBox = debug_draw.debugBox;
pub const debugLine = debug_draw.debugLine;
// pub const script_bindings = script.script_bindings;
pub const misc = @import("misc.zig");
pub const engineTime = @import("engineTime.zig");
pub const engineObject = @import("engineObject.zig");
pub const EngineObjectOpts = engineObject.EngineObjectOpts;
pub const ObjectOpts = engineObject.EngineObjectOpts;
pub const EngineObjectVTable = engineObject.EngineObjectVTable;
pub const MakeTypeName = engineObject.MakeTypeName;
pub const PatchStruct = engineObject.PatchStruct;
@ -41,13 +38,16 @@ pub const EngineObjectDelegate = engineObject.EngineObjectDelegate;
pub const FieldInfo = engineObject.FieldInfo;
pub const jobs = @import("jobs.zig");
pub const JobContext = jobs.ThreadContext;
pub const ThreadContext = jobs.ThreadContext;
pub const JobContext = jobs.JobContext;
pub const JobManager = jobs.JobManager;
pub const JobWorker = jobs.JobWorker;
pub const file_utils = @import("file_utils.zig");
pub const string_utils = @import("string_utils.zig");
pub const type_utils = @import("type_utils.zig");
pub const engine = @import("engine.zig");
pub const tracy = @import("tracy").t;
pub const png = @import("png.zig");
pub const colors = @import("colors.zig");
@ -140,6 +140,8 @@ pub const SceneSystem = scene.SceneSystem;
pub const Engine = engine.Engine;
pub const spng = @import("spng");
pub const MemoryTracker = @import("MemoryTracker.zig");
pub const DefaultSavePath = "Saved";
@ -170,16 +172,14 @@ pub const defaultHighlight = logging.defaultHighlight;
pub const packer = @import("packer");
pub const FileSystem = packer.PackerFS;
pub const PackerFS = packer.PackerFS;
pub const StackCompactor = stacks.StackCompactor;
pub var staticsInitialized = false;
var staticsInitialized = false;
var gEngine: *Engine = undefined;
pub const PackerFS = packer.PackerFS;
var gPackerFS: *PackerFS = undefined;
pub fn fs() *PackerFS {
return gPackerFS;
}
var fsInitialized: bool = false;
pub fn EngineObject(comptime T: type) type {
@ -216,6 +216,8 @@ pub const undefineComponentList = ecs.undefineComponentList;
pub const Entity = ecs.Entity;
pub const createEntity = ecs.createEntity;
pub const script = @import("script.zig");
pub const stacks = @import("stacks.zig");
pub const walkAndPrintStack = stacks.walkAndPrintStack;
@ -234,6 +236,10 @@ pub fn registerObjectAdvanced(comptime T: type, name: []const u8, comptime funcN
try get(GameObjectSystem).registerObjectAdvanced(name, T, funcName);
}
pub fn fs() *PackerFS {
return gPackerFS;
}
pub const Module = ModuleDescription{
.name = "core",
.enabledByDefault = true,
@ -274,6 +280,8 @@ pub fn maybeInitPackerFs(allocator: std.mem.Allocator) !void {
}
pub fn start_module_(map: *SpecVariantMap, args: anytype, allocator: std.mem.Allocator) !void {
staticsInitialized = true;
if (map.get("utility")) |x| {
if (x.boolean == true) {
engine_logs("utility mode - no gui");
@ -286,9 +294,15 @@ pub fn start_module_(map: *SpecVariantMap, args: anytype, allocator: std.mem.All
fatDump = true;
}
script.lua.setupMiniDump(fatDump);
_ = try algorithm.createNameRegistry(allocator);
// LUA BEGIN -- what if i want to make the scripting integration optional?
// try script.start_lua(allocator);
// LUA END
try maybeInitPackerFs(allocator);
// load configs.
//const name = if (@hasField(@TypeOf(programSpec), "configName")) programSpec.configName else programSpec.name;
var name = map.get("name").?.string;
if (map.get("configName")) |x| {
@ -297,7 +311,8 @@ pub fn start_module_(map: *SpecVariantMap, args: anytype, allocator: std.mem.All
gEngine = try allocator.create(Engine);
gEngine.* = try Engine.init(allocator);
staticsInitialized = true;
_ = try createObject(ModuleLoader, .{});
try console.start();
const engineName = try std.fmt.allocPrint(allocator, "{s}Engine.ini", .{name});
defer allocator.free(engineName);
@ -308,15 +323,16 @@ pub fn start_module_(map: *SpecVariantMap, args: anytype, allocator: std.mem.All
try logging.setupLogging(gEngine);
}
try algorithm.string_pool.setup(allocator);
gEngine.stringContext = algorithm.string_pool.gStringContext;
try ecs.setup(allocator);
_ = try gEngine.createObject(scene.SceneSystem, .{ .can_tick = true });
try algorithm.string_pool.setup(allocator);
// _ = try gEngine.createObject(script_bindings.ScriptTicks, .{ .can_tick = true });
_ = try createObject(ModuleLoader, .{});
_ = try createObject(ecs.EcsRegistry, .{ .can_tick = true, .isCore = true });
_ = try createObject(scene.SceneSystem, .{ .can_tick = true });
_ = try createObject(GameObjectSystem, .{ .can_tick = true });
_ = try createObject(inputs.InputStack, .{});
_ = try createObject(console.Console, .{});
_ = try inputs.initInputStack();
// components define
try ecs.defineComponentList(ComponentList, allocator);
@ -327,8 +343,8 @@ pub fn start_module_(map: *SpecVariantMap, args: anytype, allocator: std.mem.All
pub fn setupFromModule(__args: ModuleLoaderArgs) !void {
gEngine = __args.engine;
gPackerFS = __args.packerFS;
// script.setupLuaFromModule(__args.luaState, __args.luaAllocator);
algorithm.names.gRegistry = __args.nameRegistry;
algorithm.string_pool.gStringContext = gEngine.stringContext;
logging.setupLoggingFromModule();
staticsInitialized = true;
@ -350,36 +366,18 @@ pub fn shutdown_module(_: std.mem.Allocator) void {
ecs.shutdown();
algorithm.destroyNameRegistry();
gEngine.deinit();
gPackerFS.destroy();
// LUA BEGIN
// script.shutdown_lua();
// LUA END
console.shutdown();
algorithm.string_pool.shutdown();
gEngine.deinit();
return;
}
pub fn parallelJob(capture: anytype, async: bool, maxWorkerCount: ?u32) !void {
const z = tracy.ZoneNC(@src(), "parallel Job", 0xCF8774);
defer z.End();
const jobManager = getEngine().jobManager;
const requestedWorkers = if (maxWorkerCount) |m| m else jobManager.numWorkers;
const baseWorkerCount = @min(requestedWorkers, @max(getEngine().jobManager.numWorkers - 2, 1));
const workerStart: u32 = if (async) 0 else 1;
for (workerStart..baseWorkerCount) |workerId| {
try jobManager.newJob(capture, .{ .threadId = @intCast(workerId), .threadCount = baseWorkerCount });
}
// start working on the job locally
if (!async) {
try jobManager.runLocally(capture, .{ .threadId = 0, .threadCount = baseWorkerCount });
}
}
pub fn dispatchJob(capture: anytype) !void {
try gEngine.jobManager.newJob(capture, .{ .threadId = 0, .threadCount = 1 }); //0, 1, null);
try gEngine.jobManager.newJob(capture);
}
pub fn createObject(comptime T: type, params: engine.NeonObjectParams) !*T {
@ -526,11 +524,7 @@ pub fn startup_getArgs(p_allocator: *anyopaque) ModuleLoaderArgs {
pub fn modulePreamble(p_allocator: *anyopaque, p_a: ?*anyopaque) !std.mem.Allocator {
const args = startup_getArgs(p_a.?);
const allocator = startup_getAllocator(p_allocator);
if (comptime !BuildOption("static_build")) {
try setupFromModule(args);
}
try setupFromModule(args);
return allocator;
}
@ -614,7 +608,3 @@ pub fn itof32(i: anytype) f32 {
pub fn itof64(i: anytype) f32 {
return @as(f32, @floatFromInt(i));
}
pub fn setShutdownModule(shutdownFunction: *const fn () callconv(.c) void) void {
getEngine().shutdownModuleFunction = shutdownFunction;
}

View File

@ -111,7 +111,7 @@
pub fn createEntity() !Entity {
const rv = Entity{ .handle = try getRegistry().baseSet.createObject(.{}) };
// core.engine_log("entity created: {d}", .{rv.handle.index});
core.engine_log("entity created: {d}", .{rv.handle.index});
return rv;
}
@ -132,7 +132,7 @@ pub fn destroyAllEntities() void {
}
pub fn destroyEntity(e: Entity) void {
// core.engine_log("entity destroyed: {d}", .{e.handle.index});
core.engine_log("entity destroyed: {d}", .{e.handle.index});
const registry = getRegistry();
if (registry.baseSet.get(e.handle)) |entityEntry| {
for (entityEntry.containers.items) |ref| {
@ -210,15 +210,15 @@ pub const EcsRegistry = struct {
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "core.EcsRegistry");
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first) {
return;
}
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
.baseSet = BaseSet.init(allocator),
};
return self;
}
pub fn registerContainer(self: *@This(), ref: EcsContainerRef, _containerName: core.Name) !void {
@ -279,11 +279,13 @@ pub const EcsRegistry = struct {
}
pub fn deinit(self: *@This()) void {
// this should never work... wtf?
// for (self.containers.items) |ref| {
// ref.vtable.evictFromRegistry(ref.ptr);
// }
self.destroy();
}
pub fn destroy(self: *@This()) void {
for (self.containers.items) |ref| {
ref.vtable.evictFromRegistry(ref.ptr);
}
for (self.systems.items) |ref| {
ref.vtable.destroy(ref.ptr);
}
@ -298,6 +300,7 @@ pub const EcsRegistry = struct {
self.containers.deinit(self.allocator);
self.containerNames.deinit(self.allocator);
self.containersByName.deinit(self.allocator);
self.allocator.destroy(self);
}
};
@ -374,7 +377,7 @@ pub const Entity = struct {
}
pub fn addComponent(self: @This(), comptime Component: type) ?*Component {
// core.engine_log("adding component: {d} {s}", .{ self.handle.index, @typeName(Component) });
core.engine_log("adding component: {d} {s}", .{ self.handle.index, @typeName(Component) });
const rv = Component.BaseContainer.createWithHandleECS(self.handle);
const list = &getRegistry().baseSet.get(self.handle).?.containers;

View File

@ -5,8 +5,6 @@ const time = @import("engineTime.zig");
const core = @import("core.zig");
const jobs = @import("jobs.zig");
const math = @import("math.zig");
const builtin = @import("builtin");
const pscopes = core.algorithm.pscopes;
const tracy = @import("tracy").t;
const p2 = @import("p2");
@ -86,15 +84,8 @@ pub const Engine = struct {
delegates: EngineDelegates,
rootTimer: std.time.Timer,
scopesContext: *pscopes.ScopesContext,
timeTilCalibration: f64 = 1.0, // in seconds
calibrationPeriod: f64 = 1.0, // in seconds
first: bool = true,
shutdownModuleFunction: ?*const fn () callconv(.c) void = null,
stringContext: *core.algorithm.string_pool.StringContext = undefined,
pub fn init(allocator: std.mem.Allocator) !@This() {
const rv = Engine{
.allocator = allocator,
@ -105,10 +96,8 @@ pub const Engine = struct {
.lastEngineTime = 0.0,
.jobManager = try JobManager.create(allocator),
.eventors = .{},
.rootTimer = try std.time.Timer.start(),
.frameNumber = 1,
.exitListeners = .{},
.scopesContext = try pscopes.ScopesContext.create(allocator),
// .nfdRuntime = try nfd.NFDRuntime.create(allocator, .{}),
.delegates = EngineDelegates.init(allocator),
};
@ -124,13 +113,14 @@ pub const Engine = struct {
self.jobManager.destroy();
self.engineObjectsByName.deinit(self.allocator);
self.scopesContext.destroy();
if (self.destroyListCore.items.len > 0) {
var i: i32 = @intCast(self.destroyListCore.items.len - 1);
while (i >= 0) : (i -= 1) {
const item = self.destroyListCore.items[@as(usize, @intCast(i))];
self.destroyObject(item);
if (item.vtable.deinit_func) |deinitFn| {
deinitFn(item.ptr);
}
}
}
self.destroyListCore.deinit(self.allocator);
@ -161,10 +151,6 @@ pub const Engine = struct {
return @ptrCast(@alignCast(rv));
}
const DebugSlack = struct {
slack: [1024 * 64]u8 align(16) = undefined,
};
// creates an engine object using the engine's allocator.
pub fn createObjectVTable(self: *@This(), vtable: *core.EngineObjectVTable, params: NeonObjectParams) !*anyopaque {
if (self.createObjectLock) {
@ -176,17 +162,7 @@ pub const Engine = struct {
self.createObjectLock = true;
defer self.createObjectLock = false;
const newIndex = self.engineObjects.items.len;
var newObjectPtr: *anyopaque = undefined; //self.allocator.create(DebugSlack);
if (comptime core.BuildOption("static_build")) {
newObjectPtr = @ptrCast(@alignCast((try self.allocator.alignedAlloc(u8, .@"16", vtable.typeSize))));
} else {
newObjectPtr = @ptrCast(@alignCast((try self.allocator.create(DebugSlack))));
}
try vtable.init_func(newObjectPtr, self.allocator, true);
// try vtable.init_func(self.allocator); // call this thing with the special allocator that adds vtable. slackSize to it.
const newObjectPtr = try vtable.init_func(self.allocator); // call this thing with the special allocator that adds vtable. slackSize to it.
const newObjectRef = EngineObjectRef{
.ptr = @as(*anyopaque, @ptrCast(newObjectPtr)),
@ -254,7 +230,6 @@ pub const Engine = struct {
}
pub fn tick(self: *@This()) !void {
// ------------ frame updates ---------
tracy.FrameMark();
tracy.FrameMarkStart("frame");
defer tracy.FrameMarkEnd("frame");
@ -263,18 +238,14 @@ pub const Engine = struct {
var z1 = tracy.ZoneN(@src(), "time updates");
var shouldCalibrate: bool = false;
if (self.first) {
self.first = false;
self.engineStartTime = newTime;
self.lastEngineTime = newTime;
self.sessionStamp = std.time.microTimestamp();
shouldCalibrate = true;
}
if (newTime < self.lastEngineTime) {
try core.assertf(false, "negative deltaTime this should not be possible", .{});
std.debug.print("Warning! negative deltaTime? clamping to 0.0 newTime: {d} lastEngineTime:{d}", .{
newTime,
self.lastEngineTime,
@ -282,16 +253,6 @@ pub const Engine = struct {
}
self.deltaTime = @max(newTime - self.lastEngineTime, 0.0);
self.timeTilCalibration -= self.deltaTime;
if (self.timeTilCalibration < 0 or shouldCalibrate) {
const zcalibrate = core.tracy.ZoneN(@src(), "calibrate timing scopeContext");
self.scopesContext.calibrate();
zcalibrate.End();
self.timeTilCalibration = self.calibrationPeriod;
}
math.rollingAverage(&self.averageFrameTime, self.deltaTime, @floatFromInt(self.averageFrameSampleWindow));
z1.End();
@ -302,9 +263,7 @@ pub const Engine = struct {
z2.End();
self.frameNumber += 1;
// -------------------------------------------
// sync
var z3 = tracy.ZoneN(@src(), "platform event updates");
if (self.platformProcEventsFunc) |procEventsFn| {
try procEventsFn(self.platformCtx, self.frameNumber);
@ -315,14 +274,12 @@ pub const Engine = struct {
objectRef.vtable.processEvents.?(objectRef.ptr, self.frameNumber) catch @panic("process event error");
}
// pretick
var z4 = tracy.ZoneN(@src(), "pretick event updates");
for (self.preTickables.items) |*objectRef| {
objectRef.vtable.preTick_func.?(objectRef.ptr, self.deltaTime) catch @panic("pretick event error");
}
z4.End();
// tick
var z5 = tracy.ZoneN(@src(), "system object ticks");
var index: isize = @as(isize, @intCast(self.tickables.items.len)) - 1;
while (index >= 0) : (index -= 1) {
@ -336,7 +293,6 @@ pub const Engine = struct {
const systemsThreadTime: f64 = time.getEngineTime() - newTime;
// renderer
for (self.renderers.items) |*renderer| {
var z = tracy.Zone(@src());
z.Name(renderer.vtable.typeName);
@ -372,29 +328,14 @@ pub const Engine = struct {
self.destroyDependents();
}
fn destroyObject(self: *@This(), item: EngineObjectRef) void {
core.engine_log("destroying object {s}", .{item.vtable.typeName});
if (item.vtable.deinit_func) |deinitFn| {
deinitFn(item.ptr);
}
if (comptime core.BuildOption("static_build")) {
var slice: []u8 = undefined;
slice.ptr = @ptrCast(@alignCast(item.ptr));
slice.len = item.vtable.typeSize;
self.allocator.rawFree(slice, .@"16", @returnAddress());
} else {
const asStruct: *DebugSlack = @ptrCast(@alignCast(item.ptr));
self.allocator.destroy(asStruct);
}
}
fn destroyDependents(self: *@This()) void {
if (self.destroyListSimple.items.len > 0) {
var i: i32 = @intCast(self.destroyListSimple.items.len - 1);
while (i >= 0) : (i -= 1) {
const item = self.destroyListSimple.items[@as(usize, @intCast(i))];
self.destroyObject(item);
if (item.vtable.deinit_func) |deinitFn| {
deinitFn(item.ptr);
}
}
}
self.dependentsDestroyed.store(true, .seq_cst);

View File

@ -68,6 +68,10 @@ pub fn updateEngineVTable(comptime T: type) void {
vtable.* = T.NeonObjectTable;
vtable.version = version + 1;
if (@hasDecl(T, "objectReload")) {
core.get(T).objectReload();
}
}
pub fn PatchStruct(comptime T: type, p: *T, old: []const FieldInfo) void {
@ -163,8 +167,7 @@ pub const EngineObjectVTable = struct {
singletonName: ?[]const u8 = null,
// new init_function passes in an already created object
init_func: *const fn (*anyopaque, std.mem.Allocator, bool) EngineDataEventError!void,
init_func: *const fn (std.mem.Allocator) EngineDataEventError!*anyopaque,
tick_func: ?*const fn (*anyopaque, f64) void = null,
engineDraw_func: ?*const fn (*anyopaque, f64) void = null,
preTick_func: ?*const fn (*anyopaque, f64) EngineDataEventError!void = null,
@ -268,10 +271,11 @@ pub const EngineObjectVTable = struct {
if (@hasDecl(TargetType, "init")) {
const wrappedInit = struct {
pub fn func(p: *anyopaque, allocator: std.mem.Allocator, first: bool) EngineDataEventError!void {
const newObject: *TargetType = @ptrCast(@alignCast(p)); // funcFind(allocator) catch return error.BadInit;
newObject.init(allocator, first) catch return error.BadInit;
// return @as(*anyopaque, @ptrCast(newObject));
const funcFind: @TypeOf(@field(TargetType, "init")) = @field(TargetType, "init");
pub fn func(allocator: std.mem.Allocator) EngineDataEventError!*anyopaque {
const newObject = funcFind(allocator) catch return error.BadInit;
return @as(*anyopaque, @ptrCast(newObject));
}
};
@ -280,10 +284,11 @@ pub const EngineObjectVTable = struct {
if (@hasDecl(TargetType, "create")) {
const wrappedInit = struct {
pub fn func(p: *anyopaque, allocator: std.mem.Allocator, first: bool) EngineDataEventError!void {
const newObject: *TargetType = @ptrCast(@alignCast(p)); // funcFind(allocator) catch return error.BadInit;
newObject.create(allocator, first) catch return error.BadInit;
// return @as(*anyopaque, @ptrCast(newObject));
const funcFind: @TypeOf(@field(TargetType, "create")) = @field(TargetType, "create");
pub fn func(allocator: std.mem.Allocator) EngineDataEventError!*anyopaque {
const newObject = funcFind(allocator) catch return error.BadInit;
return @as(*anyopaque, @ptrCast(newObject));
}
};

View File

@ -1,19 +1,13 @@
const std = @import("std");
const core = @import("core.zig");
const pscopes = core.algorithm.pscopes;
// returns time since engine started in nanoseconds
// todo, replace with monotonic timer
pub fn getEngineTime() f64 {
if (core.staticsInitialized) {
const read = core.getEngine().rootTimer.read();
return @as(f64, @floatFromInt(read)) / std.time.ns_per_s;
}
return 0;
return @as(f64, @floatFromInt(std.time.milliTimestamp())) / 1000;
}
// returns a pscopes.TimingScope for the current time.
pub fn takeProfilingStamp() pscopes.TimingScope {
return core.getEngine().scopesContext.stampScope();
// return the current system timestamp in nanoseconds
pub fn getEngineTimeStamp() i128 {
return std.time.nanoTimestamp();
}

View File

@ -121,23 +121,21 @@ fn dllChangedCallback(pathChanged: []const u8, ctx: ?*anyopaque) void {
}
pub const ModuleLoader = struct {
backingAllocator: std.mem.Allocator = undefined,
arena: std.heap.ArenaAllocator = undefined,
backingAllocator: std.mem.Allocator,
arena: std.heap.ArenaAllocator,
loadedModules: std.ArrayListUnmanaged(*LoadedModule) = .{},
watchInitialized: bool = false,
watchInitializeFn: ?*const fn () void = null,
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "core.ModuleLoader");
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first) {
return;
}
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.backingAllocator = allocator,
.arena = std.heap.ArenaAllocator.init(allocator),
};
return self;
}
pub fn addModule(self: *@This(), moduleName: []const u8) !void {
@ -152,11 +150,8 @@ pub const ModuleLoader = struct {
const loaded = try self.arena.allocator().create(LoadedModule);
if (self.watchInitialized == false) {
// core.fs().watchPath("zig-out/modules/");
if (self.watchInitializeFn) |watchInitializeFn| {
watchInitializeFn();
self.watchInitialized = true;
}
core.fs().watchPath("zig-out/modules/");
self.watchInitialized = true;
}
loaded.* = LoadedModule{
@ -170,7 +165,6 @@ pub const ModuleLoader = struct {
}
try core.fs().addFileChangedCallback(libFileName, dllChangedCallback, loaded);
core.engine_log("[ModuleLoader]: addFileChangedCallback '{s}'", .{libFileName});
try self.loadedModules.append(self.arena.allocator(), loaded);
}
@ -216,9 +210,10 @@ pub const ModuleLoader = struct {
}
}
pub fn deinit(self: *@This()) void {
pub fn destroy(self: *@This()) void {
// std.fs.cwd().deleteTree(".modulecache") catch {};
self.arena.deinit();
self.backingAllocator.destroy(self);
}
};

View File

@ -0,0 +1,34 @@
const std = @import("std");
const logging = @import("logging.zig");
pub fn writeToFile(data: []const u8, path: []const u8) !void {
const file = try std.fs.cwd().createFile(
path,
.{
.read = true,
},
);
const bytes_written = try file.writeAll(data);
_ = bytes_written;
logging.engine_log("written: bytes to {s}", .{path});
}
pub fn splitIntoLines(file_contents: []const u8) std.mem.SplitIterator(u8) {
// find a \n and see if it has \r\n
var index: u32 = 0;
while (index < file_contents.len) : (index += 1) {
if (file_contents[index] == '\n') {
if (index > 0) {
if (file_contents[index - 1] == '\r') {
return std.mem.split(u8, file_contents, "\r\n");
} else {
return std.mem.split(u8, file_contents, "\n");
}
} else {
return std.mem.split(u8, file_contents, "\n");
}
}
}
return std.mem.split(u8, file_contents, "\n");
}

View File

@ -71,20 +71,21 @@ pub const GameObjectSystem = struct {
// this is the new one, GameObjectList should be deleted after this passes initial usability
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "core.GameObjectSystem");
allocator: std.mem.Allocator = undefined,
allocator: std.mem.Allocator,
objectDefinitions: std.AutoHashMapUnmanaged(u32, GameObjectInterfaceVTable) = .{},
typesArena: std.heap.ArenaAllocator = undefined,
typesArena: std.heap.ArenaAllocator,
objectSpawnEvents: std.ArrayListUnmanaged(SpawnEvent) = .{},
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first)
return;
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
.typesArena = std.heap.ArenaAllocator.init(self.allocator),
};
return self;
}
pub fn spawnObject(self: *@This(), comptime T: type, objectName: []const u8, parameters: SpawnParameters) !*T {
@ -176,8 +177,9 @@ pub const GameObjectSystem = struct {
_ = dt;
}
pub fn deinit(self: *@This()) void {
pub fn destroy(self: *@This()) void {
self.typesArena.deinit();
self.allocator.destroy(self);
}
};

View File

@ -111,7 +111,6 @@ fn BindingData(Func: type) type {
pub const ActionBinding = struct {
data: BindingData(ActionFunc),
keys: std.ArrayListUnmanaged(ActionBindingKey) = .{},
layer: ?*BindingLayer = null,
// pub const Listener = struct {
// id: u32,
@ -574,11 +573,11 @@ pub const BindingLayer = struct {
// not gonna actually deal with layers right now
pub const InputStack = struct {
allocator: std.mem.Allocator = undefined,
allocator: std.mem.Allocator,
active: ?*BindingLayer = undefined,
active: ?*BindingLayer,
bindingStack: std.ArrayListUnmanaged(*BindingLayer) = .{},
arena: std.heap.ArenaAllocator = undefined,
arena: std.heap.ArenaAllocator,
keysDown: std.AutoHashMapUnmanaged(Key, bool) = .{},
@ -591,10 +590,8 @@ pub const InputStack = struct {
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.InputStack");
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first) {
return;
}
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
@ -603,6 +600,8 @@ pub const InputStack = struct {
};
core.EngineObject(@This()).gInstance = self;
return self;
}
pub fn updatePreviousInputs(self: *@This()) void {
@ -716,6 +715,7 @@ pub const InputStack = struct {
if (self.active) |active| {
active.destroy();
}
self.allocator.destroy(self);
}
};

View File

@ -0,0 +1,14 @@
moveAxis = CreateInput2DAxis()
moveAxis:addKey(Keys.W, 1.0, Axis.Y)
moveAxis:addKey(Keys.S, -1.0, Axis.Y)
moveAxis:addKey(Keys.A, -1.0, Axis.X)
moveAxis:addKey(Keys.D, 1.0, Axis.X)
moveAxis:addListener(balls, )
Input.addBinding("move", moveAxis, true)
Input.removeBinding("move")

View File

@ -7,18 +7,13 @@ const ArrayListUnmanaged = std.ArrayListUnmanaged;
const mutex_job_queue = core.BuildOption("mutex_job_queue");
pub const TaskInstanceOptions = struct {
threadId: u32,
threadCount: u32,
};
pub const JobManager = struct {
allocator: std.mem.Allocator,
// need a mutex for the jobQueue... todo later
jobQueue: RingQueueU(ThreadContext),
jobQueue: RingQueueU(JobContext),
mutex: std.Thread.Mutex = .{},
jobQueueConcurrent: core.ConcurrentQueueU(ThreadContext),
jobQueueConcurrent: core.ConcurrentQueueU(JobContext),
workers: []*JobWorker,
numCpus: usize,
numWorkers: u32 = 0,
@ -29,8 +24,8 @@ pub const JobManager = struct {
self.* = JobManager{
.allocator = allocator,
.numCpus = std.Thread.getCpuCount() catch 4,
.jobQueue = RingQueueU(ThreadContext).init(allocator, 4096) catch unreachable,
.jobQueueConcurrent = core.ConcurrentQueueU(ThreadContext).initCapacity(allocator, 4096) catch unreachable,
.jobQueue = RingQueueU(JobContext).init(allocator, 4096) catch unreachable,
.jobQueueConcurrent = core.ConcurrentQueueU(JobContext).initCapacity(allocator, 4096) catch unreachable,
.workers = undefined,
};
@ -40,28 +35,17 @@ pub const JobManager = struct {
var i: usize = 0;
while (i < self.workers.len) : (i += 1) {
self.workers[i] = JobWorker.init(self.allocator, i, true) catch unreachable;
self.workers[i].workerThreadNumber = i;
self.workers[i] = JobWorker.init(self.allocator, i) catch unreachable;
self.workers[i].workerId = i;
self.workers[i].manager = self;
}
return self;
}
pub fn runLocally(self: *@This(), capture: anytype, task: TaskInstanceOptions) !void {
pub fn newJob(self: *@This(), capture: anytype) !void {
const Lambda = @TypeOf(capture);
const ctx = try ThreadContext.new(self.allocator, Lambda, capture, task);
//pub fn init(allocator: std.mem.Allocator, workerNumber: usize, detached:bool) !*@This() {
const worker = try JobWorker.init(self.allocator, 0xff, false); // todo move these things into a pool of non-detached workers that we can just grab whenever
defer worker.deinit();
worker.currentJobContext = ctx;
worker.run();
}
pub fn newJob(self: *@This(), capture: anytype, task: TaskInstanceOptions) !void {
const Lambda = @TypeOf(capture);
const ctx = try ThreadContext.new(self.allocator, Lambda, capture, task);
const ctx = try JobContext.new(self.allocator, Lambda, capture);
if (mutex_job_queue) {
self.mutex.lock();
@ -107,7 +91,7 @@ pub const JobManager = struct {
}
pub fn clearJobs(self: *@This()) void {
var jobCtx: ?ThreadContext = null;
var jobCtx: ?JobContext = null;
if (mutex_job_queue) {
self.mutex.lock();
@ -133,16 +117,15 @@ pub const JobManager = struct {
pub const JobWorker = struct {
detached: bool = true, // most threads are detached, completions are handled via callbacks.
currentJobContext: ?ThreadContext = null,
workerThread: ?std.Thread = null,
currentJobContext: ?JobContext = null,
workerThread: std.Thread,
futex: Atomic(u32) = Atomic(u32).init(0),
current: u32 = 0,
shouldDie: Atomic(bool) = Atomic(bool).init(false),
busy: Atomic(bool) = Atomic(bool).init(false),
allocator: std.mem.Allocator,
workerThreadNumber: usize = 0, // identifier for debugging purposes, never changes.
workerId: usize = 0,
manager: ?*JobManager = null,
scratchArena: std.heap.ArenaAllocator,
pub fn wake(self: *JobWorker) void {
// this needs to be re-evaluated, it's nice for system performance but
@ -150,20 +133,15 @@ pub const JobWorker = struct {
std.Thread.Futex.wake(&self.futex, 1);
}
pub fn init(allocator: std.mem.Allocator, workerNumber: usize, detached: bool) !*@This() {
pub fn init(allocator: std.mem.Allocator, workerNumber: usize) !*@This() {
const self = try allocator.create(JobWorker);
self.* = .{
.allocator = allocator,
.detached = detached,
.workerThreadNumber = workerNumber,
.scratchArena = std.heap.ArenaAllocator.init(allocator),
.workerThread = try std.Thread.spawn(.{}, @This().workerThreadFunc, .{self}),
.workerId = workerNumber,
};
if (detached) {
self.workerThread = try std.Thread.spawn(.{}, @This().workerThreadFunc, .{self});
}
return self;
}
@ -171,26 +149,8 @@ pub const JobWorker = struct {
return self.busy.load(.acquire);
}
fn run(self: *@This()) void {
self.busy.store(true, .seq_cst);
if(self.manager)|manager|
{
manager.bump();
}
var ctx = self.currentJobContext.?;
ctx.workerInfo = self;
ctx.func(ctx.capture, &ctx);
self.busy.store(false, .seq_cst);
ctx.deinit();
if (self.scratchArena.reset(.retain_capacity)) {}
self.currentJobContext = null;
}
pub fn workerThreadFunc(self: *@This()) void {
const printed = std.fmt.allocPrintSentinel(self.allocator, "WorkerThread_{d}", .{self.workerThreadNumber}, 0) catch unreachable;
const printed = std.fmt.allocPrintSentinel(self.allocator, "WorkerThread_{d}", .{self.workerId}, 0) catch unreachable;
tracy.InitThread();
tracy.SetThreadName(@as([*:0]u8, @ptrCast(printed.ptr)));
@ -201,7 +161,12 @@ pub const JobWorker = struct {
while (!self.shouldDie.load(.acquire)) {
if (self.currentJobContext != null) {
wakeGrabCount = 0;
self.run();
self.busy.store(true, .seq_cst);
var ctx = self.currentJobContext.?;
ctx.func(ctx.capture, &ctx);
self.busy.store(false, .seq_cst);
ctx.deinit();
self.currentJobContext = null;
} else {
std.Thread.Futex.wait(&self.futex, self.current);
}
@ -215,6 +180,13 @@ pub const JobWorker = struct {
self.manager.?.mutex.unlock();
} else {
self.currentJobContext = manager.jobQueueConcurrent.pop();
//while (wakeGrabCount > 0) : (wakeGrabCount -= 1) {
// self.currentJobContext = manager.jobQueueConcurrent.pop();
// if (self.currentJobContext != null) {
// break;
// }
// std.Thread.sleep(1000 * 1000);
//}
wakeGrabCount = 1;
}
}
@ -228,99 +200,28 @@ pub const JobWorker = struct {
pub fn deinit(self: *@This()) void {
self.shouldDie.store(true, .seq_cst);
self.wake();
if(self.workerThread)|workerThread|
{
workerThread.join();
}
self.scratchArena.deinit();
self.workerThread.join();
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;
pub const ThreadContext = struct {
pub const JobContext = struct {
const Self = @This();
funcName: []const u8 = "unnamed",
allocator: std.mem.Allocator, //todo, backed arena allocator would be sick for this.
func: *const fn (*anyopaque, *ThreadContext) void, // todo, add an error for job funcs
func: *const fn (*anyopaque, *JobContext) void, // todo, add an error for job funcs
capture: *anyopaque = undefined,
threadId: u32,
threadIdCount: u32,
destroyFunc: *const fn (*anyopaque, std.mem.Allocator) void,
workerInfo: ?*JobWorker = null,
pub fn new(
allocator: std.mem.Allocator,
comptime CaptureType: type,
capture: CaptureType,
task: TaskInstanceOptions,
) !ThreadContext {
const align8_struct = struct { size: u64 };
pub fn new(allocator: std.mem.Allocator, comptime CaptureType: type, capture: CaptureType) !JobContext {
if (!@hasDecl(CaptureType, "func")) {
return error.NoValidLambda;
}
const Wrap = struct {
pub fn wrappedFunc(pointer: *anyopaque, context: *ThreadContext) void {
pub fn wrappedFunc(pointer: *anyopaque, context: *JobContext) void {
var ptr = @as(*CaptureType, @ptrCast(@alignCast(pointer)));
ptr.func(context);
}
@ -333,9 +234,6 @@ pub const ThreadContext = struct {
const self = Self{
.allocator = allocator,
.threadId = task.threadId,
.threadIdCount = task.threadCount,
.funcName = @typeName(CaptureType),
.func = Wrap.wrappedFunc,
.destroyFunc = Wrap.wrappedDestroy,
.capture = try allocator.create(CaptureType),
@ -345,22 +243,6 @@ pub const ThreadContext = struct {
return self;
}
// helper functions
pub fn scratch(self: *@This()) std.mem.Allocator {
return self.workerInfo.?.scratchArena.allocator();
}
pub fn splitSlice(self: @This(), comptime T: type, slice: []T) struct{ slice: []T, startIndex: usize } {
const r = core.p2.splitSliceWork(T, slice, self.threadId, self.threadIdCount);
return .{.slice = r.slice, .startIndex = r.startIndex};
}
pub fn barrier(self: *@This(), comptime src: std.builtin.SourceLocation) !void {
try Barrier(src).sync(self.threadId, self.threadIdCount);
}
pub fn deinit(self: *Self) void {
self.destroyFunc(self.capture, self.allocator);
}

View File

@ -192,13 +192,13 @@ pub const FileLog = struct {
pub const LoggerSys = struct {
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.LoggerSys");
writeOutBuffer: std.ArrayList(u8) = .{},
flushBuffer: std.ArrayList(u8) = .{},
allocator: std.mem.Allocator = undefined,
logFilePath: []const u8 = "none",
logFile: std.fs.File = undefined,
consoleFile: std.fs.File = undefined,
writerBuffer: []u8 = undefined,
writeOutBuffer: std.ArrayList(u8),
flushBuffer: std.ArrayList(u8),
allocator: std.mem.Allocator,
logFilePath: []const u8,
logFile: std.fs.File,
consoleFile: std.fs.File,
writerBuffer: []u8,
lock: std.Thread.Mutex = .{},
flushing: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
@ -306,14 +306,12 @@ pub const LoggerSys = struct {
}
}
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first) {
return;
}
pub fn init(allocator: std.mem.Allocator) !*@This() {
const cwd = std.fs.cwd();
const ofile = std.fmt.allocPrint(allocator, core.DefaultSavePath ++ "/{s}", .{"Session_Log.txt"}) catch unreachable;
cwd.makePath(core.DefaultSavePath) catch unreachable;
const self = try allocator.create(@This());
self.* = @This(){
.allocator = allocator,
.writeOutBuffer = std.ArrayList(u8).initCapacity(allocator, LogBufferSize) catch unreachable,
@ -323,6 +321,8 @@ pub const LoggerSys = struct {
.logFile = cwd.createFile(ofile, .{}) catch unreachable,
.consoleFile = std.fs.File.stdout(),
};
return self;
}
pub fn deinit(self: *@This()) void {
@ -333,6 +333,8 @@ pub const LoggerSys = struct {
self.writeOutBuffer.deinit(self.allocator);
self.flushBuffer.deinit(self.allocator);
self.allocator.destroy(self);
}
pub fn processEvents(self: *@This(), frameNumber: u64) core.EngineDataEventError!void {

View File

@ -0,0 +1,33 @@
tickables = {}
properties = {}
local function registerTick(object, f)
table.insert(tickables, {obj = object, func = f})
end
function __TickScripts(deltaTime)
for index, entry in ipairs(tickables) do
entry.func(entry.obj, deltaTime)
end
end
function __RegisterEntityProperty(userdata)
properties[userdata] = {}
end
function SetProperty(entity, property)
properties[entity] = property
end
function GetProperty(entity)
return properties[entity]
end
function GetObject(entity)
return properties[entity]
end
Core = {
registerTick = registerTick;
}

View File

@ -0,0 +1,2 @@
print("Hello from lua.")

View File

@ -1,56 +0,0 @@
pub const StructApi = struct {
fields: []const StructField,
};
pub const StructField = struct {
name: [:0]const u8,
offset: usize,
alignment: usize,
size: usize,
};
fn generateStructApi(comptime T: type) StructApi {
return .{
.fields = &generateFieldsList(T),
};
}
fn generateFieldsList(comptime T: type) [std.meta.fields(T).len]StructField {
var flist: [std.meta.fields(T).len]StructField = undefined;
const E = std.meta.FieldEnum(T);
inline for (std.meta.fields(T)) |field| {
const fieldIndex = @intFromEnum(@field(E, field.name));
flist[fieldIndex].name = field.name;
flist[fieldIndex].size = @sizeOf(field.type);
flist[fieldIndex].alignment = @alignOf(field.type);
flist[fieldIndex].offset = @offsetOf(T, field.name);
}
return flist;
}
pub fn ComponentApi(comptime T: type) type {
return struct {
pub const Api = generateStructApi(T);
};
}
pub fn listApi(structApi: StructApi) void {
for (structApi.fields) |field| {
core.engine_log("field: {s} offset: {d} name: {d} size: {d}", field);
}
}
// example function call via api
pub fn callInner(func: anytype, args: []const u8) void {
const Args = std.meta.ArgsTuple(@typeInfo(@TypeOf(func)).pointer.child);
const asArgsTuple: *const Args = @ptrCast(@alignCast(args.ptr));
@call(.auto, func, asArgsTuple.*);
}
const std = @import("std");
const core = @import("../core.zig");

View File

@ -1,7 +1,14 @@
const std = @import("std");
const misc = @import("misc.zig");
const zm = @import("zmath");
const math = std.math;
// LUA BEGIN feel like I should mark up the parts of the engine that need to be
// sliced out should i ever decide I'm sick of lua
const lua = @import("lua");
const pod = lua.pod;
// LUA END
// vector versions.
pub const f32x4 = @Vector(4, f32); // __m128_int
pub const f32x8 = @Vector(8, f32); // __m256_int
@ -65,7 +72,23 @@ pub fn Vector2Type(comptime T: type, comptime typeName: []const u8) type {
x: T = 0,
y: T = 0,
pub const VectorTypeName = typeName;
// LUA BEGIN
pub const PodDataTable: pod.DataTable = .{
.name = typeName,
.funcs = &.{
"fmul",
"dot",
"length",
"normalize",
},
.operators = .{
.add = "add",
.sub = "sub",
.mul = "vmul",
.eq = "equals",
},
};
// LUA END
pub const Ones = @This(){ .x = 1, .y = 1 };
pub const Zeroes = @This(){ .x = 0, .y = 0 };
@ -209,8 +232,6 @@ pub fn Vector3Type(comptime T: type, comptime typeName: []const u8) type {
y: T = 0,
z: T = 0,
pub const VectorTypeName = typeName;
pub const Ones = @This(){ .x = 1, .y = 1, .z = 1 };
pub const Zeroes = @This(){ .x = 0, .y = 0, .z = 0 };
@ -218,6 +239,24 @@ pub fn Vector3Type(comptime T: type, comptime typeName: []const u8) type {
pub const Right = @This(){ .x = 1 };
pub const Forward = @This(){ .z = 1 };
// LUA BEGIN
pub const PodDataTable: pod.DataTable = .{
.name = typeName,
.funcs = &.{
"fmul",
"dot",
"length",
"normalize",
},
.operators = .{
.add = "add",
.sub = "sub",
.mul = "vmul",
.eq = "equals",
},
};
// LUA END
pub inline fn new(x: T, y: T, z: T) @This() {
return .{
.x = x,

41
engine/core/src/misc.zig Normal file
View File

@ -0,0 +1,41 @@
// Random misc. utilities that aren't /totally/ categorized right now.
const logging = @import("logging.zig");
// Creates a for-loopable range between [0, n)
pub fn count(comptime n: anytype) [n]u0 {
return comptime [1]u0{0} ** n;
}
// Creates a for-loopable range between [start, end).
// reccomended for use with inline FORs
pub fn range(comptime start: usize, comptime end: anytype) [end - start]@TypeOf(start) {
comptime var r = [1]@TypeOf(start){start} ** (end - start);
comptime {
for (r, 0..) |val, i| {
r[i] = val + i;
}
}
return comptime r;
}
pub fn slice_to_cstr(str: []const u8) ?[*:0]const u8 {
return @as(?[*:0]const u8, @ptrCast(str.ptr));
}
pub fn buf_to_cstr(str: anytype) ?[*:0]const u8 {
return @as(?[*:0]const u8, @ptrCast(&str[0]));
}
pub const CStr = [*:0]const u8;
pub fn debug_struct(preamble: []const u8, s: anytype) void {
logging.graphics_log("{s}:", .{preamble});
logging.graphics_log(" {any}", .{s});
}
pub fn p_to_av(a: anytype) [*]@TypeOf(a.*) {
return @as([*]@TypeOf(a.*), @ptrCast(a));
}

View File

@ -2,7 +2,7 @@
const std = @import("std");
const spng = @import("spng");
const core = @import("core");
const core = @import("core.zig");
pub const PngContents = struct {
path: []const u8,
@ -27,7 +27,7 @@ pub const PngContents = struct {
return pngContents;
}
pub fn initFromFSCooked(fs: *core.PackerFS, allocator: std.mem.Allocator, path: []const u8) !@This() {
pub fn initFromFSCooked(fs: *core.FileSystem, allocator: std.mem.Allocator, path: []const u8) !@This() {
const mapping = try fs.loadFile(path);
defer fs.unmap(mapping);
@ -55,7 +55,7 @@ pub const PngContents = struct {
return buffer;
}
pub fn initFromFS(fs: *core.PackerFS, allocator: std.mem.Allocator, path: []const u8) !@This() {
pub fn initFromFS(fs: *core.FileSystem, allocator: std.mem.Allocator, path: []const u8) !@This() {
// 1. check the fs to see if a cooked version of the file exists
// 2. load that one if possible
// 3. otherwise, load the other one.

View File

@ -56,8 +56,6 @@ pub const SceneObjectRepr = struct {
attachmentMode: SceneAttachMode = .relative, // doesn't do anything yet, only support relative right now
transformOverride: ?*core.Transform = null,
lastUpdate: u32 = 0,
merge: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), // set to true if we've merged already, false if we havent
};
pub const SceneObject = struct {
@ -296,17 +294,12 @@ fn childAllocator() std.mem.Allocator {
pub const SceneSystem = struct {
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.SceneSystem");
allocator: std.mem.Allocator = undefined,
allocator: std.mem.Allocator,
dynamicObjects: ArrayListUnmanaged(core.ObjectHandle) = .{},
childrenArena: std.heap.ArenaAllocator = undefined,
childrenArena: std.heap.ArenaAllocator,
tickCount: u32 = 0,
sceneObjectContainer: *SceneObjectSet = undefined,
cachedOutputs: std.ArrayList(std.ArrayList(core.Transform)) = .{},
writeOutList: std.ArrayList(std.ArrayList(usize)) = .{},
lastUpdateTransformTime: f64 = 0.0,
pub const Field = SceneObjectSet.Field;
pub const FieldType = SceneObjectSet.FieldType;
@ -333,6 +326,16 @@ pub const SceneSystem = struct {
} else {}
}
// core.engine_log("final\n{d} {d} {d} {d}\n{d} {d} {d} {d}", .{
// final[0][0],
// final[0][1],
// final[0][2],
// final[0][3],
// final[1][0],
// final[1][1],
// final[1][2],
// final[1][3],
// });
repr.transform = core.zm.mul(
core.zm.mul(
core.zm.mul(
@ -344,52 +347,37 @@ pub const SceneSystem = struct {
final,
);
// core.engine_log("final\n{d} {d} {d} {d}\n{d} {d} {d} {d}", .{
// repr.transform[0][0],
// repr.transform[0][1],
// repr.transform[0][2],
// repr.transform[0][3],
// repr.transform[1][0],
// repr.transform[1][1],
// repr.transform[1][2],
// repr.transform[1][3],
// });
repr.lastUpdate = self.tickCount;
}
const useParallelJob = true;
pub fn updateTransforms(self: *@This()) void {
// todo. calculate a running load factor for the number of movable objects
// vs static objects
// if we have a small amount of movable vs static AND if we have > 1000 objects,
// then iterate over dynamicObjects array instead
if (useParallelJob) {
// we use 6 workers if the last timing scope ran > 2ms
// otherwise use 1 worker
var scope = core.engineTime.takeProfilingStamp();
var workerCount: u32 = 1;
if (self.lastUpdateTransformTime > 0.001) // 1ms
{
workerCount = 6;
}
core.parallelJob(UpdateWorldTransformsJob{}, false, workerCount) catch unreachable;
scope.end();
if (scope.duration()) |duration| {
self.lastUpdateTransformTime = duration;
}
} else {
for (Scene.SceneObjectContainer.denseItems(._repr), 0..) |*repr, i| {
const settings = Scene.SceneObjectContainer.readDense(i, .settings);
if (settings.sceneMode == .moveable or repr.lastUpdate == 0) {
const posRot = Scene.SceneObjectContainer.readDense(i, .posRot);
self.updateTransform(repr, posRot);
}
for (Scene.SceneObjectContainer.denseItems(._repr), 0..) |*repr, i| {
const settings = Scene.SceneObjectContainer.readDense(i, .settings);
if (settings.sceneMode == .moveable or repr.lastUpdate == 0) {
const posRot = Scene.SceneObjectContainer.readDense(i, .posRot);
self.updateTransform(repr, posRot);
}
}
}
pub const MaxWorkerCount = 24;
// ----- NeonObject interace ----
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first) {
return;
}
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
.childrenArena = std.heap.ArenaAllocator.init(allocator),
@ -399,24 +387,7 @@ pub const SceneSystem = struct {
Scene.SceneObjectContainer = try SceneObjectSet.create(allocator);
self.sceneObjectContainer = Scene.SceneObjectContainer;
for (0..MaxWorkerCount) |i| {
_ = i;
try self.cachedOutputs.append(self.allocator, .{});
try self.writeOutList.append(self.allocator, .{});
}
}
pub fn getOutputList(self: *@This(), threadId: u32) !*std.ArrayList(usize) {
const outputLen = self.sceneObjectContainer.denseItems(._repr).len;
try self.writeOutList.items[threadId].ensureTotalCapacity(self.allocator, outputLen);
self.writeOutList.items[threadId].shrinkRetainingCapacity(0);
return &self.writeOutList.items[threadId];
}
pub fn getOutputForWorker(self: *@This(), threadId: u32) ![]core.Transform {
const outputLen = self.sceneObjectContainer.denseItems(._repr).len;
try self.cachedOutputs.items[threadId].resize(self.allocator, outputLen);
return self.cachedOutputs.items[threadId].items;
return self;
}
pub fn preTick(self: *@This(), dt: f64) !void {
@ -436,113 +407,10 @@ pub const SceneSystem = struct {
}
pub fn deinit(self: *@This()) void {
for (self.cachedOutputs.items) |*i| {
i.deinit(self.allocator);
}
for (self.writeOutList.items) |*i| {
i.deinit(self.allocator);
}
self.dynamicObjects.deinit(self.allocator);
self.childrenArena.deinit();
Scene.SceneObjectContainer.destroy();
self.cachedOutputs.deinit(self.allocator);
self.writeOutList.deinit(self.allocator);
}
};
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 = parentTransform;
} 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| {
repr.merge.store(false, .seq_cst); // reset the merge big
const index = split.startIndex + i;
self.updateTransform(thread, index, densePosRot, denseRepr, outputs, outputList);
}
z2.End();
self.allocator.destroy(self);
}
};

150
engine/core/src/script.zig Normal file
View File

@ -0,0 +1,150 @@
// scripting integration using lua
const std = @import("std");
pub const lua = @import("lua");
const core = @import("core.zig");
const startup_script = @embedFile("lua/startup.lua");
const core_script = @embedFile("lua/core.lua");
const ecs = @import("ecs.zig");
const ComponentRef = @import("script/ComponentRef.zig");
const ComponentRegistration = @import("script/ComponentRegistration.zig");
pub const script_bindings = @import("script_bindings.zig");
const c = lua.c;
pub var gLuaState: lua.LuaState = undefined;
pub var gLuaAllocator: std.mem.Allocator = undefined;
pub fn setupLuaFromModule(pstate: *anyopaque, p_lua_allocator: *anyopaque) void {
gLuaAllocator = core.startup_getAllocator(p_lua_allocator);
gLuaState.l = @ptrCast(pstate);
}
const luaRegLibs: []const c.luaL_Reg = &.{
.{ .name = "print", .func = printWrapper },
.{ .name = null, .func = null },
};
// binds the default print() function in lua to print to the console
fn printWrapper(l: ?*lua.c.lua_State) callconv(.c) i32 {
const state: lua.LuaState = .{ .l = l };
const argc = state.getTop();
if (core.getLogger() == null) {
core.printRaw("> ", .{});
}
if (argc >= 1) {
core.printRaw("[SCRIPT ]: ", .{});
}
var i: i32 = 1;
while (i <= argc) : (i += 1) {
if (state.isString(i)) {
if (i > 1) {
core.printRaw(" ", .{});
}
core.printRaw("{s}", .{state.toString(i)});
} else if (state.isUserdata(i)) {
const s = state.toStringL(i);
state.pop(1);
core.printRaw("{s}", .{s});
}
}
core.printRaw("\n", .{});
state.pop(argc);
return 0;
}
// initialization of the lua scripting interface.
// this interface is only threadsafe to operate on from the main systems thread (at this time).
pub fn start_lua(allocator: std.mem.Allocator) !void {
gLuaAllocator = allocator;
gLuaState = try lua.LuaState.init(.{});
try lua.pod.setupFormatBuffer(allocator);
// overload and hook into global functions
_ = gLuaState.getGlobal("_G");
c.luaL_setfuncs(gLuaState.l, luaRegLibs.ptr, 0);
gLuaState.pop(1);
try lua.pod.registerPodType(&gLuaState, ecs.Entity);
try lua.pod.registerPodType(&gLuaState, ComponentRegistration);
try script_bindings.registerTypes();
try gLuaState.loadString(startup_script);
try gLuaState.pcall();
try gLuaState.loadString(core_script);
try gLuaState.pcall();
}
pub fn createLuaComponentDefinitions(globalName: []const u8, container: ecs.EcsContainerRef, luaNew: anytype) !void {
const reg = try gLuaState.newZigUserdata(ComponentRegistration);
reg.ref = container;
reg.name = globalName;
reg.luaNew = luaNew;
try gLuaState.setGlobal(globalName);
}
pub fn registerComponent(comptime Component: type, container: ecs.EcsContainerRef) !void {
const ReferenceType = ComponentRef.ComponentReferenceType(Component);
try createLuaComponentDefinitions(@ptrCast(Component.ComponentName), container, ReferenceType.luaNew);
try ReferenceType.registerType(getState());
}
pub fn shutdown_lua() void {
lua.pod.shutdownFormatBuffer();
gLuaState.deinit();
}
pub fn getState() *lua.LuaState {
return &gLuaState;
}
fn findScriptBasepath(s: []const u8) []const u8 {
var i: usize = s.len - 1;
while (i > 0) : (i -= 1) {
if (s[i] == '\\' or s[i] == '/') {
break;
}
}
if (s[i] == '\\' or s[i] == '/') {
return s[i + 1 ..];
}
return s;
}
pub fn loadTypes(scriptPath: []const u8) !void {
// 1. scan filesystem for all files under the script path
var fileList = try core.fs().listAllSubpaths(gLuaAllocator, scriptPath);
defer fileList.deinit();
for (fileList.data.items, 0..) |f, i| {
// 2. enforce naming scheme for each script.
const basePath = findScriptBasepath(f);
// core.engine_log("checking script file: {s} path:{s} base {s}", .{ f, fileList.sources.items[i], basePath });
// 3. for each one, load the script and assign them based on name.
if (std.ascii.isUpper(basePath[0])) {
core.engine_log("loading script file: {s} path:{s}", .{ f, fileList.sources.items[i] });
const scriptFile = try core.fs().loadFile(f);
defer core.fs().unmap(scriptFile);
try gLuaState.loadString(scriptFile.bytes);
try gLuaState.pcall();
}
}
}
pub fn runScriptFile(scriptPath: []const u8) !void {
const scriptFile = try core.fs().loadFile(scriptPath);
defer core.fs().unmap(scriptFile);
try gLuaState.loadString(scriptFile.bytes);
try gLuaState.pcall();
}

View File

@ -0,0 +1,258 @@
// lwdt can't have meta tables so I'm going to create component references
//
// the way this works is
// - entities are created as POD types
// - entities can get components added to them via entity:addComponent
// - this returns a ComponentReference
// - you can also get components from an entity via enity:get()
// - this also returns a ComponentReference
//
// - ComponentReferences allow you to modify data on a component or call functions on them
//
//
// How the registration works
//
// - define component
// - ecs.zig defineComponent
// - ComponentRef.zig - ReferenceType
// - addComponentRegistration - script.zig
// - ComponentRef - ReferenceType.registerType
// this represents the lua side of the object
pub fn ComponentReferenceType(comptime T: type) type {
return struct {
ptr: *T = undefined,
// used for resolving deltas.
handle: core.ObjectHandle = undefined,
stateCount: u32 = 0,
containerRef: ecs.EcsContainerRef = undefined,
pub const MetatableName = T.ComponentName;
// argc = 1,
// 1. a componentRegistration userdata
// can only be called from entity.luaAddComponent
pub fn luaNew(state: lua.LuaState, handle: core.ObjectHandle, ptr: ?*anyopaque) void {
const ud = state.newZigUserdata(@This()) catch return;
const containerRef = ecs.getTypeContainer(T);
ud.* = .{
.handle = handle,
.containerRef = containerRef,
.stateCount = 0,
};
// std.debug.print("luaNew ComponentReferenceType: {x}\n", .{@intFromPtr(ud)});
// std.debug.print("luaNew ContainerRef: {x}\n", .{@intFromPtr(containerRef.ptr)});
// std.debug.print("luaNew " ++ @typeName(T) ++ " ud.ptr = {x}\n", .{@intFromPtr(ptr)});
if (ptr) |p| {
ud.ptr = @ptrCast(@alignCast(p));
ud.stateCount = containerRef.vtable.getStateCount(containerRef.ptr);
}
if (@hasDecl(@TypeOf(T.BaseContainer.*), "IsMultiset")) {
// ... there are several things that need to be reworked here...
ud.ptr = @ptrCast(@alignCast(@as(*anyopaque, @ptrCast(&ud.handle))));
// std.debug.print("luaNew " ++ @typeName(T) ++ " v = {any}\n", .{ud.ptr.getPosition()});
}
}
pub fn resolve(self: *@This()) void {
const ref = self.containerRef;
// std.debug.print("self: {x}\n", .{@intFromPtr(self)});
// std.debug.print("ptr: {x}\n", .{@intFromPtr(ref.ptr)});
if (@hasDecl(@TypeOf(T.BaseContainer.*), "IsMultiset")) {
//
}
if (ref.vtable.getStateCount(ref.ptr) != self.stateCount) {
self.ptr = @ptrCast(@alignCast(ref.vtable.get(ref.ptr, self.handle)));
}
}
pub fn get(self: *@This()) *T {
self.resolve();
return self.ptr;
}
pub fn luaToString(state: lua.LuaState) i32 {
// oh god... this isn't good
// I think i've been treating this component ref as the user type
// huge failure of type resolution
if (state.toUserdata(@This(), 1)) |self| {
self.resolve();
Buffer.clearRetainingCapacity();
var writer = Buffer.writer();
writer.print("{s}{{", .{MetatableName}) catch return 0;
inline for (std.meta.fields(T), 0..) |field, i| {
if (i == 0) {
writer.print(" {s} = ", .{field.name}) catch return 0;
} else {
writer.print(", {s} = ", .{field.name}) catch return 0;
}
switch (field.type) {
f32, i32, u32, f64, i64, u64 => {
writer.print("{d}", .{@field(self.ptr, field.name)}) catch return 0;
},
[]const u8 => {
writer.print("\"{s}\"", .{@field(self.ptr, field.name)}) catch return 0;
},
else => {
writer.print("<unknown type {s}>", .{@typeName(field.type)}) catch return 0;
},
}
}
writer.print(" }}" ++ "\x00", .{}) catch return 0;
state.pop(1);
state.pushString(Buffer.items) catch return 0;
return 1;
}
return 0;
}
pub fn luaIndex(state: lua.LuaState) i32 {
if (state.toUserdata(@This(), 1)) |self| {
if (state.isString(2)) {
_ = self;
const argument = state.toString(2);
// core.engine_log(@typeName(@This()) ++ " got indexed. 0x{x}", .{self.handle.index});
// check metatable
if (state.getMetafield(1, @ptrCast(argument))) {
return 1;
}
}
}
return 0;
}
fn makeTypeTable() lua.LibSpec {
const methods = blk: {
comptime var m: lua.LibSpec = &.{};
m = m ++ .{lua.luaL_Reg{ .name = "__index", .func = lua.CWrap(luaIndex) }};
m = m ++ .{lua.luaL_Reg{ .name = "__tostring", .func = lua.CWrap(luaToString) }};
inline for (T.ScriptExports) |name| {
m = m ++ .{lua.luaL_Reg{ .name = @as([*c]const u8, @ptrCast(name)), .func = ComponentFuncWrapper(@field(T, name), T) }};
}
break :blk m ++ .{lua.luaL_Reg{ .name = null, .func = null }};
};
return methods;
}
pub fn registerType(state: *lua.LuaState) !void {
core.engine_log("creating lua metatable {s}", .{MetatableName});
const methods = comptime makeTypeTable();
try state.newMetatable(@ptrCast(MetatableName));
try state.setFuncs(methods, 0);
}
};
}
pub fn ComponentFuncWrapper(comptime baseFunc: anytype, comptime baseType: type) lua.LuaCFunc {
return lua.CWrap(FuncWrapper(baseFunc, baseType).wrapper);
}
pub fn FuncWrapper(comptime baseFunc: anytype, comptime baseType: type) type {
return struct {
pub fn wrapper(state: lua.LuaState) i32 {
const Args = std.meta.ArgsTuple(@TypeOf(baseFunc));
var args: Args = undefined;
inline for (std.meta.fields(Args), 0..) |field, index| {
// std.debug.print("typename = {s}\n", .{@typeName(field.type)});
switch (field.type) {
f32 => {
args[index] = @as(f32, @floatCast(state.toNumber(index + 1)));
},
f64 => {
args[index] = state.toNumber(index + 1);
},
i32 => {
args[index] = @intFromFloat(state.toNumber(index + 1));
},
[]const u8 => {
args[index] = state.toString(index + 1);
},
// I'll be honest how the hell does this work?
//
// the pointer being passed in here isn't the actual resulting type...
// it's the reference type
*baseType => {
const ref = state.toUserdata(ComponentReferenceType(baseType), index + 1).?;
ref.resolve();
args[index] = ref.ptr;
},
baseType => {
const ref = state.toUserdata(ComponentReferenceType(baseType), index + 1).?;
ref.resolve();
args[index] = ref.ptr.*;
},
else => {
// if(isComponentType(field.type)) {
// const ref = state.toUserdata(field.type, index + 1).?;
// ref.resolve();
// args[index] = ref.ptr.*;
// continue;
// }
//
lua.debugPrints(true);
args[index] = (state.toUserdata(field.type, index + 1) orelse {
std.debug.print("argument error in index: {d}\n", .{index});
state.emitError("something's weird with this argument\n");
@panic("lmao");
}).*;
lua.debugPrints(false);
},
}
}
state.pop(@intCast(args.len));
//const rv = @call(.always_inline, baseFunc, args);
const rv = @call(.auto, baseFunc, args);
switch (@TypeOf(rv)) {
i32, u32, i64, u64 => {
state.pushNumber(@floatFromInt(rv));
},
f32, f64 => {
state.pushNumber(@floatCast(rv));
},
void => {
return 0;
},
else => {
const ud = state.newZigUserdata(@TypeOf(rv)) catch @panic("not implemented");
ud.* = rv;
},
}
return 1;
}
};
}
var Buffer: std.ArrayList(u8) = undefined;
pub fn setupFormatBuffer(allocator: std.mem.Allocator) !void {
Buffer = std.ArrayList(u8).init(allocator);
}
pub fn shutdownFormatBuffer() void {
Buffer.deinit();
}
const std = @import("std");
const core = @import("../core.zig");
const ecs = @import("../ecs.zig");
const lua = @import("lua");
const pod = lua.pod;
const scene = @import("../scene.zig");

View File

@ -0,0 +1,33 @@
ref: ecs.EcsContainerRef = undefined,
name: []const u8 = undefined,
luaNew: *const fn (state: lua.LuaState, core.ObjectHandle, ?*anyopaque) void = undefined,
pub const PodDataTable: pod.DataTable = .{
.name = "ComponentRegistration",
.banInstantiation = true,
.toStringOverride = lua.CWrap(toString),
};
// I should really move all the registration to this file.
pub fn toString(state: lua.LuaState) i32 {
var workBuffer: [256]u8 = undefined;
const ud = state.toUserdata(@This(), 1).?;
state.pop(1);
const str = std.fmt.bufPrintZ(&workBuffer, "Component Data Table: {s} 0x{x}", .{ ud.name, ud.ref.ptr }) catch return 0;
state.pushString(str) catch return 0;
return 1;
}
pub fn createComponent(self: @This(), handle: core.ObjectHandle) ?*anyopaque {
const ref = self.ref;
const rv = ref.vtable.createWithHandle(ref.ptr, handle);
// std.debug.print("createComponent: {p}\n", .{rv});
return rv;
}
const lua = @import("lua");
const ecs = @import("../ecs.zig");
const std = @import("std");
const core = @import("../core.zig");
const pod = lua.pod;

View File

@ -0,0 +1,59 @@
const luaRegLibs: []const lua.c.luaL_Reg = &.{
.{ .name = "registerTick", .func = lua.CWrap(registerTick) },
.{ .name = null, .func = null },
};
// register core types and subsystems into the scripting engine
pub fn registerTypes() !void {
// transform POD type
const state = script.getState();
try lua.pod.registerPodType(state, core.Vector);
try lua.pod.registerPodType(state, core.Vectorf);
try lua.pod.registerPodType(state, core.Vector2);
try lua.pod.registerPodType(state, core.Vector2f);
// try lua.pod.registerPodType(state, core.Transform);
// lua.pod.registerPodType(state, core.Vector4, "Vector4");
try state.createLibrary("Systems", luaRegLibs);
}
pub fn registerTick(l: lua.LuaState) i32 {
// two arguments first one is going to be userdata entity
// second one is going to be a lua function.
_ = l;
return 0;
}
pub const ScriptTicks = struct {
allocator: std.mem.Allocator,
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "core.ScriptTicks");
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
};
return self;
}
pub fn tick(self: *@This(), deltaTime: f64) void {
_ = self;
const state = script.getState();
_ = state.getGlobal("__TickScripts");
state.pushNumber(deltaTime);
state.pcallStack(1) catch {
core.engine_logs("could not execute lua script");
};
}
pub fn deinit(self: *@This()) void {
self.allocator.destroy(self);
}
};
const lua = @import("lua");
const std = @import("std");
const script = @import("script.zig");
const core = @import("core.zig");

View File

@ -141,7 +141,7 @@ pub const StackCompactor = struct {
var stackCompactor: *core.StackCompactor = undefined;
pub fn initStackCompactor() void {
stackCompactor = core.StackCompactor.create(std.heap.smp_allocator) catch unreachable;
stackCompactor = core.StackCompactor.create(std.heap.c_allocator) catch unreachable;
}
pub inline fn pushCallStack() void {

View File

@ -0,0 +1,18 @@
const std = @import("std");
pub fn dupeZ(comptime T: type, allocator: std.mem.Allocator, source: []const T) ![]T {
var buff: []T = try allocator.alloc(T, source.len + 1);
for (source, 0..source.len) |s, i| {
buff[i] = s;
}
buff[source.len] = 0;
return buff;
}
pub fn dupe(comptime T: type, allocator: std.mem.Allocator, source: []const T) ![]T {
var buff: []T = try allocator.alloc(T, source.len);
for (source, 0..) |s, i| {
buff[i] = s;
}
return buff;
}

View File

@ -0,0 +1,19 @@
const std = @import("std");
pub fn implement_func_for_tagged_union_nonull(
self: anytype,
comptime funcName: []const u8,
comptime returnType: type,
args: anytype,
) returnType {
const Self = @TypeOf(self);
inline for (@typeInfo(std.meta.Tag(Self)).Enum.fields) |field| {
if (@as(std.meta.Tag(Self), @enumFromInt(field.value)) == self) {
if (@hasDecl(@TypeOf(@field(self, field.name)), funcName)) {
return @field(@field(self, field.name), funcName)(args);
}
}
}
unreachable;
}

View File

@ -1,26 +0,0 @@
// this is generated zig code.
pub const SceneAPI = struct {
pub const Api: machinery.StructApi = .{
.fields = &.{
.{
.name = "handle",
.offset = 0,
.alignment = @alignOf(core.ObjectHandle),
.size = @sizeOf(core.ObjectHandle),
},
.{
.name = "testField",
.offset = 4,
.alignment = @alignOf(u32),
.size = @sizeOf(u32),
},
},
};
pub var setPosition: *const fn (*anyopaque, core.Vectorf) void = undefined;
pub const setPositionArgs = std.meta.ArgsTuple(@typeInfo(@TypeOf(setPosition)).pointer.child);
};
const core = @import("core");
const machinery = core.machinery;
const std = @import("std");

View File

@ -1,43 +0,0 @@
pub const Scene = extern struct {
pub const ApiGen = machinery.ComponentApi(@This());
handle: core.ObjectHandle = .{},
testField: u32 = 0,
position: core.Vectorf = .{},
pub fn init(self: *@This()) void {
_ = self;
}
pub fn setPosition(self: *@This(), vector: core.Vectorf) void {
self.position = vector;
core.engine_log("position set: x={d} y={d} z={d}", self.position);
}
pub fn deinit(self: *@This()) void {
_ = self;
}
};
const hand = @import("SceneApiHand.zig");
const SceneAPI = hand.SceneAPI;
pub fn doTest() !void {
machinery.listApi(SceneAPI.Api);
machinery.listApi(Scene.ApiGen.Api);
var scene: Scene = .{};
// from this point on, we're larping like we're a different DLL that
// has no direct reference to the 'Scene' type
// load function pointers
SceneAPI.setPosition = @ptrCast(@alignCast(&Scene.setPosition));
// takes it as an opaque ptr
testmodule.callWithNoDirectLinking(&scene);
}
const testmodule = @import("testmodule.zig");
const core = @import("core");
const machinery = core.machinery;
const std = @import("std");

View File

@ -1,28 +1,20 @@
var gAllocator: std.mem.Allocator = undefined;
pub export fn startup_module(p_allocator: *anyopaque, args: ?*anyopaque) bool {
const allocator = core.modulePreamble(p_allocator, args) catch return false;
_ = args;
gAllocator = @as(*std.mem.Allocator, @ptrCast(@alignCast(p_allocator))).*;
const allocator = gAllocator;
// do some test allocations and let it leak
const t = std.fmt.allocPrint(allocator, "Lmao 2 nova {d}", .{2}) catch unreachable;
const t = std.fmt.allocPrint(gAllocator, "Lmao 2 nova {d}", .{2}) catch unreachable;
std.debug.print("external module started allocated string: {s}\n", .{t});
allocator.free(t);
const subsystem = core.get(SampleSubsystem);
SceneAPI.setPosition = @ptrCast(@alignCast(subsystem.setPositionPtr));
const a: SceneAPI.setPositionArgs = .{ subsystem.scene, core.Vectorf{ .y = 12, .z = 13 } };
const argsAsBytes = std.mem.asBytes(&a);
machinery.callInner(SceneAPI.setPosition, argsAsBytes);
return true;
}
pub export fn shutdown_module() void {}
const hand = @import("SceneApiHand.zig");
const SceneAPI = hand.SceneAPI;
const SampleSubsystem = @import("samplesubsystem.zig");
const core = @import("core");
const std = @import("std");
const machinery = core.machinery;

View File

@ -1,19 +0,0 @@
pub var NeonObjectTable: core.EngineObjectVTable = core.EngineObjectVTable.from(@This(), "testing.sampleSubsystem");
allocator: std.mem.Allocator = undefined,
scene: *anyopaque = undefined,
setPositionPtr: *const anyopaque = undefined,
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first) {
return;
}
self.* = .{
.allocator = allocator,
};
}
const exampleScene = @import("exampleScene.zig");
const core = @import("core");
const std = @import("std");

View File

@ -1,12 +0,0 @@
pub fn callWithNoDirectLinking(scene: *anyopaque) void {
const args: SceneAPI.setPositionArgs = .{ scene, core.Vectorf{} };
const argsAsBytes = std.mem.asBytes(&args);
machinery.callInner(SceneAPI.setPosition, argsAsBytes);
}
const hand = @import("SceneApiHand.zig");
const SceneAPI = hand.SceneAPI;
const core = @import("core");
const machinery = core.machinery;
const std = @import("std");

View File

@ -5,9 +5,6 @@ const memory = core.MemoryTracker;
const engine_log = core.engine_log;
const engine_logs = core.engine_logs;
const exampleScene = @import("exampleScene.zig");
const SampleSubsystem = @import("samplesubsystem.zig");
test "simple systems setup for core" {
std.testing.refAllDecls(core.algorithm);
@ -25,17 +22,7 @@ test "simple systems setup for core" {
try core.start_module(&map, .{ .unitTest = true }, allocator);
defer core.shutdown_module(allocator);
const subsystem = try core.createObject(SampleSubsystem, .{});
const scene = try allocator.create(exampleScene.Scene);
defer allocator.destroy(scene);
subsystem.scene = scene;
subsystem.setPositionPtr = @ptrCast(&exampleScene.Scene.setPosition);
engine_logs("systems started, shutting down");
engine_logs("this is a new print added to the test function");
engine_logs("this is a new print added to the test function");
memory.MTPrintStatsDelta();
try test_loadingConfigs();
@ -196,11 +183,7 @@ fn test_gameObjects(allocator: std.mem.Allocator) !void {
const spawnFunc = core.GameObjectList.spawnObjectFunction(GameObject);
const interface = spawnFunc(objList).?;
core.engine_log("lol", .{});
core.engine_log("handle = {x}", .{interface.vtable.getEntity.?(interface.ptr).handle.index});
objList.tick(0.016);
try exampleScene.doTest();
}

View File

@ -15,13 +15,14 @@ pub const Impl = struct {
};
// renderer plugin
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first)
return;
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
};
return self;
}
const ImguiArgs = struct { imguiIni: []const u8 = "imgui.ini" };
@ -108,6 +109,11 @@ pub const Impl = struct {
_ = ig.dockSpaceOverViewport(ig.getMainViewport(), .{ .passthru_central_node = true }, null);
_ = dt;
}
// renderer plugin
pub fn destroy(self: *@This()) void {
self.allocator.destroy(self);
}
};
const c = @import("cimgui").c;
const ig = @import("cimgui");

View File

@ -1,4 +1,5 @@
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "game.TopBar");
pub const Slack = core.SlackStruct(@This(), 256);
allocator: std.mem.Allocator,
arena: std.heap.ArenaAllocator,
@ -6,10 +7,8 @@ windowsMenu: std.ArrayListUnmanaged(*MenuEntry) = .{},
entriesByName: std.AutoHashMapUnmanaged(u32, *MenuEntry) = .{},
menuOpen: bool = false,
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first)
return;
pub fn create(allocator: std.mem.Allocator) !*@This() {
const self = try Slack.create(allocator);
self.* = .{
.allocator = allocator,
.arena = std.heap.ArenaAllocator.init(allocator),
@ -22,6 +21,8 @@ pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
.ctx = self,
.windowFunction = windowOpen,
});
return self;
}
pub fn setEntryOpen(self: *@This(), name: []const u8, open: ?bool) void {
@ -107,6 +108,7 @@ pub fn tick(self: *@This(), dt: f64) void {
pub fn destroy(self: *@This()) void {
self.arena.deinit();
self.allocator.destroy(Slack.fromPtr(self));
}
pub const MenuEntry = struct {

View File

@ -6,16 +6,16 @@ consolePressedEnter: bool = false,
pub var NeonObjectTable = core.EngineObjectVTable.from(@This(), "extras.consoleWindow");
pub fn init(self: *@This(), allocator: std.mem.Allocator, first: bool) !void {
if (!first)
return;
pub fn init(allocator: std.mem.Allocator) !*@This() {
const self = try allocator.create(@This());
self.* = .{
.allocator = allocator,
.buffer = core.logging.LogBuffer.init(allocator),
};
self.setup();
return self;
}
pub fn setup(self: *@This()) void {
@ -65,6 +65,7 @@ pub fn windowOpen(entry: *imgui.utils.MenuEntry, dt: f64) void {
pub fn destroy(self: *@This()) void {
self.buffer.deinit();
self.allocator.destroy(self);
}
const imgui = @import("../imgui.zig");

View File

@ -46,7 +46,7 @@ pub fn main() !void {
core.engine_log("Working Dir set: {s}", .{try std.fs.cwd().realpath(".", &BUFFER)});
}
// panickers.attachSegfaultHandler();
panickers.attachSegfaultHandler();
try realMain.main();
}

View File

@ -1,161 +0,0 @@
const std = @import("std");
const core = @import("core").module;
const panickers = core.panickers;
// const realMain = @import("main");
pub const gamespec = @import("gamespec");
pub const options = @import("BacklogOptions");
pub const std_options = std.Options{
.enable_segfault_handler = true,
};
//pub const build_options = @import("build_options");
//pub const tracy_enabled = build_options.tracy_enabled;
// pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, x: ?usize) noreturn {
// core.forceFlush();
// core.script.lua.takeDump();
// std.debug.defaultPanic(error_return_trace, x, msg);
// }
pub const NwArgs = struct {
useGPA: bool = false, // slower zig based memory allocator, provides detailed tracking of leaks and memory violations
vulkanValidation: bool = true,
fastTest: bool = false,
dmt: bool = false, // detailed memory tracking, implements a timeline for tracking all memory allocations
fatDump: bool = false, // takes a full fat minidump on crash, very large files are produced
};
pub fn getArgs() !NwArgs {
const a = try core.ParseArgs(NwArgs);
return a;
}
fn fileExists(path: []const u8) bool {
std.fs.cwd().access(path, .{}) catch return false;
return true;
}
pub fn run() !void {
core.engine_logs("calling gEngine.run");
try core.getEngine().run();
while (!core.getEngine().exitFinished()) {
const z = core.tracy.ZoneN(@src(), "shutdown poll");
z.End();
}
}
pub fn startEngine(spec: *core.SpecVariantMap) bool {
const args = getArgs() catch return false;
var backingAllocator: std.mem.Allocator = std.heap.smp_allocator;
var gpa: std.heap.GeneralPurposeAllocator(.{
.stack_trace_frames = 20,
}) = .{};
defer {
const cleanupStatus = gpa.deinit();
if (cleanupStatus == .leak) {
std.debug.print("gpa cleanup leaked memory\n", .{});
}
}
if (spec.get("useGPA")) |arg| {
if (arg.boolean == true) {
backingAllocator = gpa.allocator();
}
}
const memory = core.MemoryTracker;
memory.MTSetup(backingAllocator, .{ .timeline = args.dmt });
defer memory.MTShutdown();
var tracker = memory.MTGet().?;
const allocator = tracker.allocator();
_ = core.createNameRegistry(allocator) catch return false;
core.maybeInitPackerFs(allocator) catch return false;
if (comptime core.BuildOption("static_build")) {
core.engine_log("static build, using embedded shaders", .{});
const loader = @import("staticShaderLoader");
loader.installStaticResources() catch return false;
}
core.start_module(spec, args, allocator) catch return false;
const moduleName = spec.get("moduleName").?.string;
if (comptime core.BuildOption("static_build")) {
core.engine_logs("static build");
const modlaunch = @import("modulelaunch.zig");
var modargs = core.externModule.getModuleLoaderArgs(true);
var modalloc = allocator;
_ = modlaunch.startup_module(&modalloc, &modargs);
} else {
core.engine_logs("using hot reloading");
const platform = @import("platform").module;
platform.watchModules();
//if (comptime @hasDecl(backlog, "platform")) {
//backlog.platform.watchModules();
// }
core.loadModule(moduleName, true) catch return false;
}
// load the shared object and call startup()
// core.beginLoading("");
// if (!start_modules(spec, args, allocator)) return false;
// defer shutdown_modules(allocator);
run() catch return false;
if (core.getEngine().shutdownModuleFunction) |shutdownFunc| {
shutdownFunc();
} else {
core.shutdown_module(allocator);
}
return true;
}
pub fn main() !void {
if (!core.BuildOption("RootDeploymentOnly")) {
// if we don't see a content/ folder or a
// content.pak file in the current directory,
// walk up the directory tree until we see one, then change dirs to that before doing anything else
var iterations: u32 = 0;
var BUFFER: [8192]u8 = undefined;
while (iterations < 8) : (iterations += 1) {
if (fileExists("content") or fileExists("content.pak")) {
break;
}
var dir = try std.fs.cwd().openDir("..", .{});
defer dir.close();
core.engine_log("content not found scanning dir {s}", .{try dir.realpath(".", &BUFFER)});
try dir.setAsCwd();
}
core.engine_log("Working Dir set: {s}", .{try std.fs.cwd().realpath(".", &BUFFER)});
}
const spec = try gamespec.getSpec();
_ = startEngine(spec);
// panickers.attachSegfaultHandler();
// try realMain.main();
shutdown_hook();
}
pub fn shutdown_hook() void {
std.debug.print("[engine] shutting down now! goodbye!", .{});
}

Some files were not shown because too many files have changed in this diff Show More