Convert project into Zig package and add demos (#55)
continuous-integration/drone/push Build is failing
Details
continuous-integration/drone/push Build is failing
Details
This commit is contained in:
parent
9323cad130
commit
6ec24c765c
|
@ -1 +1,2 @@
|
||||||
*.bmp filter=lfs diff=lfs merge=lfs -text
|
*.bmp filter=lfs diff=lfs merge=lfs -text
|
||||||
|
*.spv filter=lfs diff=lfs merge=lfs -text
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
# Generated assets
|
# Generated assets
|
||||||
.zig-cache
|
.zig-cache
|
||||||
/zig-out
|
/zig-out
|
||||||
*.spv
|
/src/**/*.spv
|
||||||
|
/demos/**/*.out
|
||||||
|
/demos/**/*.exe
|
||||||
|
|
|
@ -5,8 +5,8 @@
|
||||||
"name": "Runner",
|
"name": "Runner",
|
||||||
"type": "gdb",
|
"type": "gdb",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"target": "${workspaceRoot}/zig-out/bin/main",
|
"target": "${workspaceRoot}/demos/effects.out",
|
||||||
"cwd": "${workspaceRoot}/debug/",
|
"cwd": "${workspaceRoot}/demos/",
|
||||||
"valuesFormatting": "prettyPrinters",
|
"valuesFormatting": "prettyPrinters",
|
||||||
"preLaunchTask": "Build All"
|
"preLaunchTask": "Build All"
|
||||||
},
|
},
|
||||||
|
|
384
build.zig
384
build.zig
|
@ -2,44 +2,215 @@ const builtin = @import("builtin");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
const ImportList = std.ArrayList(struct {
|
||||||
|
name: []const u8,
|
||||||
|
module: *std.Build.Module
|
||||||
|
});
|
||||||
|
|
||||||
|
const Project = struct {
|
||||||
|
target: std.Build.ResolvedTarget,
|
||||||
|
optimize: std.builtin.OptimizeMode,
|
||||||
|
imports: ImportList,
|
||||||
|
|
||||||
|
pub fn find_demos(self: Project, b: *std.Build) !void {
|
||||||
|
const demos = b.step("demos", "Build demos");
|
||||||
|
|
||||||
|
var dir = try std.fs.cwd().openDir("demos/", .{
|
||||||
|
.iterate = true,
|
||||||
|
});
|
||||||
|
|
||||||
|
defer {
|
||||||
|
dir.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
var entries = try dir.walk(b.allocator);
|
||||||
|
|
||||||
|
defer {
|
||||||
|
entries.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (try entries.next()) |entry| {
|
||||||
|
if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const source_path = try sub_path(.{"demos", entry.basename});
|
||||||
|
var path_buffer = [_:0]u8{0} ** 255;
|
||||||
|
|
||||||
|
const demo = b.addExecutable(.{
|
||||||
|
.name = try std.fmt.bufPrint(&path_buffer, "{s}.out", .{std.fs.path.stem(entry.basename)}),
|
||||||
|
.root_source_file = b.path(source_path.bytes()),
|
||||||
|
.target = self.target,
|
||||||
|
.optimize = self.optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
for (self.imports.items) |import| {
|
||||||
|
demo.root_module.addImport(import.name, import.module);
|
||||||
|
}
|
||||||
|
|
||||||
|
demos.dependOn(&b.addInstallArtifact(demo, .{
|
||||||
|
.dest_dir = .{
|
||||||
|
.override = .{
|
||||||
|
.custom = "../demos/",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_tests(self: Project, b: *std.Build) !void {
|
||||||
|
const tests = b.step("test", "Build and run tests");
|
||||||
|
|
||||||
|
for (self.imports.items) |import| {
|
||||||
|
tests.dependOn(&b.addRunArtifact(b.addTest(.{
|
||||||
|
.root_source_file = import.module.root_source_file.?,
|
||||||
|
.target = self.target,
|
||||||
|
.optimize = self.optimize,
|
||||||
|
})).step);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_module(self: *Project, b: *std.Build, comptime name: []const u8, options: std.Build.Module.CreateOptions) !*std.Build.Module {
|
||||||
|
const cwd = std.fs.cwd();
|
||||||
|
var corrected_options = options;
|
||||||
|
|
||||||
|
if (corrected_options.root_source_file == null) {
|
||||||
|
corrected_options.root_source_file = b.path("src/" ++ name ++ "/" ++ name ++ ".zig");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (corrected_options.target == null) {
|
||||||
|
corrected_options.target = self.target;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (corrected_options.optimize == null) {
|
||||||
|
corrected_options.optimize = self.optimize;
|
||||||
|
}
|
||||||
|
|
||||||
|
const module = b.addModule(name, corrected_options);
|
||||||
|
|
||||||
|
try self.imports.append(.{
|
||||||
|
.name = name,
|
||||||
|
.module = module
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: Probably want to make it search the same path as any explicit root_source_path override for shaders.
|
||||||
|
const shaders_path = "src/" ++ name ++ "/shaders/";
|
||||||
|
|
||||||
|
var shaders_dir = cwd.openDir(shaders_path, .{.iterate = true}) catch |open_error| {
|
||||||
|
return switch (open_error) {
|
||||||
|
error.FileNotFound, error.NotDir => module,
|
||||||
|
else => open_error,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
defer {
|
||||||
|
shaders_dir.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
var shaders_entries = try shaders_dir.walk(b.allocator);
|
||||||
|
|
||||||
|
defer {
|
||||||
|
shaders_entries.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (try shaders_entries.next()) |entry| {
|
||||||
|
if (entry.kind != .file) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
|
||||||
|
|
||||||
|
if (!is_shader_file) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var binary_buffer = [_:0]u8{0} ** 255;
|
||||||
|
const binary_name = try std.fmt.bufPrint(&binary_buffer, "{s}.spv", .{entry.path});
|
||||||
|
const full_source_path = try sub_path(.{shaders_path, entry.path});
|
||||||
|
const full_binary_path = try sub_path(.{shaders_path, binary_name});
|
||||||
|
|
||||||
|
const glslang_validator_args = [_][]const u8{
|
||||||
|
"glslangValidator",
|
||||||
|
"-V",
|
||||||
|
full_source_path.bytes(),
|
||||||
|
"-o",
|
||||||
|
full_binary_path.bytes(),
|
||||||
|
};
|
||||||
|
|
||||||
|
shaders_dir.access(binary_name, .{.mode = .read_only}) catch {
|
||||||
|
const output = b.run(&glslang_validator_args);
|
||||||
|
|
||||||
|
std.log.info("{s}", .{output[0 .. output.len - 1]});
|
||||||
|
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if ((try shaders_dir.statFile(entry.path)).mtime > (try shaders_dir.statFile(binary_name)).mtime) {
|
||||||
|
const output = b.run(&glslang_validator_args);
|
||||||
|
|
||||||
|
std.log.info("{s}", .{output[0 .. output.len - 1]});
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return module;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const SubPath = struct {
|
const SubPath = struct {
|
||||||
buffer: [max]u8 = [_]u8{0} ** max,
|
buffer: [max]u8 = [_]u8{0} ** max,
|
||||||
unused: u8 = max,
|
unused: u8 = max,
|
||||||
|
|
||||||
const max = 255;
|
pub const max = 255;
|
||||||
|
|
||||||
pub fn utf8(self: *const SubPath) [:0]const u8 {
|
pub fn append(self: *SubPath, component: []const u8) !void {
|
||||||
return self.buffer[0 .. (max - self.unused):0];
|
const used = max - self.unused;
|
||||||
|
|
||||||
|
if (used != 0 and self.buffer[used - 1] != '/') {
|
||||||
|
if (component.len > self.unused) {
|
||||||
|
return error.PathTooBig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@memcpy(self.buffer[used .. (used + component.len)], component);
|
||||||
|
|
||||||
|
self.unused -= @intCast(component.len);
|
||||||
|
} else {
|
||||||
|
const required_len = component.len + 1;
|
||||||
|
|
||||||
|
if (required_len > self.unused) {
|
||||||
|
return error.PathTooBig;
|
||||||
|
}
|
||||||
|
|
||||||
|
@memcpy(self.buffer[used .. (used + component.len)], component);
|
||||||
|
|
||||||
|
self.buffer[component.len] = '/';
|
||||||
|
|
||||||
|
self.unused -= @intCast(required_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bytes(self: *const SubPath) [:0]const u8 {
|
||||||
|
return @ptrCast(self.buffer[0 .. max - self.unused]);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn build(b: *std.Build) !void {
|
pub fn build(b: *std.Build) !void {
|
||||||
const target = b.standardTargetOptions(.{});
|
var project = Project{
|
||||||
const optimize = b.standardOptimizeOption(.{});
|
.imports = ImportList.init(b.allocator),
|
||||||
|
.target = b.standardTargetOptions(.{}),
|
||||||
|
.optimize = b.standardOptimizeOption(.{}),
|
||||||
|
};
|
||||||
|
|
||||||
const sokol_dependency = b.dependency("sokol", .{
|
const sokol_dependency = b.dependency("sokol", .{
|
||||||
.target = target,
|
.target = project.target,
|
||||||
.optimize = optimize,
|
.optimize = project.optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
const coral_module = b.createModule(.{
|
const ona_module = try project.add_module(b, "ona", .{});
|
||||||
.root_source_file = b.path("src/coral/coral.zig"),
|
const input_module = try project.add_module(b, "input", .{});
|
||||||
});
|
|
||||||
|
|
||||||
const flow_module = b.createModule(.{
|
|
||||||
.root_source_file = b.path("src/flow/flow.zig"),
|
|
||||||
|
|
||||||
.imports = &.{
|
|
||||||
.{
|
|
||||||
.name = "coral",
|
|
||||||
.module = coral_module,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const ona_module = b.createModule(.{
|
|
||||||
.root_source_file = b.path("src/ona/ona.zig"),
|
|
||||||
|
|
||||||
|
const coral_module = try project.add_module(b, "coral", .{
|
||||||
.imports = &.{
|
.imports = &.{
|
||||||
.{
|
.{
|
||||||
.name = "sokol",
|
.name = "sokol",
|
||||||
|
@ -47,20 +218,20 @@ pub fn build(b: *std.Build) !void {
|
||||||
},
|
},
|
||||||
|
|
||||||
.{
|
.{
|
||||||
.name = "coral",
|
.name = "ona",
|
||||||
.module = coral_module,
|
.module = ona_module,
|
||||||
},
|
},
|
||||||
|
|
||||||
.{
|
.{
|
||||||
.name = "flow",
|
.name = "input",
|
||||||
.module = flow_module,
|
.module = input_module,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
ona_module.addIncludePath(b.path("ext/"));
|
coral_module.addIncludePath(b.path("ext/"));
|
||||||
|
|
||||||
ona_module.linkLibrary(spirv_cross: {
|
coral_module.linkLibrary(spirv_cross: {
|
||||||
const dir = "ext/spirv-cross/";
|
const dir = "ext/spirv-cross/";
|
||||||
|
|
||||||
const sources = [_][]const u8{
|
const sources = [_][]const u8{
|
||||||
|
@ -78,8 +249,8 @@ pub fn build(b: *std.Build) !void {
|
||||||
|
|
||||||
const lib = b.addStaticLibrary(.{
|
const lib = b.addStaticLibrary(.{
|
||||||
.name = "spirvcross",
|
.name = "spirvcross",
|
||||||
.target = target,
|
.target = project.target,
|
||||||
.optimize = optimize,
|
.optimize = project.optimize,
|
||||||
});
|
});
|
||||||
|
|
||||||
switch (lib.rootModuleTarget().abi) {
|
switch (lib.rootModuleTarget().abi) {
|
||||||
|
@ -90,135 +261,62 @@ pub fn build(b: *std.Build) !void {
|
||||||
inline for (sources) |src| {
|
inline for (sources) |src| {
|
||||||
lib.addCSourceFile(.{
|
lib.addCSourceFile(.{
|
||||||
.file = b.path(dir ++ src),
|
.file = b.path(dir ++ src),
|
||||||
.flags = &.{"-fstrict-aliasing", "-DSPIRV_CROSS_C_API_GLSL", "-DSPIRV_CROSS_C_API_HLSL", "-DSPIRV_CROSS_C_API_MSL"},
|
|
||||||
|
.flags = &.{
|
||||||
|
"-fstrict-aliasing",
|
||||||
|
"-DSPIRV_CROSS_C_API_GLSL",
|
||||||
|
"-DSPIRV_CROSS_C_API_HLSL",
|
||||||
|
"-DSPIRV_CROSS_C_API_MSL",
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
break: spirv_cross lib;
|
break: spirv_cross lib;
|
||||||
});
|
});
|
||||||
|
|
||||||
b.step("test", "Run unit tests").dependOn(tests: {
|
coral_module.linkSystemLibrary("SDL2", .{
|
||||||
const tests = b.addTest(.{
|
.needed = true,
|
||||||
.root_source_file = b.path("src/main.zig"),
|
.preferred_link_mode = .dynamic,
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
|
|
||||||
break: tests &tests.step;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
b.installArtifact(add: {
|
coral_module.link_libc = true;
|
||||||
const exe = b.addExecutable(.{
|
|
||||||
.name = "main",
|
|
||||||
.root_source_file = b.path("src/main.zig"),
|
|
||||||
.target = target,
|
|
||||||
.optimize = optimize,
|
|
||||||
});
|
|
||||||
|
|
||||||
exe.root_module.addImport("ona", ona_module);
|
try project.find_tests(b);
|
||||||
exe.root_module.addImport("coral", coral_module);
|
try project.find_demos(b);
|
||||||
exe.linkLibC();
|
}
|
||||||
exe.linkSystemLibrary("SDL2");
|
|
||||||
|
|
||||||
const shaders_sub_path = "src/ona/gfx/shaders/";
|
fn sub_path(components: anytype) !SubPath {
|
||||||
|
var path = comptime try std.BoundedArray(u8, SubPath.max).init(0);
|
||||||
|
const Components = @TypeOf(components);
|
||||||
|
|
||||||
var shaders_dir = try std.fs.cwd().openDir(shaders_sub_path, .{
|
switch (@typeInfo(Components)) {
|
||||||
.iterate = true,
|
.Struct => |@"struct"| {
|
||||||
});
|
if (!@"struct".is_tuple) {
|
||||||
|
@compileError("`components` must be a tuple");
|
||||||
defer shaders_dir.close();
|
|
||||||
|
|
||||||
var shaders_walker = try shaders_dir.walk(b.allocator);
|
|
||||||
|
|
||||||
defer shaders_walker.deinit();
|
|
||||||
|
|
||||||
const Shader = struct {
|
|
||||||
source_sub_path: SubPath = .{},
|
|
||||||
binary_sub_path: SubPath = .{},
|
|
||||||
};
|
|
||||||
|
|
||||||
var pending_shaders = std.ArrayList(Shader).init(b.allocator);
|
|
||||||
|
|
||||||
defer pending_shaders.deinit();
|
|
||||||
|
|
||||||
scan_shaders: while (try shaders_walker.next()) |entry| {
|
|
||||||
if (entry.kind != .file) {
|
|
||||||
continue: scan_shaders;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
|
const last_component_index = components.len - 1;
|
||||||
|
|
||||||
if (!is_shader_file) {
|
inline for (components, 0 .. components.len) |component, i| {
|
||||||
continue: scan_shaders;
|
path.appendSlice(component) catch {
|
||||||
}
|
return error.PathTooBig;
|
||||||
|
};
|
||||||
|
|
||||||
const shader_name = std.fs.path.stem(entry.path);
|
if (i < last_component_index and !std.mem.endsWith(u8, component, "/")) {
|
||||||
|
path.append('/') catch {
|
||||||
for (pending_shaders.items) |pending_shader| {
|
return error.PathTooBig;
|
||||||
if (std.mem.endsWith(u8, pending_shader.source_sub_path.utf8(), shader_name)) {
|
};
|
||||||
continue: scan_shaders;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
var shader = Shader{};
|
else => {
|
||||||
const source_sub_path = try std.fmt.bufPrint(&shader.source_sub_path.buffer, "{s}{s}", .{shaders_sub_path, shader_name});
|
@compileError("`components` cannot be a " ++ @typeName(Components));
|
||||||
const binary_sub_path = try std.fmt.bufPrint(&shader.binary_sub_path.buffer, "{s}.spv", .{source_sub_path});
|
|
||||||
|
|
||||||
shaders_dir.access(std.fs.path.basename(binary_sub_path), .{.mode = .read_only}) catch {
|
|
||||||
shader.source_sub_path.unused -= @intCast(source_sub_path.len);
|
|
||||||
shader.binary_sub_path.unused -= @intCast(binary_sub_path.len);
|
|
||||||
|
|
||||||
try pending_shaders.append(shader);
|
|
||||||
|
|
||||||
continue: scan_shaders;
|
|
||||||
};
|
|
||||||
|
|
||||||
if ((try shaders_dir.statFile(entry.basename)).mtime > (try shaders_dir.statFile(std.fs.path.basename(binary_sub_path))).mtime) {
|
|
||||||
shader.source_sub_path.unused -= @intCast(source_sub_path.len);
|
|
||||||
shader.binary_sub_path.unused -= @intCast(binary_sub_path.len);
|
|
||||||
|
|
||||||
try pending_shaders.append(shader);
|
|
||||||
|
|
||||||
continue: scan_shaders;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (pending_shaders.items) |pending_shader| {
|
return .{
|
||||||
var vertex_binary_sub_path = SubPath{};
|
.unused = SubPath.max - path.len,
|
||||||
var fragment_binary_sub_path = SubPath{};
|
.buffer = path.buffer,
|
||||||
const source_sub_path_utf8 = pending_shader.source_sub_path.utf8();
|
};
|
||||||
|
|
||||||
vertex_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&vertex_binary_sub_path.buffer, "{s}.vert.spv", .{source_sub_path_utf8})).len);
|
|
||||||
fragment_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&fragment_binary_sub_path.buffer, "{s}.frag.spv", .{source_sub_path_utf8})).len);
|
|
||||||
|
|
||||||
const vertex_binary_sub_path_utf8 = vertex_binary_sub_path.utf8();
|
|
||||||
const fragment_binary_sub_path_utf8 = fragment_binary_sub_path.utf8();
|
|
||||||
|
|
||||||
exe.step.dependOn(compile_vertex: {
|
|
||||||
const compile_command = b.addSystemCommand(&.{
|
|
||||||
"glslangValidator",
|
|
||||||
"-V",
|
|
||||||
vertex_binary_sub_path_utf8[0 .. vertex_binary_sub_path_utf8.len - 4],
|
|
||||||
"-o",
|
|
||||||
vertex_binary_sub_path_utf8,
|
|
||||||
});
|
|
||||||
|
|
||||||
break: compile_vertex &compile_command.step;
|
|
||||||
});
|
|
||||||
|
|
||||||
exe.step.dependOn(compile_fragment: {
|
|
||||||
const compile_command = b.addSystemCommand(&.{
|
|
||||||
"glslangValidator",
|
|
||||||
"-V",
|
|
||||||
fragment_binary_sub_path_utf8[0 .. fragment_binary_sub_path_utf8.len - 4],
|
|
||||||
"-o",
|
|
||||||
fragment_binary_sub_path_utf8,
|
|
||||||
});
|
|
||||||
|
|
||||||
break: compile_fragment &compile_command.step;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
break: add exe;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
.{
|
.{
|
||||||
.name = "Ona",
|
.name = "ona",
|
||||||
.version = "0.0.1",
|
.version = "0.0.1",
|
||||||
|
.minimum_zig_version = "0.13.0",
|
||||||
|
|
||||||
.paths = .{
|
.paths = .{
|
||||||
"src",
|
"src",
|
||||||
"build.zig",
|
"build.zig",
|
||||||
|
@ -8,10 +10,15 @@
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"README.md",
|
"README.md",
|
||||||
},
|
},
|
||||||
|
|
||||||
.dependencies = .{
|
.dependencies = .{
|
||||||
.sokol = .{
|
.sokol = .{
|
||||||
.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
|
.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
|
||||||
.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
|
.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.@"spirv-cross" = . {
|
||||||
|
.path = "./ext/spirv-cross",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
BIN
debug/actor.bmp (Stored with Git LFS)
BIN
debug/actor.bmp (Stored with Git LFS)
Binary file not shown.
|
@ -1,25 +0,0 @@
|
||||||
#version 430
|
|
||||||
|
|
||||||
layout (binding = 0) uniform sampler2D sprite;
|
|
||||||
|
|
||||||
layout (location = 0) in vec4 color;
|
|
||||||
layout (location = 1) in vec2 uv;
|
|
||||||
|
|
||||||
layout (location = 0) out vec4 texel;
|
|
||||||
|
|
||||||
layout (binding = 0) uniform Effect {
|
|
||||||
float effect_magnitude;
|
|
||||||
};
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 color1 = texture(sprite, uv) / 3.0;
|
|
||||||
vec4 color2 = texture(sprite, uv + 0.002 * effect_magnitude) / 3.0;
|
|
||||||
vec4 color3 = texture(sprite, uv - 0.002 * effect_magnitude) / 3.0;
|
|
||||||
|
|
||||||
color1 *= 2.0;
|
|
||||||
color2.g = 0.0;
|
|
||||||
color2.b = 0.0;
|
|
||||||
color3.r = 0.0;
|
|
||||||
|
|
||||||
texel = color * (color1 + color2 + color3);
|
|
||||||
}
|
|
|
@ -1,5 +1,7 @@
|
||||||
#version 430
|
#version 430
|
||||||
|
|
||||||
|
// Adapted from: https://www.shadertoy.com/view/4sf3Dr
|
||||||
|
|
||||||
layout (binding = 0) uniform sampler2D sprite;
|
layout (binding = 0) uniform sampler2D sprite;
|
||||||
|
|
||||||
layout (location = 0) in vec4 color;
|
layout (location = 0) in vec4 color;
|
||||||
|
@ -13,14 +15,12 @@ layout (binding = 0) uniform Effect {
|
||||||
float time;
|
float time;
|
||||||
};
|
};
|
||||||
|
|
||||||
vec3 scanline(vec2 coord, vec3 screen)
|
vec3 scanline(vec2 coord, vec3 screen) {
|
||||||
{
|
|
||||||
screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02;
|
screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02;
|
||||||
return screen;
|
return screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
vec2 crt(vec2 coord, float bend)
|
vec2 crt(vec2 coord, float bend) {
|
||||||
{
|
|
||||||
// put in symmetrical coords
|
// put in symmetrical coords
|
||||||
coord = (coord - 0.5) * 2.0;
|
coord = (coord - 0.5) * 2.0;
|
||||||
|
|
||||||
|
@ -36,17 +36,13 @@ vec2 crt(vec2 coord, float bend)
|
||||||
return coord;
|
return coord;
|
||||||
}
|
}
|
||||||
|
|
||||||
void main()
|
void main() {
|
||||||
{
|
|
||||||
vec2 crtCoords = crt(uv, 4.8);
|
vec2 crtCoords = crt(uv, 4.8);
|
||||||
|
|
||||||
// Split the color channels
|
// Split the color channels
|
||||||
texel.rgb = texture(sprite, crtCoords).rgb;
|
texel.rgb = texture(sprite, crtCoords).rgb;
|
||||||
texel.a = 1;
|
texel.a = 1;
|
||||||
|
|
||||||
// HACK: this bend produces a shitty moire pattern.
|
|
||||||
// Up the bend for the scanline
|
|
||||||
vec2 screenSpace = crtCoords * vec2(screen_width, screen_height);
|
vec2 screenSpace = crtCoords * vec2(screen_width, screen_height);
|
||||||
texel.rgb = scanline(screenSpace, texel.rgb);
|
texel.rgb = scanline(screenSpace, texel.rgb);
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,92 @@
|
||||||
|
const coral = @import("coral");
|
||||||
|
|
||||||
|
const ona = @import("ona");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const CRT = extern struct {
|
||||||
|
width: f32,
|
||||||
|
height: f32,
|
||||||
|
time: f32,
|
||||||
|
padding: [4]u8 = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const Effects = struct {
|
||||||
|
render_texture: coral.Texture = .default,
|
||||||
|
crt_effect: coral.Effect = .default,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const main = ona.start(setup, .{
|
||||||
|
.tick_rate = 60,
|
||||||
|
.execution = .{.thread_share = 0.1},
|
||||||
|
.middleware = &.{coral.setup},
|
||||||
|
});
|
||||||
|
|
||||||
|
fn load(display: ona.Write(coral.Display), actors: ona.Write(Effects), assets: ona.Write(coral.Assets)) !void {
|
||||||
|
display.state.width, display.state.height = .{1280, 720};
|
||||||
|
|
||||||
|
actors.state.render_texture = try assets.state.load_texture(.{
|
||||||
|
.format = .rgba8,
|
||||||
|
|
||||||
|
.access = .{
|
||||||
|
.render = .{
|
||||||
|
.width = display.state.width,
|
||||||
|
.height = display.state.height,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
actors.state.crt_effect = try assets.state.load_effect_file(ona.files.bundle, "./crt.frag.spv");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(commands: coral.Commands, effects: ona.Write(Effects), app: ona.Read(ona.App), display: ona.Write(coral.Display)) !void {
|
||||||
|
try commands.set_target(.{
|
||||||
|
.texture = effects.state.render_texture,
|
||||||
|
.clear_color = coral.colors.black,
|
||||||
|
.clear_depth = 0,
|
||||||
|
.clear_stencil = 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const display_width: f32 = @floatFromInt(display.state.width);
|
||||||
|
const display_height: f32 = @floatFromInt(display.state.height);
|
||||||
|
|
||||||
|
const display_transform = coral.Transform2D{
|
||||||
|
.origin = .{display_width / 2, display_height / 2},
|
||||||
|
.xbasis = .{display_width, 0},
|
||||||
|
.ybasis = .{0, display_height},
|
||||||
|
};
|
||||||
|
|
||||||
|
try commands.draw_texture(.{
|
||||||
|
.texture = .default,
|
||||||
|
.transform = display_transform,
|
||||||
|
});
|
||||||
|
|
||||||
|
try commands.set_effect(.{
|
||||||
|
.effect = effects.state.crt_effect,
|
||||||
|
|
||||||
|
.properties = std.mem.asBytes(&CRT{
|
||||||
|
.width = display_width,
|
||||||
|
.height = display_height,
|
||||||
|
.time = @floatCast(app.state.elapsed_time),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
try commands.set_target(.{
|
||||||
|
.texture = .backbuffer,
|
||||||
|
.clear_color = null,
|
||||||
|
.clear_depth = null,
|
||||||
|
.clear_stencil = null,
|
||||||
|
});
|
||||||
|
|
||||||
|
try commands.draw_texture(.{
|
||||||
|
.texture = effects.state.render_texture,
|
||||||
|
.transform = display_transform,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(world: *ona.World, events: ona.App.Events) !void {
|
||||||
|
try world.set_state(Effects{});
|
||||||
|
|
||||||
|
try world.on_event(events.load, ona.system_fn(load), .{.label = "load"});
|
||||||
|
try world.on_event(events.render, ona.system_fn(render), .{.label = "render actors"});
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
const coral = @import("coral");
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
const gfx = @import("../gfx.zig");
|
const ona = @import("ona");
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
const sokol = @import("sokol");
|
||||||
|
|
||||||
|
@ -14,18 +14,18 @@ textures: TexturePool,
|
||||||
pub const Effect = struct {
|
pub const Effect = struct {
|
||||||
shader: sokol.gfx.Shader,
|
shader: sokol.gfx.Shader,
|
||||||
pipeline: sokol.gfx.Pipeline,
|
pipeline: sokol.gfx.Pipeline,
|
||||||
properties: []coral.io.Byte,
|
properties: []u8,
|
||||||
|
|
||||||
pub fn deinit(self: *Effect) void {
|
pub fn deinit(self: *Effect) void {
|
||||||
coral.heap.allocator.free(self.properties);
|
ona.heap.allocator.free(self.properties);
|
||||||
sokol.gfx.destroyPipeline(self.pipeline);
|
sokol.gfx.destroyPipeline(self.pipeline);
|
||||||
sokol.gfx.destroyShader(self.shader);
|
sokol.gfx.destroyShader(self.shader);
|
||||||
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(desc: gfx.Effect.Desc) spirv.Error!Effect {
|
pub fn init(desc: coral.Effect.Desc) spirv.Error!Effect {
|
||||||
var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
var spirv_arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
spirv_arena.deinit();
|
spirv_arena.deinit();
|
||||||
|
@ -80,13 +80,13 @@ pub const Effect = struct {
|
||||||
break: pipeline_desc pipeline_desc;
|
break: pipeline_desc pipeline_desc;
|
||||||
});
|
});
|
||||||
|
|
||||||
const properties = try coral.heap.allocator.alloc(
|
const properties = try ona.heap.allocator.alloc(
|
||||||
coral.io.Byte,
|
u8,
|
||||||
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
|
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
errdefer {
|
errdefer {
|
||||||
coral.heap.allocator.free(properties);
|
ona.heap.allocator.free(properties);
|
||||||
sokol.gfx.destroyPipeline(pipeline);
|
sokol.gfx.destroyPipeline(pipeline);
|
||||||
sokol.gfx.destroyShader(shader);
|
sokol.gfx.destroyShader(shader);
|
||||||
}
|
}
|
||||||
|
@ -239,7 +239,7 @@ pub const Effect = struct {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const EffectPool = coral.Pool(Effect);
|
const EffectPool = ona.Pool(Effect);
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
|
@ -282,7 +282,7 @@ pub const Texture = struct {
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture {
|
pub fn init(desc: coral.Texture.Desc) std.mem.Allocator.Error!Texture {
|
||||||
const pixel_format = switch (desc.format) {
|
const pixel_format = switch (desc.format) {
|
||||||
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
||||||
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
|
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
|
||||||
|
@ -384,9 +384,9 @@ pub const Texture = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const TexturePool = coral.Pool(Texture);
|
const TexturePool = ona.Pool(Texture);
|
||||||
|
|
||||||
pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect {
|
pub fn create_effect(self: *Self, desc: coral.Effect.Desc) !coral.Effect {
|
||||||
var effect = try Effect.init(desc);
|
var effect = try Effect.init(desc);
|
||||||
|
|
||||||
errdefer effect.deinit();
|
errdefer effect.deinit();
|
||||||
|
@ -394,7 +394,7 @@ pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect {
|
||||||
return @enumFromInt(try self.effects.insert(effect));
|
return @enumFromInt(try self.effects.insert(effect));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_texture(self: *Self, desc: gfx.Texture.Desc) !gfx.Texture {
|
pub fn create_texture(self: *Self, desc: coral.Texture.Desc) !coral.Texture {
|
||||||
var texture = try Texture.init(desc);
|
var texture = try Texture.init(desc);
|
||||||
|
|
||||||
errdefer texture.deinit();
|
errdefer texture.deinit();
|
||||||
|
@ -422,7 +422,7 @@ pub fn deinit(self: *Self) void {
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
|
pub fn destroy_effect(self: *Self, handle: coral.Effect) bool {
|
||||||
switch (handle) {
|
switch (handle) {
|
||||||
.default => {},
|
.default => {},
|
||||||
|
|
||||||
|
@ -438,7 +438,7 @@ pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
|
pub fn destroy_texture(self: *Self, handle: coral.Texture) bool {
|
||||||
switch (handle) {
|
switch (handle) {
|
||||||
.default => {},
|
.default => {},
|
||||||
|
|
||||||
|
@ -454,18 +454,18 @@ pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect {
|
pub fn get_effect(self: *Self, handle: coral.Effect) ?*Effect {
|
||||||
return self.effects.get(@intFromEnum(handle));
|
return self.effects.get(@intFromEnum(handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_texture(self: *Self, handle: gfx.Texture) ?*Texture {
|
pub fn get_texture(self: *Self, handle: coral.Texture) ?*Texture {
|
||||||
return self.textures.get(@intFromEnum(handle));
|
return self.textures.get(@intFromEnum(handle));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init() !Self {
|
pub fn init() !Self {
|
||||||
var pools = Self{
|
var pools = Self{
|
||||||
.effects = EffectPool.init(coral.heap.allocator),
|
.effects = EffectPool.init(ona.heap.allocator),
|
||||||
.textures = TexturePool.init(coral.heap.allocator),
|
.textures = TexturePool.init(ona.heap.allocator),
|
||||||
};
|
};
|
||||||
|
|
||||||
errdefer {
|
errdefer {
|
||||||
|
@ -478,11 +478,11 @@ pub fn init() !Self {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
assert.is_handle(gfx.Effect.default, try pools.create_effect(.{
|
assert.is_handle(coral.Effect.default, try pools.create_effect(.{
|
||||||
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
|
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
assert.is_handle(gfx.Texture.default, try pools.create_texture(.{
|
assert.is_handle(coral.Texture.default, try pools.create_texture(.{
|
||||||
.format = .rgba8,
|
.format = .rgba8,
|
||||||
|
|
||||||
.access = .{
|
.access = .{
|
||||||
|
@ -503,7 +503,7 @@ pub fn init() !Self {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{
|
assert.is_handle(coral.Texture.backbuffer, try pools.create_texture(.{
|
||||||
.format = .rgba8,
|
.format = .rgba8,
|
||||||
|
|
||||||
.access = .{
|
.access = .{
|
|
@ -0,0 +1,13 @@
|
||||||
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
|
pub const black = greyscale(0);
|
||||||
|
|
||||||
|
pub const white = greyscale(1);
|
||||||
|
|
||||||
|
pub fn greyscale(v: f32) coral.Color {
|
||||||
|
return .{v, v, v, 1};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rgb(r: f32, g: f32, b: f32) coral.Color {
|
||||||
|
return .{r, g, b, 1};
|
||||||
|
}
|
|
@ -1,125 +1,524 @@
|
||||||
pub const ascii = @import("./ascii.zig");
|
pub const colors = @import("./colors.zig");
|
||||||
|
|
||||||
pub const asyncio = @import("./asyncio.zig");
|
const input = @import("input");
|
||||||
|
|
||||||
pub const files = @import("./files.zig");
|
const ona = @import("ona");
|
||||||
|
|
||||||
pub const hashes = @import("./hashes.zig");
|
const ext = @cImport({
|
||||||
|
@cInclude("SDL2/SDL.h");
|
||||||
|
});
|
||||||
|
|
||||||
pub const heap = @import("./heap.zig");
|
const rendering = @import("./rendering.zig");
|
||||||
|
|
||||||
pub const io = @import("./io.zig");
|
|
||||||
|
|
||||||
pub const lina = @import("./lina.zig");
|
|
||||||
|
|
||||||
pub const map = @import("./map.zig");
|
|
||||||
|
|
||||||
pub const scalars = @import("./scalars.zig");
|
|
||||||
|
|
||||||
pub const slices = @import("./slices.zig");
|
|
||||||
|
|
||||||
pub const stack = @import("./stack.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const utf8 = @import("./utf8.zig");
|
pub const Assets = struct {
|
||||||
|
window: *ext.SDL_Window,
|
||||||
|
texture_formats: ona.stack.Sequential(TextureFormat),
|
||||||
|
frame_rendered: std.Thread.ResetEvent = .{},
|
||||||
|
pending_work: WorkQueue = .{},
|
||||||
|
has_worker_thread: ?std.Thread = null,
|
||||||
|
|
||||||
pub fn Pool(comptime Value: type) type {
|
pub const LoadError = std.mem.Allocator.Error;
|
||||||
return struct {
|
|
||||||
entries: stack.Sequential(Entry),
|
|
||||||
first_free_index: usize = 0,
|
|
||||||
|
|
||||||
const Entry = union (enum) {
|
pub const LoadFileError = LoadError || ona.files.ReadAllError || error {
|
||||||
free_index: usize,
|
FormatUnsupported,
|
||||||
occupied: Value,
|
};
|
||||||
|
|
||||||
|
pub const TextureFormat = struct {
|
||||||
|
extension: []const u8,
|
||||||
|
load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const WorkQueue = ona.asyncio.BlockingQueue(1024, union (enum) {
|
||||||
|
load_effect: LoadEffectWork,
|
||||||
|
load_texture: LoadTextureWork,
|
||||||
|
render_frame: RenderFrameWork,
|
||||||
|
shutdown,
|
||||||
|
unload_effect: UnloadEffectWork,
|
||||||
|
unload_texture: UnloadTextureWork,
|
||||||
|
|
||||||
|
const LoadEffectWork = struct {
|
||||||
|
desc: Effect.Desc,
|
||||||
|
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect),
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Values = struct {
|
const LoadTextureWork = struct {
|
||||||
cursor: usize = 0,
|
desc: Texture.Desc,
|
||||||
pool: *const Self,
|
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture),
|
||||||
|
};
|
||||||
|
|
||||||
pub fn next(self: *Values) ?*Value {
|
const RenderFrameWork = struct {
|
||||||
while (self.cursor < self.pool.entries.len()) {
|
clear_color: Color,
|
||||||
defer self.cursor += 1;
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
finished: *std.Thread.ResetEvent,
|
||||||
|
has_command_params: ?*ona.Params(Commands).Node,
|
||||||
|
};
|
||||||
|
|
||||||
switch (self.pool.entries.values[self.cursor]) {
|
const UnloadEffectWork = struct {
|
||||||
.free_index => {
|
handle: Effect,
|
||||||
continue;
|
};
|
||||||
},
|
|
||||||
|
|
||||||
.occupied => |*occupied| {
|
const UnloadTextureWork = struct {
|
||||||
return occupied;
|
handle: Texture,
|
||||||
},
|
};
|
||||||
}
|
});
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
fn deinit(self: *Assets) void {
|
||||||
|
self.pending_work.enqueue(.shutdown);
|
||||||
|
|
||||||
|
if (self.has_worker_thread) |worker_thread| {
|
||||||
|
worker_thread.join();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.texture_formats.deinit();
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init() !Assets {
|
||||||
|
const window = create: {
|
||||||
|
const position = ext.SDL_WINDOWPOS_CENTERED;
|
||||||
|
const flags = ext.SDL_WINDOW_OPENGL;
|
||||||
|
const width = 640;
|
||||||
|
const height = 480;
|
||||||
|
|
||||||
|
break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
|
||||||
|
return error.Unsupported;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
ext.SDL_DestroyWindow(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.texture_formats = .{.allocator = ona.heap.allocator},
|
||||||
|
.window = window,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_effect_file(self: *Assets, storage: ona.files.Storage, path: []const u8) LoadFileError!Effect {
|
||||||
|
if (!std.mem.endsWith(u8, path, ".spv")) {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fragment_file_stat = try storage.stat(path);
|
||||||
|
const fragment_spirv_ops = try ona.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
|
||||||
|
|
||||||
|
defer {
|
||||||
|
ona.heap.allocator.free(fragment_spirv_ops);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{});
|
||||||
|
|
||||||
|
std.debug.assert(bytes_read.len == fragment_file_stat.size);
|
||||||
|
|
||||||
|
var loaded = ona.asyncio.Future(std.mem.Allocator.Error!Effect){};
|
||||||
|
|
||||||
|
self.pending_work.enqueue(.{
|
||||||
|
.load_effect = .{
|
||||||
|
.desc = .{
|
||||||
|
.fragment_spirv_ops = fragment_spirv_ops,
|
||||||
|
},
|
||||||
|
|
||||||
|
.loaded = &loaded,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return loaded.get().*;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_texture(self: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture {
|
||||||
|
var loaded = ona.asyncio.Future(std.mem.Allocator.Error!Texture){};
|
||||||
|
|
||||||
|
self.pending_work.enqueue(.{
|
||||||
|
.load_texture = .{
|
||||||
|
.desc = desc,
|
||||||
|
.loaded = &loaded,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return loaded.get().*;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_texture_file(self: *Assets, storage: ona.files.Storage, path: []const u8) LoadFileError!Texture {
|
||||||
|
var arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
|
||||||
|
|
||||||
|
defer {
|
||||||
|
arena.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (self.texture_formats.values) |format| {
|
||||||
|
if (!std.mem.endsWith(u8, path, format.extension)) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const Self = @This();
|
return self.load_texture(try format.load_file(&arena, storage, path));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
return error.FormatUnsupported;
|
||||||
self.entries.deinit();
|
}
|
||||||
|
|
||||||
|
pub const thread_restriction = .main;
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Color = @Vector(4, f32);
|
||||||
|
|
||||||
|
pub const Commands = struct {
|
||||||
|
pending: *List,
|
||||||
|
|
||||||
|
const Command = union (enum) {
|
||||||
|
draw_texture: DrawTextureCommand,
|
||||||
|
set_effect: SetEffectCommand,
|
||||||
|
set_target: SetTargetCommand,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const DrawTextureCommand = struct {
|
||||||
|
texture: Texture,
|
||||||
|
transform: Transform2D,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SetEffectCommand = struct {
|
||||||
|
effect: Effect,
|
||||||
|
properties: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SetTargetCommand = struct {
|
||||||
|
texture: Texture,
|
||||||
|
clear_color: ?Color,
|
||||||
|
clear_depth: ?f32,
|
||||||
|
clear_stencil: ?u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const List = struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
stack: ona.stack.Sequential(Command),
|
||||||
|
|
||||||
|
fn clear(self: *List) void {
|
||||||
|
self.stack.clear();
|
||||||
|
|
||||||
|
if (!self.arena.reset(.retain_capacity)) {
|
||||||
|
std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deinit(self: *List) void {
|
||||||
|
self.arena.deinit();
|
||||||
|
self.stack.deinit();
|
||||||
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get(self: *Self, key: usize) ?*Value {
|
fn init(allocator: std.mem.Allocator) List {
|
||||||
return switch (self.entries.values[key]) {
|
|
||||||
.free_index => null,
|
|
||||||
.occupied => |*occupied| occupied,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator) Self {
|
|
||||||
return .{
|
return .{
|
||||||
.entries = .{.allocator = allocator},
|
.arena = std.heap.ArenaAllocator.init(allocator),
|
||||||
};
|
.stack = .{.allocator = allocator},
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
|
|
||||||
const entries_count = self.entries.len();
|
|
||||||
|
|
||||||
if (self.first_free_index == entries_count) {
|
|
||||||
try self.entries.push_grow(.{.occupied = value});
|
|
||||||
|
|
||||||
self.first_free_index += 1;
|
|
||||||
|
|
||||||
return entries_count;
|
|
||||||
}
|
|
||||||
|
|
||||||
const insersion_index = self.first_free_index;
|
|
||||||
|
|
||||||
self.first_free_index = self.entries.values[self.first_free_index].free_index;
|
|
||||||
self.entries.values[insersion_index] = .{.occupied = value};
|
|
||||||
|
|
||||||
return insersion_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove(self: *Self, key: usize) ?Value {
|
|
||||||
if (key >= self.entries.len()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (self.entries.values[key]) {
|
|
||||||
.free_index => {
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
.occupied => |occupied| {
|
|
||||||
self.entries.values[key] = .{.free_index = self.first_free_index};
|
|
||||||
self.first_free_index = key;
|
|
||||||
|
|
||||||
return occupied;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn values(self: *const Self) Values {
|
|
||||||
return .{
|
|
||||||
.pool = self,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Param = struct {
|
||||||
|
swap_lists: [2]List,
|
||||||
|
swap_state: u1 = 0,
|
||||||
|
|
||||||
|
fn deinit(self: *Param) void {
|
||||||
|
for (&self.swap_lists) |*list| {
|
||||||
|
list.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pending_list(self: *Param) *List {
|
||||||
|
return &self.swap_lists[self.swap_state];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rotate(self: *Param) void {
|
||||||
|
const swapped_state = self.swap_state ^ 1;
|
||||||
|
|
||||||
|
self.swap_lists[swapped_state].clear();
|
||||||
|
|
||||||
|
self.swap_state = swapped_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn submitted_commands(self: Param) []const Command {
|
||||||
|
return self.swap_lists[self.swap_state ^ 1].stack.values;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param {
|
||||||
|
return .{
|
||||||
|
.swap_lists = .{
|
||||||
|
List.init(ona.heap.allocator),
|
||||||
|
List.init(ona.heap.allocator),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(param: *Param) Commands {
|
||||||
|
return .{
|
||||||
|
.pending = param.pending_list(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void {
|
||||||
|
try self.pending.stack.push_grow(.{.draw_texture = command});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void {
|
||||||
|
try self.pending.stack.push_grow(.{
|
||||||
|
.set_effect = .{
|
||||||
|
.properties = try self.pending.arena.allocator().dupe(u8, command.properties),
|
||||||
|
.effect = command.effect,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn unbind(param: *Param, _: ona.World.UnbindContext) void {
|
||||||
|
param.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void {
|
||||||
|
try self.pending.stack.push_grow(.{.set_target = command});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Display = struct {
|
||||||
|
width: u16 = 1280,
|
||||||
|
height: u16 = 720,
|
||||||
|
clear_color: Color = colors.black,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Effect = enum (u32) {
|
||||||
|
default,
|
||||||
|
_,
|
||||||
|
|
||||||
|
pub const Desc = struct {
|
||||||
|
fragment_spirv_ops: []const u32,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Rect = struct {
|
||||||
|
left: f32,
|
||||||
|
top: f32,
|
||||||
|
right: f32,
|
||||||
|
bottom: f32,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Texture = enum (u32) {
|
||||||
|
default,
|
||||||
|
backbuffer,
|
||||||
|
_,
|
||||||
|
|
||||||
|
pub const Desc = struct {
|
||||||
|
format: Format,
|
||||||
|
access: Access,
|
||||||
|
|
||||||
|
pub const Access = union (enum) {
|
||||||
|
static: StaticAccess,
|
||||||
|
render: RenderAccess,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const StaticAccess = struct {
|
||||||
|
width: u16,
|
||||||
|
data: []const u8,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const RenderAccess = struct {
|
||||||
|
width: u16,
|
||||||
|
height: u16,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Format = enum {
|
||||||
|
rgba8,
|
||||||
|
bgra8,
|
||||||
|
|
||||||
|
pub fn byte_size(self: Format) usize {
|
||||||
|
return switch (self) {
|
||||||
|
.rgba8, .bgra8 => 4,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Transform2D = extern struct {
|
||||||
|
xbasis: Vector = .{1, 0},
|
||||||
|
ybasis: Vector = .{0, 1},
|
||||||
|
origin: Vector = @splat(0),
|
||||||
|
|
||||||
|
const Vector = @Vector(2, f32);
|
||||||
|
};
|
||||||
|
|
||||||
|
fn load_bmp_texture(arena: *std.heap.ArenaAllocator, storage: ona.files.Storage, path: []const u8) !Texture.Desc {
|
||||||
|
const header = try storage.read_little(path, 0, extern struct {
|
||||||
|
type: [2]u8 align (1),
|
||||||
|
file_size: u32 align (1),
|
||||||
|
reserved: [2]u16 align (1),
|
||||||
|
image_offset: u32 align (1),
|
||||||
|
header_size: u32 align (1),
|
||||||
|
pixel_width: i32 align (1),
|
||||||
|
pixel_height: i32 align (1),
|
||||||
|
color_planes: u16 align (1),
|
||||||
|
bits_per_pixel: u16 align (1),
|
||||||
|
compression_method: u32 align (1),
|
||||||
|
image_size: u32 align(1),
|
||||||
|
pixels_per_meter_x: i32 align (1),
|
||||||
|
pixels_per_meter_y: i32 align (1),
|
||||||
|
palette_colors_used: u32 align (1),
|
||||||
|
important_colors_used: u32 align (1),
|
||||||
|
}) orelse {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!std.mem.eql(u8, &header.type, "BM")) {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
};
|
||||||
|
|
||||||
|
const pixels = try arena.allocator().alloc(u8, header.image_size);
|
||||||
|
const bytes_per_pixel = header.bits_per_pixel / @bitSizeOf(u8);
|
||||||
|
const alignment = 4;
|
||||||
|
const byte_stride = pixel_width * bytes_per_pixel;
|
||||||
|
const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
|
||||||
|
const byte_padding = ona.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
|
||||||
|
var buffer_offset: usize = 0;
|
||||||
|
var file_offset = @as(usize, header.image_offset);
|
||||||
|
|
||||||
|
switch (header.bits_per_pixel) {
|
||||||
|
32 => {
|
||||||
|
while (buffer_offset < pixels.len) {
|
||||||
|
const line = pixels[buffer_offset .. buffer_offset + byte_stride];
|
||||||
|
|
||||||
|
if (try storage.read(path, line, file_offset) != byte_stride) {
|
||||||
|
return error.FormatUnsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (0 .. pixel_width) |i| {
|
||||||
|
const line_offset = i * 4;
|
||||||
|
const pixel = line[line_offset .. line_offset + 4];
|
||||||
|
|
||||||
|
std.mem.swap(u8, &pixel[0], &pixel[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_offset += line.len + byte_padding;
|
||||||
|
buffer_offset += padded_byte_stride;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
else => return error.FormatUnsupported,
|
||||||
|
}
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.format = .rgba8,
|
||||||
|
|
||||||
|
.access = .{
|
||||||
|
.static = .{
|
||||||
|
.width = pixel_width,
|
||||||
|
.data = pixels,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn poll(app: ona.Write(ona.App), events: ona.Send(input.Event)) !void {
|
||||||
|
var event = @as(ext.SDL_Event, undefined);
|
||||||
|
|
||||||
|
while (ext.SDL_PollEvent(&event) != 0) {
|
||||||
|
switch (event.type) {
|
||||||
|
ext.SDL_QUIT => app.state.quit(),
|
||||||
|
ext.SDL_KEYUP => try events.push(.{.key_up = @enumFromInt(event.key.keysym.scancode)}),
|
||||||
|
ext.SDL_KEYDOWN => try events.push(.{.key_down = @enumFromInt(event.key.keysym.scancode)}),
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
|
||||||
|
if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) {
|
||||||
|
return error.Unsupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
const assets = create: {
|
||||||
|
var assets = try Assets.init();
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
assets.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
break: create try world.set_get_state(assets);
|
||||||
|
};
|
||||||
|
|
||||||
|
assets.frame_rendered.set();
|
||||||
|
|
||||||
|
errdefer {
|
||||||
|
assets.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{
|
||||||
|
&assets.pending_work,
|
||||||
|
assets.window,
|
||||||
|
});
|
||||||
|
|
||||||
|
const builtin_texture_formats = [_]Assets.TextureFormat{
|
||||||
|
.{
|
||||||
|
.extension = "bmp",
|
||||||
|
.load_file = load_bmp_texture,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (builtin_texture_formats) |format| {
|
||||||
|
try assets.texture_formats.push_grow(format);
|
||||||
|
}
|
||||||
|
|
||||||
|
try world.set_state(Display{});
|
||||||
|
try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll coral"});
|
||||||
|
try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop coral"});
|
||||||
|
try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize coral"});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(assets: ona.Write(Assets)) void {
|
||||||
|
assets.state.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void {
|
||||||
|
const assets, const display = exclusive.states;
|
||||||
|
|
||||||
|
assets.frame_rendered.wait();
|
||||||
|
assets.frame_rendered.reset();
|
||||||
|
|
||||||
|
{
|
||||||
|
var has_command_param = exclusive.world.get_params(Commands).has_head;
|
||||||
|
|
||||||
|
while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
|
||||||
|
command_param.param.rotate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var display_width, var display_height = [_]c_int{0, 0};
|
||||||
|
|
||||||
|
ext.SDL_GL_GetDrawableSize(assets.window, &display_width, &display_height);
|
||||||
|
|
||||||
|
if (display.width != display_width or display.height != display_height) {
|
||||||
|
ext.SDL_SetWindowSize(assets.window, display.width, display.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exclusive.world.get_params(Commands).has_head) |command_param| {
|
||||||
|
assets.pending_work.enqueue(.{
|
||||||
|
.render_frame = .{
|
||||||
|
.has_command_params = command_param,
|
||||||
|
.width = display.width,
|
||||||
|
.height = display.height,
|
||||||
|
.clear_color = display.clear_color,
|
||||||
|
.finished = &assets.frame_rendered,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
assets.frame_rendered.set();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +1,31 @@
|
||||||
const Resources = @import("./Resources.zig");
|
const Resources = @import("./Resources.zig");
|
||||||
|
|
||||||
const coral = @import("coral");
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
const ext = @import("../ext.zig");
|
const ext = @cImport({
|
||||||
|
@cInclude("SDL2/SDL.h");
|
||||||
|
});
|
||||||
|
|
||||||
const gfx = @import("../gfx.zig");
|
const ona = @import("ona");
|
||||||
|
|
||||||
const spirv = @import("./spirv.zig");
|
|
||||||
|
|
||||||
const sokol = @import("sokol");
|
const sokol = @import("sokol");
|
||||||
|
|
||||||
|
const spirv = @import("./spirv.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const Frame = struct {
|
const Frame = struct {
|
||||||
texture_batch_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
|
texture_batch_buffers: ona.stack.Sequential(sokol.gfx.Buffer),
|
||||||
quad_index_buffer: sokol.gfx.Buffer,
|
quad_index_buffer: sokol.gfx.Buffer,
|
||||||
quad_vertex_buffer: sokol.gfx.Buffer,
|
quad_vertex_buffer: sokol.gfx.Buffer,
|
||||||
drawn_count: usize = 0,
|
drawn_count: usize = 0,
|
||||||
flushed_count: usize = 0,
|
flushed_count: usize = 0,
|
||||||
current_source_texture: gfx.Texture = .default,
|
current_source_texture: coral.Texture = .default,
|
||||||
current_target_texture: gfx.Texture = .backbuffer,
|
current_target_texture: coral.Texture = .backbuffer,
|
||||||
current_effect: gfx.Effect = .default,
|
current_effect: coral.Effect = .default,
|
||||||
|
|
||||||
const DrawTexture = extern struct {
|
const DrawTexture = extern struct {
|
||||||
transform: gfx.Transform2D,
|
transform: coral.Transform2D,
|
||||||
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
|
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
|
||||||
depth: f32 = 0,
|
depth: f32 = 0,
|
||||||
texture_offset: @Vector(2, f32) = @splat(0),
|
texture_offset: @Vector(2, f32) = @splat(0),
|
||||||
|
@ -65,15 +67,15 @@ const Frame = struct {
|
||||||
});
|
});
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.texture_batch_buffers = .{.allocator = coral.heap.allocator},
|
.texture_batch_buffers = .{.allocator = ona.heap.allocator},
|
||||||
.quad_index_buffer = quad_index_buffer,
|
.quad_index_buffer = quad_index_buffer,
|
||||||
.quad_vertex_buffer = quad_vertex_buffer,
|
.quad_vertex_buffer = quad_vertex_buffer,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_texture(self: *Frame, pools: *Resources, command: gfx.Commands.DrawTextureCommand) !void {
|
pub fn draw_texture(self: *Frame, resources: *Resources, command: coral.Commands.DrawTextureCommand) !void {
|
||||||
if (command.texture != self.current_source_texture) {
|
if (command.texture != self.current_source_texture) {
|
||||||
self.flush(pools);
|
self.flush(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.current_source_texture = command.texture;
|
self.current_source_texture = command.texture;
|
||||||
|
@ -99,8 +101,8 @@ const Frame = struct {
|
||||||
self.drawn_count += 1;
|
self.drawn_count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish(self: *Frame, pools: *Resources) void {
|
pub fn finish(self: *Frame, resources: *Resources) void {
|
||||||
self.flush(pools);
|
self.flush(resources);
|
||||||
|
|
||||||
self.drawn_count = 0;
|
self.drawn_count = 0;
|
||||||
self.flushed_count = 0;
|
self.flushed_count = 0;
|
||||||
|
@ -109,7 +111,7 @@ const Frame = struct {
|
||||||
self.current_effect = .default;
|
self.current_effect = .default;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush(self: *Frame, pools: *Resources) void {
|
pub fn flush(self: *Frame, resources: *Resources) void {
|
||||||
if (self.flushed_count == self.drawn_count) {
|
if (self.flushed_count == self.drawn_count) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -120,7 +122,7 @@ const Frame = struct {
|
||||||
|
|
||||||
bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer;
|
bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer;
|
||||||
|
|
||||||
switch (pools.get_texture(self.current_source_texture).?.access) {
|
switch (resources.get_texture(self.current_source_texture).?.access) {
|
||||||
.render => |render| {
|
.render => |render| {
|
||||||
bindings.fs.images[0] = render.color_image;
|
bindings.fs.images[0] = render.color_image;
|
||||||
bindings.fs.samplers[0] = default_sampler;
|
bindings.fs.samplers[0] = default_sampler;
|
||||||
|
@ -136,13 +138,13 @@ const Frame = struct {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const effect = pools.get_effect(self.current_effect).?;
|
const effect = resources.get_effect(self.current_effect).?;
|
||||||
|
|
||||||
sokol.gfx.applyPipeline(effect.pipeline);
|
sokol.gfx.applyPipeline(effect.pipeline);
|
||||||
|
|
||||||
const texture = pools.get_texture(self.current_target_texture).?;
|
const texture = resources.get_texture(self.current_target_texture).?;
|
||||||
|
|
||||||
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{
|
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{
|
||||||
.left = 0,
|
.left = 0,
|
||||||
.top = 0,
|
.top = 0,
|
||||||
.right = @floatFromInt(texture.width),
|
.right = @floatFromInt(texture.width),
|
||||||
|
@ -171,19 +173,19 @@ const Frame = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_effect(self: *Frame, pools: *Resources, command: gfx.Commands.SetEffectCommand) void {
|
pub fn set_effect(self: *Frame, resources: *Resources, command: coral.Commands.SetEffectCommand) void {
|
||||||
if (command.effect != self.current_effect) {
|
if (command.effect != self.current_effect) {
|
||||||
self.flush(pools);
|
self.flush(resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.current_effect = command.effect;
|
self.current_effect = command.effect;
|
||||||
|
|
||||||
if (pools.get_effect(self.current_effect)) |effect| {
|
if (resources.get_effect(self.current_effect)) |effect| {
|
||||||
@memcpy(effect.properties, command.properties);
|
@memcpy(effect.properties, command.properties);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void {
|
pub fn set_target(self: *Frame, resources: *Resources, command: coral.Commands.SetTargetCommand) void {
|
||||||
sokol.gfx.endPass();
|
sokol.gfx.endPass();
|
||||||
|
|
||||||
var pass = sokol.gfx.Pass{
|
var pass = sokol.gfx.Pass{
|
||||||
|
@ -212,7 +214,7 @@ const Frame = struct {
|
||||||
pass.action.depth = .{.load_action = .LOAD};
|
pass.action.depth = .{.load_action = .LOAD};
|
||||||
}
|
}
|
||||||
|
|
||||||
pass.attachments = switch (pools.get_texture(self.current_target_texture).?.access) {
|
pass.attachments = switch (resources.get_texture(self.current_target_texture).?.access) {
|
||||||
.static => @panic("Cannot render to static textures"),
|
.static => @panic("Cannot render to static textures"),
|
||||||
.empty => @panic("Cannot render to empty textures"),
|
.empty => @panic("Cannot render to empty textures"),
|
||||||
.render => |render| render.attachments,
|
.render => |render| render.attachments,
|
||||||
|
@ -224,6 +226,10 @@ const Frame = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fn Matrix(comptime n: usize, comptime Element: type) type {
|
||||||
|
return [n]@Vector(n, Element);
|
||||||
|
}
|
||||||
|
|
||||||
var default_sampler: sokol.gfx.Sampler = undefined;
|
var default_sampler: sokol.gfx.Sampler = undefined;
|
||||||
|
|
||||||
const vertex_indices = .{
|
const vertex_indices = .{
|
||||||
|
@ -231,7 +237,19 @@ const vertex_indices = .{
|
||||||
.instance = 1,
|
.instance = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void {
|
fn orthographic_projection(near: f32, far: f32, viewport: coral.Rect) Matrix(4, f32) {
|
||||||
|
const width = viewport.right - viewport.left;
|
||||||
|
const height = viewport.bottom - viewport.top;
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.{2 / width, 0, 0, 0},
|
||||||
|
.{0, 2 / height, 0, 0},
|
||||||
|
.{0, 0, 1 / (far - near), 0},
|
||||||
|
.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_work(pending_work: *coral.Assets.WorkQueue, window: *ext.SDL_Window) !void {
|
||||||
const context = configure_and_create: {
|
const context = configure_and_create: {
|
||||||
var result = @as(c_int, 0);
|
var result = @as(c_int, 0);
|
||||||
|
|
2218
src/coral/script.zig
2218
src/coral/script.zig
File diff suppressed because it is too large
Load Diff
|
@ -1,10 +1,10 @@
|
||||||
const coral = @import("coral");
|
const coral = @import("./coral.zig");
|
||||||
|
|
||||||
const ext = @cImport({
|
const ext = @cImport({
|
||||||
@cInclude("spirv-cross/spirv_cross_c.h");
|
@cInclude("spirv-cross/spirv_cross_c.h");
|
||||||
});
|
});
|
||||||
|
|
||||||
const ona = @import("../ona.zig");
|
const ona = @import("ona");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
@ -202,7 +202,7 @@ pub const Stage = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
uniform.* = .{
|
uniform.* = .{
|
||||||
.name = try coral.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{
|
.name = try ona.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{
|
||||||
.id = reflected_resource.id,
|
.id = reflected_resource.id,
|
||||||
.member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)),
|
.member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)),
|
||||||
}),
|
}),
|
||||||
|
@ -357,7 +357,7 @@ fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Tar
|
||||||
var binding: u32 = 0;
|
var binding: u32 = 0;
|
||||||
|
|
||||||
for (combined_image_samplers) |combined_image_sampler| {
|
for (combined_image_samplers) |combined_image_sampler| {
|
||||||
const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
|
const name = try ona.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
|
||||||
.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
|
.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
|
||||||
.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
|
.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
|
||||||
});
|
});
|
|
@ -1,98 +0,0 @@
|
||||||
const builtin = @import("builtin");
|
|
||||||
|
|
||||||
const flow = @import("./flow.zig");
|
|
||||||
|
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const states = @import("./states.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const system = @import("./system.zig");
|
|
||||||
|
|
||||||
thread_pool: ?*std.Thread.Pool = null,
|
|
||||||
thread_restricted_resources: [std.enums.values(states.ThreadRestriction).len]states.Table,
|
|
||||||
event_systems: coral.stack.Sequential(system.Schedule),
|
|
||||||
|
|
||||||
pub const Event = enum (usize) { _ };
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event {
|
|
||||||
var systems = try system.Schedule.init(label);
|
|
||||||
|
|
||||||
errdefer systems.deinit(self);
|
|
||||||
|
|
||||||
const index = self.event_systems.len();
|
|
||||||
|
|
||||||
try self.event_systems.push_grow(systems);
|
|
||||||
|
|
||||||
return @enumFromInt(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
for (self.event_systems.values) |*schedule| {
|
|
||||||
schedule.deinit(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (&self.thread_restricted_resources) |*resources| {
|
|
||||||
resources.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (self.thread_pool) |thread_pool| {
|
|
||||||
thread_pool.deinit();
|
|
||||||
coral.heap.allocator.destroy(thread_pool);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.event_systems.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_params(self: Self, comptime Value: type) flow.Params(Value) {
|
|
||||||
const params = self.get_state(flow.Params(Value)) orelse {
|
|
||||||
return .{};
|
|
||||||
};
|
|
||||||
|
|
||||||
return params.*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_state(self: Self, comptime Value: type) ?*Value {
|
|
||||||
return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(states.thread_restriction(Value))].get(Value)));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
|
|
||||||
return self.thread_restricted_resources[@intFromEnum(states.thread_restriction(@TypeOf(value)))].set_get(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
|
|
||||||
var world = Self{
|
|
||||||
.thread_restricted_resources = .{states.Table.init(), states.Table.init()},
|
|
||||||
.event_systems = .{.allocator = coral.heap.allocator},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (thread_count != 0 and !builtin.single_threaded) {
|
|
||||||
const thread_pool = try coral.heap.allocator.create(std.Thread.Pool);
|
|
||||||
|
|
||||||
try thread_pool.init(.{
|
|
||||||
.allocator = coral.heap.allocator,
|
|
||||||
.n_jobs = thread_count,
|
|
||||||
});
|
|
||||||
|
|
||||||
world.thread_pool = thread_pool;
|
|
||||||
}
|
|
||||||
|
|
||||||
return world;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn on_event(self: *Self, event: Event, action: *const system.Info, order: system.Order) std.mem.Allocator.Error!void {
|
|
||||||
try self.event_systems.values[@intFromEnum(event)].then(self, action, order);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_event(self: *Self, event: Event) anyerror!void {
|
|
||||||
try self.event_systems.values[@intFromEnum(event)].run(self);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void {
|
|
||||||
try self.thread_restricted_resources[@intFromEnum(states.thread_restriction(@TypeOf(value)))].set(value);
|
|
||||||
}
|
|
|
@ -1,329 +0,0 @@
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const states = @import("./states.zig");
|
|
||||||
|
|
||||||
pub const system = @import("./system.zig");
|
|
||||||
|
|
||||||
pub const World = @import("./World.zig");
|
|
||||||
|
|
||||||
pub const Exclusive = struct {
|
|
||||||
world: *World,
|
|
||||||
|
|
||||||
pub const Param = struct {
|
|
||||||
world: *World,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn bind(context: system.BindContext) std.mem.Allocator.Error!Param {
|
|
||||||
return .{
|
|
||||||
.world = context.world,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(param: *Param) Exclusive {
|
|
||||||
return .{
|
|
||||||
.world = param.world,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const thread_restriction = .main;
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn Params(comptime Value: type) type {
|
|
||||||
if (!@hasDecl(Value, "Param")) {
|
|
||||||
@compileError("System parameters must have a Params type declaration");
|
|
||||||
}
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
has_head: ?*Node = null,
|
|
||||||
has_tail: ?*Node = null,
|
|
||||||
|
|
||||||
pub const Node = struct {
|
|
||||||
param: Value.Param,
|
|
||||||
has_prev: ?*Node = null,
|
|
||||||
has_next: ?*Node = null,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Read(comptime Value: type) type {
|
|
||||||
return Shared(Value, .{
|
|
||||||
.thread_restriction = states.thread_restriction(Value),
|
|
||||||
.read_only = true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const ShareInfo = struct {
|
|
||||||
thread_restriction: states.ThreadRestriction,
|
|
||||||
read_only: bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn Shared(comptime Value: type, comptime info: ShareInfo) type {
|
|
||||||
const value_info = @typeInfo(Value);
|
|
||||||
|
|
||||||
const Qualified = switch (value_info) {
|
|
||||||
.Optional => @Type(.{
|
|
||||||
.Optional = .{
|
|
||||||
.child = .{
|
|
||||||
.Pointer = .{
|
|
||||||
.is_allowzero = false,
|
|
||||||
.sentinel = null,
|
|
||||||
.address_space = .generic,
|
|
||||||
.is_volatile = false,
|
|
||||||
.alignment = @alignOf(Value),
|
|
||||||
.size = .One,
|
|
||||||
.child = Value,
|
|
||||||
.is_const = info.read_only,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
else => @Type(.{
|
|
||||||
.Pointer = .{
|
|
||||||
.is_allowzero = false,
|
|
||||||
.sentinel = null,
|
|
||||||
.address_space = .generic,
|
|
||||||
.is_volatile = false,
|
|
||||||
.alignment = @alignOf(Value),
|
|
||||||
.size = .One,
|
|
||||||
.child = Value,
|
|
||||||
.is_const = info.read_only,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
res: Qualified,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub const Param = struct {
|
|
||||||
res: Qualified,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn bind(context: system.BindContext) std.mem.Allocator.Error!Param {
|
|
||||||
const thread_restriction_name = switch (info.thread_restriction) {
|
|
||||||
.main => "main thread-restricted ",
|
|
||||||
.none => ""
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = switch (info.read_only) {
|
|
||||||
true => (try context.register_readable_state_access(Value)),
|
|
||||||
false => (try context.register_writable_state_access(Value)),
|
|
||||||
};
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.res = switch (value_info) {
|
|
||||||
.Optional => res,
|
|
||||||
|
|
||||||
else => res orelse {
|
|
||||||
@panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{
|
|
||||||
thread_restriction_name,
|
|
||||||
if (info.read_only) "read-only" else "read-write",
|
|
||||||
@typeName(Value),
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(param: *Param) Self {
|
|
||||||
return .{
|
|
||||||
.res = param.res,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Write(comptime Value: type) type {
|
|
||||||
return Shared(Value, .{
|
|
||||||
.thread_restriction = states.thread_restriction(Value),
|
|
||||||
.read_only = false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parameter_type(comptime Value: type) *const system.Info.Parameter {
|
|
||||||
const ValueParams = Params(Value);
|
|
||||||
|
|
||||||
if (@sizeOf(Value) == 0) {
|
|
||||||
@compileError("System parameters must have a non-zero size");
|
|
||||||
}
|
|
||||||
|
|
||||||
const parameters = struct {
|
|
||||||
fn bind(allocator: std.mem.Allocator, context: system.BindContext) std.mem.Allocator.Error!*anyopaque {
|
|
||||||
const value_name = @typeName(Value);
|
|
||||||
|
|
||||||
if (!@hasDecl(Value, "bind")) {
|
|
||||||
@compileError(
|
|
||||||
"a `bind` declaration on " ++
|
|
||||||
value_name ++
|
|
||||||
" is requied for parameter types with a `Param` declaration");
|
|
||||||
}
|
|
||||||
|
|
||||||
const bind_type = @typeInfo(@TypeOf(Value.bind));
|
|
||||||
|
|
||||||
if (bind_type != .Fn) {
|
|
||||||
@compileError("`bind` declaration on " ++ value_name ++ " must be a fn");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (bind_type.Fn.params.len != 1 or bind_type.Fn.params[0].type.? != system.BindContext) {
|
|
||||||
@compileError(
|
|
||||||
"`bind` fn on " ++
|
|
||||||
value_name ++
|
|
||||||
" must accept " ++
|
|
||||||
@typeName(system.BindContext) ++
|
|
||||||
" as it's one and only argument");
|
|
||||||
}
|
|
||||||
|
|
||||||
const params_node = try allocator.create(ValueParams.Node);
|
|
||||||
|
|
||||||
params_node.* = .{
|
|
||||||
.param = switch (bind_type.Fn.return_type.?) {
|
|
||||||
Value.Param => Value.bind(context),
|
|
||||||
std.mem.Allocator.Error!Value.Param => try Value.bind(context),
|
|
||||||
|
|
||||||
else => @compileError(std.fmt.comptimePrint("`bind` fn on {s} must return {s} or {s}", .{
|
|
||||||
@typeName(Value),
|
|
||||||
@typeName(Value.Param),
|
|
||||||
@typeName(std.mem.Allocator.Error!Value.Param)
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (context.world.get_state(ValueParams)) |value_params| {
|
|
||||||
if (value_params.has_tail) |tail| {
|
|
||||||
tail.has_next = params_node;
|
|
||||||
}
|
|
||||||
|
|
||||||
params_node.has_prev = value_params.has_tail;
|
|
||||||
value_params.has_tail = params_node;
|
|
||||||
} else {
|
|
||||||
try context.world.set_state(ValueParams{
|
|
||||||
.has_head = params_node,
|
|
||||||
.has_tail = params_node,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return @ptrCast(params_node);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(argument: *anyopaque, erased_node: *anyopaque) void {
|
|
||||||
const value_name = @typeName(Value);
|
|
||||||
|
|
||||||
if (!@hasDecl(Value, "init")) {
|
|
||||||
@compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types");
|
|
||||||
}
|
|
||||||
|
|
||||||
const init_type = @typeInfo(@TypeOf(Value.init));
|
|
||||||
|
|
||||||
if (init_type != .Fn) {
|
|
||||||
@compileError("`init` declaration on " ++ value_name ++ " must be a fn");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (init_type.Fn.return_type.? != Value) {
|
|
||||||
@compileError("`init` fn on " ++ value_name ++ " must return a " ++ value_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
const concrete_argument = @as(*Value, @ptrCast(@alignCast(argument)));
|
|
||||||
|
|
||||||
if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.Param) {
|
|
||||||
@compileError("`init` fn on " ++ value_name ++ " must accept a " ++ @typeName(*Value.Param));
|
|
||||||
}
|
|
||||||
|
|
||||||
concrete_argument.* = Value.init(&@as(*ValueParams.Node, @ptrCast(@alignCast(erased_node))).param);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unbind(allocator: std.mem.Allocator, erased_node: *anyopaque, context: system.UnbindContext) void {
|
|
||||||
if (@hasDecl(Value, "unbind")) {
|
|
||||||
const node = @as(*ValueParams.Node, @ptrCast(@alignCast(erased_node)));
|
|
||||||
|
|
||||||
if (node.has_prev) |prev| {
|
|
||||||
prev.has_next = node.has_next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.has_next) |next| {
|
|
||||||
next.has_prev = node.has_prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (context.world.get_state(ValueParams)) |params| {
|
|
||||||
if (node.has_prev == null) {
|
|
||||||
params.has_head = node.has_next;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node.has_next == null) {
|
|
||||||
params.has_tail = node.has_prev;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Value.unbind(&node.param, context);
|
|
||||||
allocator.destroy(node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return comptime &.{
|
|
||||||
.thread_restriction = if (@hasDecl(Value, "thread_restriction")) Value.thread_restriction else .none,
|
|
||||||
.init = parameters.init,
|
|
||||||
.bind = parameters.bind,
|
|
||||||
.unbind = parameters.unbind,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn system_fn(comptime call: anytype) *const system.Info {
|
|
||||||
const Call = @TypeOf(call);
|
|
||||||
|
|
||||||
const system_info = comptime generate: {
|
|
||||||
switch (@typeInfo(Call)) {
|
|
||||||
.Fn => |call_fn| {
|
|
||||||
if (call_fn.params.len > system.max_parameters) {
|
|
||||||
@compileError("number of parameters to `call` cannot be more than 16");
|
|
||||||
}
|
|
||||||
|
|
||||||
const systems = struct {
|
|
||||||
fn run(parameters: []const *const system.Info.Parameter, data: *const [system.max_parameters]*anyopaque) anyerror!void {
|
|
||||||
var call_args = @as(std.meta.ArgsTuple(Call), undefined);
|
|
||||||
|
|
||||||
inline for (parameters, &call_args, data[0 .. parameters.len]) |parameter, *call_arg, state| {
|
|
||||||
parameter.init(call_arg, state);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (@typeInfo(call_fn.return_type.?)) {
|
|
||||||
.Void => @call(.auto, call, call_args),
|
|
||||||
.ErrorUnion => try @call(.auto, call, call_args),
|
|
||||||
else => @compileError("number of parameters to `call` must return void or !void"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var parameters = @as([system.max_parameters]*const system.Info.Parameter, undefined);
|
|
||||||
var thread_restriction = states.ThreadRestriction.none;
|
|
||||||
|
|
||||||
for (0 .. call_fn.params.len) |index| {
|
|
||||||
const CallParam = call_fn.params[index].type.?;
|
|
||||||
const parameter = parameter_type(CallParam);
|
|
||||||
|
|
||||||
if (parameter.thread_restriction != .none) {
|
|
||||||
if (thread_restriction != .none and thread_restriction != parameter.thread_restriction) {
|
|
||||||
@compileError("a system may not have conflicting thread restrictions");
|
|
||||||
}
|
|
||||||
|
|
||||||
thread_restriction = parameter.thread_restriction;
|
|
||||||
}
|
|
||||||
|
|
||||||
parameters[index] = parameter;
|
|
||||||
}
|
|
||||||
|
|
||||||
break: generate &.{
|
|
||||||
.parameters = parameters,
|
|
||||||
.parameter_count = call_fn.params.len,
|
|
||||||
.execute = systems.run,
|
|
||||||
.thread_restriction = thread_restriction,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
else => @compileError("parameter `call` must be a function"),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return system_info;
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Table = struct {
|
|
||||||
arena: std.heap.ArenaAllocator,
|
|
||||||
table: coral.map.Hashed(TypeID, Entry, coral.map.enum_traits(TypeID)),
|
|
||||||
|
|
||||||
const Entry = struct {
|
|
||||||
ptr: *anyopaque,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn deinit(self: *Table) void {
|
|
||||||
self.table.deinit();
|
|
||||||
self.arena.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(self: Table, comptime Resource: type) ?*Resource {
|
|
||||||
if (self.table.get(type_id(Resource))) |entry| {
|
|
||||||
return @ptrCast(@alignCast(entry.ptr));
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init() Table {
|
|
||||||
return .{
|
|
||||||
.arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
|
|
||||||
.table = .{.allocator = coral.heap.allocator},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_get(self: *Table, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
|
|
||||||
try self.set(value);
|
|
||||||
|
|
||||||
return self.get(@TypeOf(value)).?;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set(self: *Table, value: anytype) std.mem.Allocator.Error!void {
|
|
||||||
const Value = @TypeOf(value);
|
|
||||||
const value_id = type_id(Value);
|
|
||||||
|
|
||||||
if (self.table.get(value_id)) |entry| {
|
|
||||||
@as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value;
|
|
||||||
} else {
|
|
||||||
const resource_allocator = self.arena.allocator();
|
|
||||||
const allocated_resource = try resource_allocator.create(Value);
|
|
||||||
|
|
||||||
errdefer resource_allocator.destroy(allocated_resource);
|
|
||||||
|
|
||||||
std.debug.assert(try self.table.emplace(value_id, .{
|
|
||||||
.ptr = allocated_resource,
|
|
||||||
}));
|
|
||||||
|
|
||||||
allocated_resource.* = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const ThreadRestriction = enum {
|
|
||||||
none,
|
|
||||||
main,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const TypeID = enum (usize) { _ };
|
|
||||||
|
|
||||||
pub fn type_id(comptime T: type) TypeID {
|
|
||||||
const TypeHandle = struct {
|
|
||||||
comptime {
|
|
||||||
_ = T;
|
|
||||||
}
|
|
||||||
|
|
||||||
var byte: u8 = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
return @enumFromInt(@intFromPtr(&TypeHandle.byte));
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn thread_restriction(comptime State: type) ThreadRestriction {
|
|
||||||
if (@hasDecl(State, "thread_restriction")) {
|
|
||||||
return State.thread_restriction;
|
|
||||||
}
|
|
||||||
|
|
||||||
return .none;
|
|
||||||
}
|
|
|
@ -0,0 +1,274 @@
|
||||||
|
const ona = @import("ona");
|
||||||
|
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const Axis = struct {
|
||||||
|
keys: ?[2]Event.Key = null,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Event = union (enum) {
|
||||||
|
key_up: Key,
|
||||||
|
key_down: Key,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Key = enum (u32) {
|
||||||
|
no_event = 0x00,
|
||||||
|
error_rollover = 0x01,
|
||||||
|
post_fail = 0x02,
|
||||||
|
error_undefined = 0x03,
|
||||||
|
a = 0x04,
|
||||||
|
b = 0x05,
|
||||||
|
c = 0x06,
|
||||||
|
d = 0x07,
|
||||||
|
e = 0x08,
|
||||||
|
f = 0x09,
|
||||||
|
g = 0x0A,
|
||||||
|
h = 0x0B,
|
||||||
|
i = 0x0C,
|
||||||
|
j = 0x0D,
|
||||||
|
k = 0x0E,
|
||||||
|
l = 0x0F,
|
||||||
|
m = 0x10,
|
||||||
|
n = 0x11,
|
||||||
|
o = 0x12,
|
||||||
|
p = 0x13,
|
||||||
|
q = 0x14,
|
||||||
|
r = 0x15,
|
||||||
|
s = 0x16,
|
||||||
|
t = 0x17,
|
||||||
|
u = 0x18,
|
||||||
|
v = 0x19,
|
||||||
|
w = 0x1A,
|
||||||
|
x = 0x1B,
|
||||||
|
y = 0x1C,
|
||||||
|
z = 0x1D,
|
||||||
|
one = 0x1E,
|
||||||
|
two = 0x1F,
|
||||||
|
three = 0x20,
|
||||||
|
four = 0x21,
|
||||||
|
five = 0x22,
|
||||||
|
six = 0x23,
|
||||||
|
seven = 0x24,
|
||||||
|
eight = 0x25,
|
||||||
|
nine = 0x26,
|
||||||
|
zero = 0x27,
|
||||||
|
enter = 0x28,
|
||||||
|
escape = 0x29,
|
||||||
|
backspace = 0x2A,
|
||||||
|
tab = 0x2B,
|
||||||
|
space = 0x2C,
|
||||||
|
minus = 0x2D,
|
||||||
|
equal = 0x2E,
|
||||||
|
left_bracket = 0x2F,
|
||||||
|
right_bracket = 0x30,
|
||||||
|
backslash = 0x31,
|
||||||
|
non_us_pound = 0x32,
|
||||||
|
semicolon = 0x33,
|
||||||
|
quote = 0x34,
|
||||||
|
grave = 0x35,
|
||||||
|
comma = 0x36,
|
||||||
|
period = 0x37,
|
||||||
|
slash = 0x38,
|
||||||
|
caps_lock = 0x39,
|
||||||
|
f1 = 0x3A,
|
||||||
|
f2 = 0x3B,
|
||||||
|
f3 = 0x3C,
|
||||||
|
f4 = 0x3D,
|
||||||
|
f5 = 0x3E,
|
||||||
|
f6 = 0x3F,
|
||||||
|
f7 = 0x40,
|
||||||
|
f8 = 0x41,
|
||||||
|
f9 = 0x42,
|
||||||
|
f10 = 0x43,
|
||||||
|
f11 = 0x44,
|
||||||
|
f12 = 0x45,
|
||||||
|
print_screen = 0x46,
|
||||||
|
scroll_lock = 0x47,
|
||||||
|
pause = 0x48,
|
||||||
|
insert = 0x49,
|
||||||
|
home = 0x4A,
|
||||||
|
page_up = 0x4B,
|
||||||
|
delete = 0x4C,
|
||||||
|
end = 0x4D,
|
||||||
|
page_down = 0x4E,
|
||||||
|
right_arrow = 0x4F,
|
||||||
|
left_arrow = 0x50,
|
||||||
|
down_arrow = 0x51,
|
||||||
|
up_arrow = 0x52,
|
||||||
|
num_lock = 0x53,
|
||||||
|
keypad_slash = 0x54,
|
||||||
|
keypad_asterisk = 0x55,
|
||||||
|
keypad_minus = 0x56,
|
||||||
|
keypad_plus = 0x57,
|
||||||
|
keypad_enter = 0x58,
|
||||||
|
keypad_one = 0x59,
|
||||||
|
keypad_two = 0x5A,
|
||||||
|
keypad_three = 0x5B,
|
||||||
|
keypad_four = 0x5C,
|
||||||
|
keypad_five = 0x5D,
|
||||||
|
keypad_six = 0x5E,
|
||||||
|
keypad_seven = 0x5F,
|
||||||
|
keypad_eight = 0x60,
|
||||||
|
keypad_nine = 0x61,
|
||||||
|
keypad_zero = 0x62,
|
||||||
|
keypad_period = 0x63,
|
||||||
|
non_us_backslash = 0x64,
|
||||||
|
application = 0x65,
|
||||||
|
power = 0x66,
|
||||||
|
keypad_equal = 0x67,
|
||||||
|
f13 = 0x68,
|
||||||
|
f14 = 0x69,
|
||||||
|
f15 = 0x6A,
|
||||||
|
f16 = 0x6B,
|
||||||
|
f17 = 0x6C,
|
||||||
|
f18 = 0x6D,
|
||||||
|
f19 = 0x6E,
|
||||||
|
f20 = 0x6F,
|
||||||
|
f21 = 0x70,
|
||||||
|
f22 = 0x71,
|
||||||
|
f23 = 0x72,
|
||||||
|
f24 = 0x73,
|
||||||
|
execute = 0x74,
|
||||||
|
help = 0x75,
|
||||||
|
menu = 0x76,
|
||||||
|
select = 0x77,
|
||||||
|
stop = 0x78,
|
||||||
|
again = 0x79,
|
||||||
|
undo = 0x7A,
|
||||||
|
cut = 0x7B,
|
||||||
|
copy = 0x7C,
|
||||||
|
paste = 0x7D,
|
||||||
|
find = 0x7E,
|
||||||
|
mute = 0x7F,
|
||||||
|
volume_up = 0x80,
|
||||||
|
volume_down = 0x81,
|
||||||
|
lock_caps_lock = 0x82,
|
||||||
|
lock_num_lock = 0x83,
|
||||||
|
lock_scroll_lock = 0x84,
|
||||||
|
keypad_comma = 0x85,
|
||||||
|
keypad_equal_sign = 0x86,
|
||||||
|
international1 = 0x87,
|
||||||
|
international2 = 0x88,
|
||||||
|
international3 = 0x89,
|
||||||
|
international4 = 0x8A,
|
||||||
|
international5 = 0x8B,
|
||||||
|
international6 = 0x8C,
|
||||||
|
international7 = 0x8D,
|
||||||
|
international8 = 0x8E,
|
||||||
|
international9 = 0x8F,
|
||||||
|
lang1 = 0x90,
|
||||||
|
lang2 = 0x91,
|
||||||
|
lang3 = 0x92,
|
||||||
|
lang4 = 0x93,
|
||||||
|
lang5 = 0x94,
|
||||||
|
lang6 = 0x95,
|
||||||
|
lang7 = 0x96,
|
||||||
|
lang8 = 0x97,
|
||||||
|
lang9 = 0x98,
|
||||||
|
alternate_erase = 0x99,
|
||||||
|
sys_req_attention = 0x9A,
|
||||||
|
cancel = 0x9B,
|
||||||
|
clear = 0x9C,
|
||||||
|
prior = 0x9D,
|
||||||
|
return_key = 0x9E,
|
||||||
|
separator = 0x9F,
|
||||||
|
out = 0xA0,
|
||||||
|
oper = 0xA1,
|
||||||
|
clear_again = 0xA2,
|
||||||
|
cr_sel_props = 0xA3,
|
||||||
|
ex_sel = 0xA4,
|
||||||
|
left_control = 0xE0,
|
||||||
|
left_shift = 0xE1,
|
||||||
|
left_alt = 0xE2,
|
||||||
|
left_gui = 0xE3,
|
||||||
|
right_control = 0xE4,
|
||||||
|
right_shift = 0xE5,
|
||||||
|
right_alt = 0xE6,
|
||||||
|
right_gui = 0xE7,
|
||||||
|
_,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Mapping = struct {
|
||||||
|
keys_pressed: ScancodeSet = ScancodeSet.initEmpty(),
|
||||||
|
keys_held: ScancodeSet = ScancodeSet.initEmpty(),
|
||||||
|
|
||||||
|
const ScancodeSet = std.bit_set.StaticBitSet(512);
|
||||||
|
|
||||||
|
pub fn axis_strength(self: Mapping, axis: Axis) f32 {
|
||||||
|
if (axis.keys) |keys| {
|
||||||
|
const key_down, const key_up = keys;
|
||||||
|
const is_key_down_held = self.keys_held.isSet(@intFromEnum(key_down));
|
||||||
|
const is_key_up_held = self.keys_held.isSet(@intFromEnum(key_up));
|
||||||
|
|
||||||
|
if (is_key_down_held or is_key_up_held) {
|
||||||
|
return
|
||||||
|
@as(f32, @floatFromInt(@intFromBool(is_key_up_held))) -
|
||||||
|
@as(f32, @floatFromInt(@intFromBool(is_key_down_held)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
test "mapping values" {
|
||||||
|
const axis = Axis{
|
||||||
|
.keys = .{.minus, .equal},
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
var mapping = Mapping{};
|
||||||
|
|
||||||
|
try std.testing.expectEqual(mapping.axis_strength(axis), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var mapping = Mapping{};
|
||||||
|
|
||||||
|
mapping.keys_held.set(@intFromEnum(Key.equal));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(mapping.axis_strength(axis), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var mapping = Mapping{};
|
||||||
|
|
||||||
|
mapping.keys_held.set(@intFromEnum(Key.minus));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(mapping.axis_strength(axis), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
var mapping = Mapping{};
|
||||||
|
|
||||||
|
mapping.keys_held.set(@intFromEnum(Key.minus));
|
||||||
|
mapping.keys_held.set(@intFromEnum(Key.equal));
|
||||||
|
|
||||||
|
try std.testing.expectEqual(mapping.axis_strength(axis), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(world: *ona.World, events: ona.App.Events) std.mem.Allocator.Error!void {
|
||||||
|
try world.set_state(Mapping{});
|
||||||
|
|
||||||
|
try world.on_event(events.pre_update, ona.system_fn(update), .{
|
||||||
|
.label = "update actions",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(inputs: ona.msg.Receive(ona.gfx.Input), mapping: ona.Write(Mapping)) void {
|
||||||
|
mapping.state.keys_pressed = Mapping.ScancodeSet.initEmpty();
|
||||||
|
|
||||||
|
for (inputs.messages()) |message| {
|
||||||
|
switch (message) {
|
||||||
|
.key_down => |key| {
|
||||||
|
mapping.state.keys_pressed.set(key.scancode());
|
||||||
|
mapping.state.keys_held.set(key.scancode());
|
||||||
|
},
|
||||||
|
|
||||||
|
.key_up => |key| {
|
||||||
|
mapping.state.keys_held.unset(key.scancode());
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
124
src/main.zig
124
src/main.zig
|
@ -1,124 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
const ona = @import("ona");
|
|
||||||
|
|
||||||
const ChromaticAberration = extern struct {
|
|
||||||
effect_magnitude: f32,
|
|
||||||
padding: [12]u8 = undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const CRT = extern struct {
|
|
||||||
width: f32,
|
|
||||||
height: f32,
|
|
||||||
time: f32,
|
|
||||||
padding: [4]u8 = undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Actors = struct {
|
|
||||||
instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator},
|
|
||||||
body_texture: ona.gfx.Texture = .default,
|
|
||||||
render_texture: ona.gfx.Texture = .default,
|
|
||||||
ca_effect: ona.gfx.Effect = .default,
|
|
||||||
crt_effect: ona.gfx.Effect = .default,
|
|
||||||
staging_texture: ona.gfx.Texture = .default,
|
|
||||||
};
|
|
||||||
|
|
||||||
const Player = struct {
|
|
||||||
move_x: ona.act.Axis = .{.keys = .{.a, .d}},
|
|
||||||
move_y: ona.act.Axis = .{.keys = .{.w, .s}},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const main = ona.start(setup, .{
|
|
||||||
.tick_rate = 60,
|
|
||||||
.execution = .{.thread_share = 0.1},
|
|
||||||
});
|
|
||||||
|
|
||||||
fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void {
|
|
||||||
config.res.width, config.res.height = .{1280, 720};
|
|
||||||
actors.res.body_texture = try assets.res.load_texture_file(coral.files.bundle, "actor.bmp");
|
|
||||||
|
|
||||||
actors.res.render_texture = try assets.res.load_texture(.{
|
|
||||||
.format = .rgba8,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.render = .{
|
|
||||||
.width = config.res.width,
|
|
||||||
.height = config.res.height,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
actors.res.ca_effect = try assets.res.load_effect_file(coral.files.bundle, "./ca.frag.spv");
|
|
||||||
actors.res.crt_effect = try assets.res.load_effect_file(coral.files.bundle, "./crt.frag.spv");
|
|
||||||
|
|
||||||
try actors.res.instances.push_grow(.{0, 0});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn exit(actors: ona.Write(Actors)) void {
|
|
||||||
actors.res.instances.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), app: ona.Read(ona.App)) !void {
|
|
||||||
try commands.set_target(.{
|
|
||||||
.texture = actors.res.render_texture,
|
|
||||||
.clear_color = ona.gfx.colors.black,
|
|
||||||
.clear_depth = 0,
|
|
||||||
.clear_stencil = 0,
|
|
||||||
});
|
|
||||||
|
|
||||||
try commands.draw_texture(.{
|
|
||||||
.texture = .default,
|
|
||||||
|
|
||||||
.transform = .{
|
|
||||||
.origin = .{1280 / 2, 720 / 2},
|
|
||||||
.xbasis = .{1280, 0},
|
|
||||||
.ybasis = .{0, 720},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
try commands.set_effect(.{
|
|
||||||
.effect = actors.res.crt_effect,
|
|
||||||
|
|
||||||
.properties = std.mem.asBytes(&CRT{
|
|
||||||
.width = 1280,
|
|
||||||
.height = 720,
|
|
||||||
.time = @floatCast(app.res.elapsed_time),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
try commands.set_target(.{
|
|
||||||
.texture = .backbuffer,
|
|
||||||
.clear_color = null,
|
|
||||||
.clear_depth = null,
|
|
||||||
.clear_stencil = null,
|
|
||||||
});
|
|
||||||
|
|
||||||
try commands.draw_texture(.{
|
|
||||||
.texture = actors.res.render_texture,
|
|
||||||
|
|
||||||
.transform = .{
|
|
||||||
.origin = .{1280 / 2, 720 / 2},
|
|
||||||
.xbasis = .{1280, 0},
|
|
||||||
.ybasis = .{0, 720},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void {
|
|
||||||
actors.res.instances.values[0] += .{
|
|
||||||
mapping.res.axis_strength(player.res.move_x) * 10,
|
|
||||||
mapping.res.axis_strength(player.res.move_y) * 10,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn setup(world: *ona.World, events: ona.App.Events) !void {
|
|
||||||
try world.set_state(Actors{});
|
|
||||||
try world.set_state(Player{});
|
|
||||||
|
|
||||||
try world.on_event(events.load, ona.system_fn(load), .{.label = "load"});
|
|
||||||
try world.on_event(events.update, ona.system_fn(update), .{.label = "update"});
|
|
||||||
try world.on_event(events.exit, ona.system_fn(exit), .{.label = "exit"});
|
|
||||||
try world.on_event(events.render, ona.system_fn(render), .{.label = "render actors"});
|
|
||||||
}
|
|
|
@ -1,21 +1,19 @@
|
||||||
const coral = @import("coral");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const dag = @import("./dag.zig");
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
const states = @import("./states.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
const World = @import("./World.zig");
|
thread_pool: ?*std.Thread.Pool = null,
|
||||||
|
thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable,
|
||||||
|
event_systems: ona.stack.Sequential(Schedule),
|
||||||
|
|
||||||
pub const BindContext = struct {
|
pub const BindContext = struct {
|
||||||
node: dag.Node,
|
node: ona.dag.Node,
|
||||||
systems: *Schedule,
|
systems: *Schedule,
|
||||||
world: *World,
|
world: *Self,
|
||||||
|
|
||||||
pub const ResourceAccess = std.meta.Tag(Schedule.ResourceAccess);
|
pub fn accesses_state(self: BindContext, access: std.meta.Tag(StateAccess), id: ona.TypeID) bool {
|
||||||
|
|
||||||
pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: states.TypeID) bool {
|
|
||||||
const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses;
|
const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses;
|
||||||
|
|
||||||
for (resource_accesses.values) |resource_access| {
|
for (resource_accesses.values) |resource_access| {
|
||||||
|
@ -42,23 +40,23 @@ pub const BindContext = struct {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const id = states.type_id(Resource);
|
const id = ona.type_id(Resource);
|
||||||
|
|
||||||
if (!self.accesses_resource(.read_write, id)) {
|
if (!self.accesses_state(.read_write, id)) {
|
||||||
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id});
|
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id});
|
||||||
}
|
}
|
||||||
|
|
||||||
const read_write_resource_nodes = lazily_create: {
|
const read_write_resource_nodes = lazily_create: {
|
||||||
break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: {
|
break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: {
|
||||||
std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
|
std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
|
||||||
.allocator = coral.heap.allocator,
|
.allocator = ona.heap.allocator,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
break: insert self.systems.read_write_resource_id_nodes.get(id).?;
|
break: insert self.systems.read_write_resource_id_nodes.get(id).?;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (std.mem.indexOfScalar(dag.Node, read_write_resource_nodes.values, self.node) == null) {
|
if (std.mem.indexOfScalar(ona.dag.Node, read_write_resource_nodes.values, self.node) == null) {
|
||||||
try read_write_resource_nodes.push_grow(self.node);
|
try read_write_resource_nodes.push_grow(self.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,23 +68,23 @@ pub const BindContext = struct {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const id = states.type_id(Resource);
|
const id = ona.type_id(Resource);
|
||||||
|
|
||||||
if (!self.accesses_resource(.read_only, id)) {
|
if (!self.accesses_state(.read_only, id)) {
|
||||||
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id});
|
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id});
|
||||||
}
|
}
|
||||||
|
|
||||||
const read_only_resource_nodes = lazily_create: {
|
const read_only_resource_nodes = lazily_create: {
|
||||||
break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: {
|
break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: {
|
||||||
std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
|
std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
|
||||||
.allocator = coral.heap.allocator,
|
.allocator = ona.heap.allocator,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
break: insert self.systems.read_only_resource_id_nodes.get(id).?;
|
break: insert self.systems.read_only_resource_id_nodes.get(id).?;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (std.mem.indexOfScalar(dag.Node, read_only_resource_nodes.values, self.node) == null) {
|
if (std.mem.indexOfScalar(ona.dag.Node, read_only_resource_nodes.values, self.node) == null) {
|
||||||
try read_only_resource_nodes.push_grow(self.node);
|
try read_only_resource_nodes.push_grow(self.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,70 +92,48 @@ pub const BindContext = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Info = struct {
|
const Dependency = struct {
|
||||||
execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void,
|
kind: Kind,
|
||||||
parameters: [max_parameters]*const Parameter = undefined,
|
id: usize,
|
||||||
parameter_count: u4 = 0,
|
|
||||||
thread_restriction: states.ThreadRestriction = .none,
|
|
||||||
|
|
||||||
pub const Parameter = struct {
|
const Kind = enum {
|
||||||
thread_restriction: states.ThreadRestriction,
|
after,
|
||||||
init: *const fn (*anyopaque, *anyopaque) void,
|
before,
|
||||||
bind: *const fn (std.mem.Allocator, BindContext) std.mem.Allocator.Error!*anyopaque,
|
|
||||||
unbind: *const fn (std.mem.Allocator, *anyopaque, UnbindContext) void,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn used_parameters(self: *const Info) []const *const Parameter {
|
|
||||||
return self.parameters[0 .. self.parameter_count];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Order = struct {
|
const Graph = ona.dag.Graph(struct {
|
||||||
label: []const u8 = "",
|
info: *const ona.SystemInfo,
|
||||||
run_after: []const *const Info = &.{},
|
label: [:0]u8,
|
||||||
run_before: []const *const Info = &.{},
|
dependencies: []Dependency,
|
||||||
|
parameter_states: [ona.SystemInfo.max_parameters]*anyopaque = [_]*anyopaque{undefined} ** ona.SystemInfo.max_parameters,
|
||||||
|
resource_accesses: ona.stack.Sequential(StateAccess),
|
||||||
|
});
|
||||||
|
|
||||||
|
pub const Event = enum (usize) { _ };
|
||||||
|
|
||||||
|
const ParallelNodeBundles = ona.stack.Sequential(NodeBundle);
|
||||||
|
|
||||||
|
const NodeBundle = ona.stack.Sequential(ona.dag.Node);
|
||||||
|
|
||||||
|
const StateAccess = union (enum) {
|
||||||
|
read_only: ona.TypeID,
|
||||||
|
read_write: ona.TypeID,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Schedule = struct {
|
const ResourceNodeBundle = ona.map.Hashed(ona.TypeID, NodeBundle, ona.map.enum_traits(ona.TypeID));
|
||||||
|
|
||||||
|
const Schedule = struct {
|
||||||
label: [:0]const u8,
|
label: [:0]const u8,
|
||||||
graph: Graph,
|
graph: Graph,
|
||||||
arena: std.heap.ArenaAllocator,
|
arena: std.heap.ArenaAllocator,
|
||||||
system_id_nodes: coral.map.Hashed(usize, NodeBundle, coral.map.usize_traits),
|
system_id_nodes: ona.map.Hashed(usize, NodeBundle, ona.map.usize_traits),
|
||||||
read_write_resource_id_nodes: ResourceNodeBundle,
|
read_write_resource_id_nodes: ResourceNodeBundle,
|
||||||
read_only_resource_id_nodes: ResourceNodeBundle,
|
read_only_resource_id_nodes: ResourceNodeBundle,
|
||||||
parallel_work_bundles: ParallelNodeBundles,
|
parallel_work_bundles: ParallelNodeBundles,
|
||||||
blocking_work: NodeBundle,
|
blocking_work: NodeBundle,
|
||||||
|
|
||||||
const Dependency = struct {
|
pub fn deinit(self: *Schedule, world: *Self) void {
|
||||||
kind: Kind,
|
|
||||||
id: usize,
|
|
||||||
|
|
||||||
const Kind = enum {
|
|
||||||
after,
|
|
||||||
before,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const Graph = dag.Graph(struct {
|
|
||||||
info: *const Info,
|
|
||||||
label: [:0]u8,
|
|
||||||
dependencies: []Dependency,
|
|
||||||
parameter_states: [max_parameters]*anyopaque = [_]*anyopaque{undefined} ** max_parameters,
|
|
||||||
resource_accesses: coral.stack.Sequential(ResourceAccess),
|
|
||||||
});
|
|
||||||
|
|
||||||
const NodeBundle = coral.stack.Sequential(dag.Node);
|
|
||||||
|
|
||||||
const ParallelNodeBundles = coral.stack.Sequential(NodeBundle);
|
|
||||||
|
|
||||||
const ResourceAccess = union (enum) {
|
|
||||||
read_only: states.TypeID,
|
|
||||||
read_write: states.TypeID,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ResourceNodeBundle = coral.map.Hashed(states.TypeID, NodeBundle, coral.map.enum_traits(states.TypeID));
|
|
||||||
|
|
||||||
pub fn deinit(self: *Schedule, world: *World) void {
|
|
||||||
{
|
{
|
||||||
var nodes = self.system_id_nodes.entries();
|
var nodes = self.system_id_nodes.entries();
|
||||||
|
|
||||||
|
@ -194,8 +170,8 @@ pub const Schedule = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
system.resource_accesses.deinit();
|
system.resource_accesses.deinit();
|
||||||
coral.heap.allocator.free(system.dependencies);
|
ona.heap.allocator.free(system.dependencies);
|
||||||
coral.heap.allocator.free(system.label);
|
ona.heap.allocator.free(system.label);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (self.parallel_work_bundles.values) |*bundle| {
|
for (self.parallel_work_bundles.values) |*bundle| {
|
||||||
|
@ -211,7 +187,7 @@ pub const Schedule = struct {
|
||||||
self.arena.deinit();
|
self.arena.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(self: *Schedule, world: *World) anyerror!void {
|
pub fn run(self: *Schedule, world: *Self) anyerror!void {
|
||||||
if (self.is_invalidated()) {
|
if (self.is_invalidated()) {
|
||||||
const work = struct {
|
const work = struct {
|
||||||
fn regenerate_graph(schedule: *Schedule) !void {
|
fn regenerate_graph(schedule: *Schedule) !void {
|
||||||
|
@ -276,7 +252,7 @@ pub const Schedule = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: dag.Node) !void {
|
fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: ona.dag.Node) !void {
|
||||||
std.debug.assert(graph.mark_visited(node));
|
std.debug.assert(graph.mark_visited(node));
|
||||||
|
|
||||||
for (graph.edge_nodes(node).?) |edge| {
|
for (graph.edge_nodes(node).?) |edge| {
|
||||||
|
@ -300,7 +276,7 @@ pub const Schedule = struct {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try schedule.parallel_work_bundles.push_grow(.{.allocator = coral.heap.allocator});
|
try schedule.parallel_work_bundles.push_grow(.{.allocator = ona.heap.allocator});
|
||||||
|
|
||||||
const bundle = schedule.parallel_work_bundles.get_ptr().?;
|
const bundle = schedule.parallel_work_bundles.get_ptr().?;
|
||||||
|
|
||||||
|
@ -381,21 +357,21 @@ pub const Schedule = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule {
|
pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule {
|
||||||
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
var arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
|
||||||
|
|
||||||
errdefer arena.deinit();
|
errdefer arena.deinit();
|
||||||
|
|
||||||
const duped_label = try arena.allocator().dupeZ(u8, label);
|
const duped_label = try arena.allocator().dupeZ(u8, label);
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
.graph = Graph.init(coral.heap.allocator),
|
.graph = Graph.init(ona.heap.allocator),
|
||||||
.label = duped_label,
|
.label = duped_label,
|
||||||
.arena = arena,
|
.arena = arena,
|
||||||
.system_id_nodes = .{.allocator = coral.heap.allocator},
|
.system_id_nodes = .{.allocator = ona.heap.allocator},
|
||||||
.read_write_resource_id_nodes = .{.allocator = coral.heap.allocator},
|
.read_write_resource_id_nodes = .{.allocator = ona.heap.allocator},
|
||||||
.read_only_resource_id_nodes = .{.allocator = coral.heap.allocator},
|
.read_only_resource_id_nodes = .{.allocator = ona.heap.allocator},
|
||||||
.parallel_work_bundles = .{.allocator = coral.heap.allocator},
|
.parallel_work_bundles = .{.allocator = ona.heap.allocator},
|
||||||
.blocking_work = .{.allocator = coral.heap.allocator},
|
.blocking_work = .{.allocator = ona.heap.allocator},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -413,7 +389,7 @@ pub const Schedule = struct {
|
||||||
return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty();
|
return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn then(self: *Schedule, world: *World, info: *const Info, order: Order) std.mem.Allocator.Error!void {
|
pub fn then(self: *Schedule, world: *Self, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
|
||||||
const nodes = lazily_create: {
|
const nodes = lazily_create: {
|
||||||
const system_id = @intFromPtr(info);
|
const system_id = @intFromPtr(info);
|
||||||
|
|
||||||
|
@ -428,7 +404,7 @@ pub const Schedule = struct {
|
||||||
|
|
||||||
const dependencies = init: {
|
const dependencies = init: {
|
||||||
const total_run_orders = order.run_after.len + order.run_before.len;
|
const total_run_orders = order.run_after.len + order.run_before.len;
|
||||||
const dependencies = try coral.heap.allocator.alloc(Dependency, total_run_orders);
|
const dependencies = try ona.heap.allocator.alloc(Dependency, total_run_orders);
|
||||||
var dependencies_written = @as(usize, 0);
|
var dependencies_written = @as(usize, 0);
|
||||||
|
|
||||||
for (order.run_after) |after_system| {
|
for (order.run_after) |after_system| {
|
||||||
|
@ -452,17 +428,17 @@ pub const Schedule = struct {
|
||||||
break: init dependencies;
|
break: init dependencies;
|
||||||
};
|
};
|
||||||
|
|
||||||
errdefer coral.heap.allocator.free(dependencies);
|
errdefer ona.heap.allocator.free(dependencies);
|
||||||
|
|
||||||
const label = try coral.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
|
const label = try ona.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
|
||||||
|
|
||||||
errdefer coral.heap.allocator.free(label);
|
errdefer ona.heap.allocator.free(label);
|
||||||
|
|
||||||
const node = try self.graph.append(.{
|
const node = try self.graph.append(.{
|
||||||
.info = info,
|
.info = info,
|
||||||
.label = label,
|
.label = label,
|
||||||
.dependencies = dependencies,
|
.dependencies = dependencies,
|
||||||
.resource_accesses = .{.allocator = coral.heap.allocator},
|
.resource_accesses = .{.allocator = ona.heap.allocator},
|
||||||
});
|
});
|
||||||
|
|
||||||
const system = self.graph.get_ptr(node).?;
|
const system = self.graph.get_ptr(node).?;
|
||||||
|
@ -491,8 +467,144 @@ pub const Schedule = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const UnbindContext = struct {
|
const Self = @This();
|
||||||
world: *World,
|
|
||||||
|
const StateTable = struct {
|
||||||
|
arena: std.heap.ArenaAllocator,
|
||||||
|
table: ona.map.Hashed(ona.TypeID, Entry, ona.map.enum_traits(ona.TypeID)),
|
||||||
|
|
||||||
|
const Entry = struct {
|
||||||
|
ptr: *anyopaque,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn deinit(self: *StateTable) void {
|
||||||
|
self.table.deinit();
|
||||||
|
self.arena.deinit();
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(self: StateTable, comptime Resource: type) ?*Resource {
|
||||||
|
if (self.table.get(ona.type_id(Resource))) |entry| {
|
||||||
|
return @ptrCast(@alignCast(entry.ptr));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init() StateTable {
|
||||||
|
return .{
|
||||||
|
.arena = std.heap.ArenaAllocator.init(ona.heap.allocator),
|
||||||
|
.table = .{.allocator = ona.heap.allocator},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_get(self: *StateTable, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
|
||||||
|
try self.set(value);
|
||||||
|
|
||||||
|
return self.get(@TypeOf(value)).?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set(self: *StateTable, value: anytype) std.mem.Allocator.Error!void {
|
||||||
|
const Value = @TypeOf(value);
|
||||||
|
const value_id = ona.type_id(Value);
|
||||||
|
|
||||||
|
if (self.table.get(value_id)) |entry| {
|
||||||
|
@as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value;
|
||||||
|
} else {
|
||||||
|
const resource_allocator = self.arena.allocator();
|
||||||
|
const allocated_resource = try resource_allocator.create(Value);
|
||||||
|
|
||||||
|
errdefer resource_allocator.destroy(allocated_resource);
|
||||||
|
|
||||||
|
std.debug.assert(try self.table.emplace(value_id, .{
|
||||||
|
.ptr = allocated_resource,
|
||||||
|
}));
|
||||||
|
|
||||||
|
allocated_resource.* = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const max_parameters = 16;
|
pub const UnbindContext = struct {
|
||||||
|
world: *Self,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event {
|
||||||
|
var systems = try Schedule.init(label);
|
||||||
|
|
||||||
|
errdefer systems.deinit(self);
|
||||||
|
|
||||||
|
const index = self.event_systems.len();
|
||||||
|
|
||||||
|
try self.event_systems.push_grow(systems);
|
||||||
|
|
||||||
|
return @enumFromInt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
for (self.event_systems.values) |*schedule| {
|
||||||
|
schedule.deinit(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (&self.thread_restricted_resources) |*resources| {
|
||||||
|
resources.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self.thread_pool) |thread_pool| {
|
||||||
|
thread_pool.deinit();
|
||||||
|
ona.heap.allocator.destroy(thread_pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.event_systems.deinit();
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_params(self: Self, comptime Value: type) ona.Params(Value) {
|
||||||
|
const params = self.get_state(ona.Params(Value)) orelse {
|
||||||
|
return .{};
|
||||||
|
};
|
||||||
|
|
||||||
|
return params.*;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_state(self: Self, comptime Value: type) ?*Value {
|
||||||
|
return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(Value))].get(Value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
|
||||||
|
return self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(@TypeOf(value)))].set_get(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
|
||||||
|
var world = Self{
|
||||||
|
.thread_restricted_resources = .{StateTable.init(), StateTable.init()},
|
||||||
|
.event_systems = .{.allocator = ona.heap.allocator},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (thread_count != 0 and !builtin.single_threaded) {
|
||||||
|
const thread_pool = try ona.heap.allocator.create(std.Thread.Pool);
|
||||||
|
|
||||||
|
try thread_pool.init(.{
|
||||||
|
.allocator = ona.heap.allocator,
|
||||||
|
.n_jobs = thread_count,
|
||||||
|
});
|
||||||
|
|
||||||
|
world.thread_pool = thread_pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
return world;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_event(self: *Self, event: Event, action: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
|
||||||
|
try self.event_systems.values[@intFromEnum(event)].then(self, action, order);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_event(self: *Self, event: Event) anyerror!void {
|
||||||
|
try self.event_systems.values[@intFromEnum(event)].run(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void {
|
||||||
|
try self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(@TypeOf(value)))].set(value);
|
||||||
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const flow = @import("flow");
|
|
||||||
|
|
||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Axis = struct {
|
|
||||||
keys: ?[2]ona.gfx.Input.Key = null,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Mapping = struct {
|
|
||||||
keys_pressed: ScancodeSet = ScancodeSet.initEmpty(),
|
|
||||||
keys_held: ScancodeSet = ScancodeSet.initEmpty(),
|
|
||||||
|
|
||||||
const ScancodeSet = std.bit_set.StaticBitSet(512);
|
|
||||||
|
|
||||||
pub fn axis_strength(self: Mapping, axis: Axis) f32 {
|
|
||||||
if (axis.keys) |keys| {
|
|
||||||
const key_down, const key_up = keys;
|
|
||||||
const is_key_down_held = self.keys_held.isSet(key_down.scancode());
|
|
||||||
const is_key_up_held = self.keys_held.isSet(key_up.scancode());
|
|
||||||
|
|
||||||
if (is_key_down_held or is_key_up_held) {
|
|
||||||
return
|
|
||||||
@as(f32, @floatFromInt(@intFromBool(is_key_up_held))) -
|
|
||||||
@as(f32, @floatFromInt(@intFromBool(is_key_down_held)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn setup(world: *flow.World, events: ona.App.Events) std.mem.Allocator.Error!void {
|
|
||||||
try world.set_state(Mapping{});
|
|
||||||
|
|
||||||
try world.on_event(events.pre_update, flow.system_fn(update), .{
|
|
||||||
.label = "update act",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update(inputs: ona.msg.Receive(ona.gfx.Input), mapping: flow.Write(Mapping)) void {
|
|
||||||
mapping.res.keys_pressed = Mapping.ScancodeSet.initEmpty();
|
|
||||||
|
|
||||||
for (inputs.messages()) |message| {
|
|
||||||
switch (message) {
|
|
||||||
.key_down => |key| {
|
|
||||||
mapping.res.keys_pressed.set(key.scancode());
|
|
||||||
mapping.res.keys_held.set(key.scancode());
|
|
||||||
},
|
|
||||||
|
|
||||||
.key_up => |key| {
|
|
||||||
mapping.res.keys_held.unset(key.scancode());
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +1,10 @@
|
||||||
const coral = @import("./coral.zig");
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
const io = @import("./io.zig");
|
|
||||||
|
|
||||||
const scalars = @import("./scalars.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const DecimalFormat = struct {
|
pub const DecimalFormat = struct {
|
||||||
delimiter: []const coral.io.Byte,
|
delimiter: []const u8 = "",
|
||||||
positive_prefix: enum {none, plus, space},
|
positive_prefix: enum {none, plus, space} = .none,
|
||||||
|
|
||||||
pub const default = DecimalFormat{
|
|
||||||
.delimiter = "",
|
|
||||||
.positive_prefix = .none,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn parse(self: DecimalFormat, utf8: []const u8, comptime Decimal: type) ?Decimal {
|
pub fn parse(self: DecimalFormat, utf8: []const u8, comptime Decimal: type) ?Decimal {
|
||||||
if (utf8.len == 0) {
|
if (utf8.len == 0) {
|
||||||
|
@ -35,14 +26,14 @@ pub const DecimalFormat = struct {
|
||||||
|
|
||||||
switch (code) {
|
switch (code) {
|
||||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
|
||||||
const offset_code = scalars.sub(code, '0') orelse return null;
|
const offset_code = ona.scalars.sub(code, '0') orelse return null;
|
||||||
|
|
||||||
result = scalars.mul(result, radix) orelse return null;
|
result = ona.scalars.mul(result, radix) orelse return null;
|
||||||
result = scalars.add(result, offset_code) orelse return null;
|
result = ona.scalars.add(result, offset_code) orelse return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
else => {
|
else => {
|
||||||
if (self.delimiter.len == 0 or !coral.are_equal(self.delimiter, utf8[index ..])) {
|
if (self.delimiter.len == 0 or !std.mem.eql(u8, self.delimiter, utf8[index ..])) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -115,9 +106,9 @@ pub const DecimalFormat = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.Error!void {
|
pub fn format(self: DecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void {
|
||||||
if (value == 0) {
|
if (value == 0) {
|
||||||
return io.write_all(writer, switch (self.positive_prefix) {
|
return ona.io.write_all(writer, switch (self.positive_prefix) {
|
||||||
.none => "0",
|
.none => "0",
|
||||||
.plus => "+0",
|
.plus => "+0",
|
||||||
.space => " 0",
|
.space => " 0",
|
||||||
|
@ -151,12 +142,12 @@ pub const DecimalFormat = struct {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return io.write_all(writer, buffer[buffer_start ..]);
|
return ona.io.write_all(writer, buffer[buffer_start ..]);
|
||||||
},
|
},
|
||||||
|
|
||||||
.Float => |float| {
|
.Float => |float| {
|
||||||
if (value < 0) {
|
if (value < 0) {
|
||||||
try io.write_all(writer, "-");
|
try ona.io.write_all(writer, "-");
|
||||||
}
|
}
|
||||||
|
|
||||||
const Float = @TypeOf(value);
|
const Float = @TypeOf(value);
|
||||||
|
@ -164,7 +155,7 @@ pub const DecimalFormat = struct {
|
||||||
const integer = @as(Int, @intFromFloat(value));
|
const integer = @as(Int, @intFromFloat(value));
|
||||||
|
|
||||||
try self.format(writer, integer);
|
try self.format(writer, integer);
|
||||||
try io.write_all(writer, ".");
|
try ona.io.write_all(writer, ".");
|
||||||
try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
|
try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -173,6 +164,12 @@ pub const DecimalFormat = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test "decimal parsing" {
|
||||||
|
const format = DecimalFormat{};
|
||||||
|
|
||||||
|
try std.testing.expectEqual(format.parse("69", i64), 69);
|
||||||
|
}
|
||||||
|
|
||||||
pub const HexadecimalFormat = struct {
|
pub const HexadecimalFormat = struct {
|
||||||
delimiter: []const u8 = "",
|
delimiter: []const u8 = "",
|
||||||
positive_prefix: enum {none, plus, space} = .none,
|
positive_prefix: enum {none, plus, space} = .none,
|
||||||
|
@ -184,7 +181,7 @@ pub const HexadecimalFormat = struct {
|
||||||
.casing = .lower,
|
.casing = .lower,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn format(self: HexadecimalFormat, writer: io.Writer, value: anytype) io.Error!void {
|
pub fn format(self: HexadecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void {
|
||||||
// TODO: Implement.
|
// TODO: Implement.
|
||||||
_ = self;
|
_ = self;
|
||||||
_ = writer;
|
_ = writer;
|
|
@ -1,4 +1,4 @@
|
||||||
const coral = @import("coral");
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
@ -7,9 +7,9 @@ pub fn Graph(comptime Payload: type) type {
|
||||||
node_count: usize = 0,
|
node_count: usize = 0,
|
||||||
table: NodeTables,
|
table: NodeTables,
|
||||||
|
|
||||||
const NodeTables = coral.stack.Parallel(struct {
|
const NodeTables = ona.stack.Parallel(struct {
|
||||||
payload: Payload,
|
payload: Payload,
|
||||||
edges: coral.stack.Sequential(Node),
|
edges: ona.stack.Sequential(Node),
|
||||||
is_occupied: bool = true,
|
is_occupied: bool = true,
|
||||||
is_visited: bool = false,
|
is_visited: bool = false,
|
||||||
});
|
});
|
|
@ -1,4 +0,0 @@
|
||||||
|
|
||||||
pub usingnamespace @cImport({
|
|
||||||
@cInclude("SDL2/SDL.h");
|
|
||||||
});
|
|
|
@ -28,14 +28,14 @@ pub const Storage = struct {
|
||||||
|
|
||||||
pub const VTable = struct {
|
pub const VTable = struct {
|
||||||
stat: *const fn (*anyopaque, []const u8) AccessError!Stat,
|
stat: *const fn (*anyopaque, []const u8) AccessError!Stat,
|
||||||
read: *const fn (*anyopaque, []const u8, usize, []io.Byte) AccessError!usize,
|
read: *const fn (*anyopaque, []const u8, usize, []u8) AccessError!usize,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn read(self: Storage, path: []const u8, output: []io.Byte, offset: u64) AccessError!usize {
|
pub fn read(self: Storage, path: []const u8, output: []u8, offset: u64) AccessError!usize {
|
||||||
return self.vtable.read(self.userdata, path, offset, output);
|
return self.vtable.read(self.userdata, path, offset, output);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_all(self: Storage, path: []const u8, output: []io.Byte, options: ReadAllOptions) ReadAllError![]const io.Byte {
|
pub fn read_all(self: Storage, path: []const u8, output: []u8, options: ReadAllOptions) ReadAllError![]const u8 {
|
||||||
const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output);
|
const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output);
|
||||||
|
|
||||||
if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) {
|
if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) {
|
||||||
|
@ -45,8 +45,8 @@ pub const Storage = struct {
|
||||||
return output[0 .. bytes_read];
|
return output[0 .. bytes_read];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]io.Byte {
|
pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]u8 {
|
||||||
const buffer = try allocator.alloc(io.Byte, @min((try self.stat(path)).size, options.limit));
|
const buffer = try allocator.alloc(u8, @min((try self.stat(path)).size, options.limit));
|
||||||
|
|
||||||
errdefer allocator.free(buffer);
|
errdefer allocator.free(buffer);
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ pub const Storage = struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
|
pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
|
||||||
var buffer = @as([@sizeOf(Type)]io.Byte, undefined);
|
var buffer = @as([@sizeOf(Type)]u8, undefined);
|
||||||
|
|
||||||
if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
|
if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -105,7 +105,7 @@ pub const bundle = init: {
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) AccessError!usize {
|
fn read(_: *anyopaque, path: []const u8, offset: usize, output: []u8) AccessError!usize {
|
||||||
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
|
||||||
return switch (open_error) {
|
return switch (open_error) {
|
||||||
error.FileNotFound => error.FileNotFound,
|
error.FileNotFound => error.FileNotFound,
|
534
src/ona/gfx.zig
534
src/ona/gfx.zig
|
@ -1,534 +0,0 @@
|
||||||
pub const colors = @import("./gfx/colors.zig");
|
|
||||||
|
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const ext = @import("./ext.zig");
|
|
||||||
|
|
||||||
const flow = @import("flow");
|
|
||||||
|
|
||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const rendering = @import("./gfx/rendering.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
pub const Assets = struct {
|
|
||||||
window: *ext.SDL_Window,
|
|
||||||
texture_formats: coral.stack.Sequential(TextureFormat),
|
|
||||||
frame_rendered: std.Thread.ResetEvent = .{},
|
|
||||||
pending_work: WorkQueue = .{},
|
|
||||||
has_worker_thread: ?std.Thread = null,
|
|
||||||
|
|
||||||
pub const LoadError = std.mem.Allocator.Error;
|
|
||||||
|
|
||||||
pub const LoadFileError = LoadError || coral.files.ReadAllError || error {
|
|
||||||
FormatUnsupported,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const TextureFormat = struct {
|
|
||||||
extension: []const u8,
|
|
||||||
load_file: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) LoadFileError!Texture.Desc,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const WorkQueue = coral.asyncio.BlockingQueue(1024, union (enum) {
|
|
||||||
load_effect: LoadEffectWork,
|
|
||||||
load_texture: LoadTextureWork,
|
|
||||||
render_frame: RenderFrameWork,
|
|
||||||
shutdown,
|
|
||||||
unload_effect: UnloadEffectWork,
|
|
||||||
unload_texture: UnloadTextureWork,
|
|
||||||
|
|
||||||
const LoadEffectWork = struct {
|
|
||||||
desc: Effect.Desc,
|
|
||||||
loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Effect),
|
|
||||||
};
|
|
||||||
|
|
||||||
const LoadTextureWork = struct {
|
|
||||||
desc: Texture.Desc,
|
|
||||||
loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Texture),
|
|
||||||
};
|
|
||||||
|
|
||||||
const RenderFrameWork = struct {
|
|
||||||
clear_color: Color,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
finished: *std.Thread.ResetEvent,
|
|
||||||
has_command_params: ?*flow.Params(Commands).Node,
|
|
||||||
};
|
|
||||||
|
|
||||||
const UnloadEffectWork = struct {
|
|
||||||
handle: Effect,
|
|
||||||
};
|
|
||||||
|
|
||||||
const UnloadTextureWork = struct {
|
|
||||||
handle: Texture,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
fn deinit(self: *Assets) void {
|
|
||||||
self.pending_work.enqueue(.shutdown);
|
|
||||||
|
|
||||||
if (self.has_worker_thread) |worker_thread| {
|
|
||||||
worker_thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.texture_formats.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init() !Assets {
|
|
||||||
const window = create: {
|
|
||||||
const position = ext.SDL_WINDOWPOS_CENTERED;
|
|
||||||
const flags = ext.SDL_WINDOW_OPENGL;
|
|
||||||
const width = 640;
|
|
||||||
const height = 480;
|
|
||||||
|
|
||||||
break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
|
|
||||||
return error.Unsupported;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
ext.SDL_DestroyWindow(window);
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.texture_formats = .{.allocator = coral.heap.allocator},
|
|
||||||
.window = window,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_effect_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Effect {
|
|
||||||
if (!std.mem.endsWith(u8, path, ".spv")) {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
const fragment_file_stat = try storage.stat(path);
|
|
||||||
const fragment_spirv_ops = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
|
|
||||||
|
|
||||||
defer {
|
|
||||||
coral.heap.allocator.free(fragment_spirv_ops);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{});
|
|
||||||
|
|
||||||
std.debug.assert(bytes_read.len == fragment_file_stat.size);
|
|
||||||
|
|
||||||
var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){};
|
|
||||||
|
|
||||||
self.pending_work.enqueue(.{
|
|
||||||
.load_effect = .{
|
|
||||||
.desc = .{
|
|
||||||
.fragment_spirv_ops = fragment_spirv_ops,
|
|
||||||
},
|
|
||||||
|
|
||||||
.loaded = &loaded,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return loaded.get().*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_texture(self: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture {
|
|
||||||
var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Texture){};
|
|
||||||
|
|
||||||
self.pending_work.enqueue(.{
|
|
||||||
.load_texture = .{
|
|
||||||
.desc = desc,
|
|
||||||
.loaded = &loaded,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return loaded.get().*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_texture_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Texture {
|
|
||||||
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
|
||||||
|
|
||||||
defer {
|
|
||||||
arena.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
for (self.texture_formats.values) |format| {
|
|
||||||
if (!std.mem.endsWith(u8, path, format.extension)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.load_texture(try format.load_file(&arena, storage, path));
|
|
||||||
}
|
|
||||||
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const thread_restriction = .main;
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Color = @Vector(4, f32);
|
|
||||||
|
|
||||||
pub const Commands = struct {
|
|
||||||
pending: *List,
|
|
||||||
|
|
||||||
const Command = union (enum) {
|
|
||||||
draw_texture: DrawTextureCommand,
|
|
||||||
set_effect: SetEffectCommand,
|
|
||||||
set_target: SetTargetCommand,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const DrawTextureCommand = struct {
|
|
||||||
texture: Texture,
|
|
||||||
transform: Transform2D,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SetEffectCommand = struct {
|
|
||||||
effect: Effect,
|
|
||||||
properties: []const coral.io.Byte,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const SetTargetCommand = struct {
|
|
||||||
texture: Texture,
|
|
||||||
clear_color: ?Color,
|
|
||||||
clear_depth: ?f32,
|
|
||||||
clear_stencil: ?u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const List = struct {
|
|
||||||
arena: std.heap.ArenaAllocator,
|
|
||||||
stack: coral.stack.Sequential(Command),
|
|
||||||
|
|
||||||
fn clear(self: *List) void {
|
|
||||||
self.stack.clear();
|
|
||||||
|
|
||||||
if (!self.arena.reset(.retain_capacity)) {
|
|
||||||
std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn deinit(self: *List) void {
|
|
||||||
self.arena.deinit();
|
|
||||||
self.stack.deinit();
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(allocator: std.mem.Allocator) List {
|
|
||||||
return .{
|
|
||||||
.arena = std.heap.ArenaAllocator.init(allocator),
|
|
||||||
.stack = .{.allocator = allocator},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Param = struct {
|
|
||||||
swap_lists: [2]List,
|
|
||||||
swap_state: u1 = 0,
|
|
||||||
|
|
||||||
fn deinit(self: *Param) void {
|
|
||||||
for (&self.swap_lists) |*list| {
|
|
||||||
list.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pending_list(self: *Param) *List {
|
|
||||||
return &self.swap_lists[self.swap_state];
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rotate(self: *Param) void {
|
|
||||||
const swapped_state = self.swap_state ^ 1;
|
|
||||||
|
|
||||||
self.swap_lists[swapped_state].clear();
|
|
||||||
|
|
||||||
self.swap_state = swapped_state;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn submitted_commands(self: Param) []const Command {
|
|
||||||
return self.swap_lists[self.swap_state ^ 1].stack.values;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn bind(_: flow.system.BindContext) std.mem.Allocator.Error!Param {
|
|
||||||
return .{
|
|
||||||
.swap_lists = .{
|
|
||||||
List.init(coral.heap.allocator),
|
|
||||||
List.init(coral.heap.allocator),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(param: *Param) Commands {
|
|
||||||
return .{
|
|
||||||
.pending = param.pending_list(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void {
|
|
||||||
try self.pending.stack.push_grow(.{.draw_texture = command});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void {
|
|
||||||
try self.pending.stack.push_grow(.{
|
|
||||||
.set_effect = .{
|
|
||||||
.properties = try self.pending.arena.allocator().dupe(coral.io.Byte, command.properties),
|
|
||||||
.effect = command.effect,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn unbind(param: *Param, _: flow.system.UnbindContext) void {
|
|
||||||
param.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void {
|
|
||||||
try self.pending.stack.push_grow(.{.set_target = command});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Config = struct {
|
|
||||||
width: u16 = 1280,
|
|
||||||
height: u16 = 720,
|
|
||||||
clear_color: Color = colors.black,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Input = union (enum) {
|
|
||||||
key_up: Key,
|
|
||||||
key_down: Key,
|
|
||||||
|
|
||||||
pub const Key = enum (u32) {
|
|
||||||
a = ext.SDL_SCANCODE_A,
|
|
||||||
d = ext.SDL_SCANCODE_D,
|
|
||||||
s = ext.SDL_SCANCODE_S,
|
|
||||||
w = ext.SDL_SCANCODE_W,
|
|
||||||
|
|
||||||
pub fn scancode(key: Key) u32 {
|
|
||||||
return @intFromEnum(key);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Effect = enum (u32) {
|
|
||||||
default,
|
|
||||||
_,
|
|
||||||
|
|
||||||
pub const Desc = struct {
|
|
||||||
fragment_spirv_ops: []const u32,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Texture = enum (u32) {
|
|
||||||
default,
|
|
||||||
backbuffer,
|
|
||||||
_,
|
|
||||||
|
|
||||||
pub const Desc = struct {
|
|
||||||
format: Format,
|
|
||||||
access: Access,
|
|
||||||
|
|
||||||
pub const Access = union (enum) {
|
|
||||||
static: StaticAccess,
|
|
||||||
render: RenderAccess,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const StaticAccess = struct {
|
|
||||||
width: u16,
|
|
||||||
data: []const coral.io.Byte,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const RenderAccess = struct {
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Format = enum {
|
|
||||||
rgba8,
|
|
||||||
bgra8,
|
|
||||||
|
|
||||||
pub fn byte_size(self: Format) usize {
|
|
||||||
return switch (self) {
|
|
||||||
.rgba8, .bgra8 => 4,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
pub const Transform2D = extern struct {
|
|
||||||
xbasis: Vector = .{1, 0},
|
|
||||||
ybasis: Vector = .{0, 1},
|
|
||||||
origin: Vector = @splat(0),
|
|
||||||
|
|
||||||
const Vector = @Vector(2, f32);
|
|
||||||
};
|
|
||||||
|
|
||||||
fn load_bmp_texture(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !Texture.Desc {
|
|
||||||
const header = try storage.read_little(path, 0, extern struct {
|
|
||||||
type: [2]u8 align (1),
|
|
||||||
file_size: u32 align (1),
|
|
||||||
reserved: [2]u16 align (1),
|
|
||||||
image_offset: u32 align (1),
|
|
||||||
header_size: u32 align (1),
|
|
||||||
pixel_width: i32 align (1),
|
|
||||||
pixel_height: i32 align (1),
|
|
||||||
color_planes: u16 align (1),
|
|
||||||
bits_per_pixel: u16 align (1),
|
|
||||||
compression_method: u32 align (1),
|
|
||||||
image_size: u32 align(1),
|
|
||||||
pixels_per_meter_x: i32 align (1),
|
|
||||||
pixels_per_meter_y: i32 align (1),
|
|
||||||
palette_colors_used: u32 align (1),
|
|
||||||
important_colors_used: u32 align (1),
|
|
||||||
}) orelse {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!std.mem.eql(u8, &header.type, "BM")) {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
};
|
|
||||||
|
|
||||||
const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size);
|
|
||||||
const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte;
|
|
||||||
const alignment = 4;
|
|
||||||
const byte_stride = pixel_width * bytes_per_pixel;
|
|
||||||
const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
|
|
||||||
const byte_padding = coral.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
|
|
||||||
var buffer_offset: usize = 0;
|
|
||||||
var file_offset = @as(usize, header.image_offset);
|
|
||||||
|
|
||||||
switch (header.bits_per_pixel) {
|
|
||||||
32 => {
|
|
||||||
while (buffer_offset < pixels.len) {
|
|
||||||
const line = pixels[buffer_offset .. buffer_offset + byte_stride];
|
|
||||||
|
|
||||||
if (try storage.read(path, line, file_offset) != byte_stride) {
|
|
||||||
return error.FormatUnsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (0 .. pixel_width) |i| {
|
|
||||||
const line_offset = i * 4;
|
|
||||||
const pixel = line[line_offset .. line_offset + 4];
|
|
||||||
|
|
||||||
std.mem.swap(u8, &pixel[0], &pixel[2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
file_offset += line.len + byte_padding;
|
|
||||||
buffer_offset += padded_byte_stride;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
else => return error.FormatUnsupported,
|
|
||||||
}
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.format = .rgba8,
|
|
||||||
|
|
||||||
.access = .{
|
|
||||||
.static = .{
|
|
||||||
.width = pixel_width,
|
|
||||||
.data = pixels,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn poll(app: flow.Write(ona.App), inputs: ona.msg.Send(Input)) !void {
|
|
||||||
var event = @as(ext.SDL_Event, undefined);
|
|
||||||
|
|
||||||
while (ext.SDL_PollEvent(&event) != 0) {
|
|
||||||
switch (event.type) {
|
|
||||||
ext.SDL_QUIT => app.res.quit(),
|
|
||||||
ext.SDL_KEYUP => try inputs.push(.{.key_up = @enumFromInt(event.key.keysym.scancode)}),
|
|
||||||
ext.SDL_KEYDOWN => try inputs.push(.{.key_down = @enumFromInt(event.key.keysym.scancode)}),
|
|
||||||
else => {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn setup(world: *flow.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
|
|
||||||
if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
|
|
||||||
return error.Unsupported;
|
|
||||||
}
|
|
||||||
|
|
||||||
const assets = create: {
|
|
||||||
var assets = try Assets.init();
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
assets.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
break: create try world.set_get_state(assets);
|
|
||||||
};
|
|
||||||
|
|
||||||
assets.frame_rendered.set();
|
|
||||||
|
|
||||||
errdefer {
|
|
||||||
assets.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{
|
|
||||||
&assets.pending_work,
|
|
||||||
assets.window,
|
|
||||||
});
|
|
||||||
|
|
||||||
const builtin_texture_formats = [_]Assets.TextureFormat{
|
|
||||||
.{
|
|
||||||
.extension = "bmp",
|
|
||||||
.load_file = load_bmp_texture,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
for (builtin_texture_formats) |format| {
|
|
||||||
try assets.texture_formats.push_grow(format);
|
|
||||||
}
|
|
||||||
|
|
||||||
try world.set_state(Config{});
|
|
||||||
try world.on_event(events.pre_update, flow.system_fn(poll), .{.label = "poll gfx"});
|
|
||||||
try world.on_event(events.exit, flow.system_fn(stop), .{.label = "stop gfx"});
|
|
||||||
try world.on_event(events.finish, flow.system_fn(synchronize), .{.label = "synchronize gfx"});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop(assets: flow.Write(Assets)) void {
|
|
||||||
assets.res.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn synchronize(exclusive: flow.Exclusive) !void {
|
|
||||||
const assets = exclusive.world.get_state(Assets).?;
|
|
||||||
const config = exclusive.world.get_state(Config).?;
|
|
||||||
|
|
||||||
assets.frame_rendered.wait();
|
|
||||||
assets.frame_rendered.reset();
|
|
||||||
|
|
||||||
{
|
|
||||||
var has_command_param = exclusive.world.get_params(Commands).has_head;
|
|
||||||
|
|
||||||
while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
|
|
||||||
command_param.param.rotate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var display_width, var display_height = [_]c_int{0, 0};
|
|
||||||
|
|
||||||
ext.SDL_GL_GetDrawableSize(assets.window, &display_width, &display_height);
|
|
||||||
|
|
||||||
if (config.width != display_width or config.height != display_height) {
|
|
||||||
ext.SDL_SetWindowSize(assets.window, config.width, config.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exclusive.world.get_params(Commands).has_head) |command_param| {
|
|
||||||
assets.pending_work.enqueue(.{
|
|
||||||
.render_frame = .{
|
|
||||||
.has_command_params = command_param,
|
|
||||||
.width = config.width,
|
|
||||||
.height = config.height,
|
|
||||||
.clear_color = config.clear_color,
|
|
||||||
.finished = &assets.frame_rendered,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
assets.frame_rendered.set();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
const gfx = @import("../gfx.zig");
|
|
||||||
|
|
||||||
pub const black = greyscale(0);
|
|
||||||
|
|
||||||
pub const white = greyscale(1);
|
|
||||||
|
|
||||||
pub fn greyscale(v: f32) gfx.Color {
|
|
||||||
return .{v, v, v, 1};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rgb(r: f32, g: f32, b: f32) gfx.Color {
|
|
||||||
return .{r, g, b, 1};
|
|
||||||
}
|
|
|
@ -1,13 +1,9 @@
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
|
|
||||||
const coral = @import("./coral.zig");
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
const slices = @import("./slices.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub const Byte = u8;
|
|
||||||
|
|
||||||
pub const Error = error {
|
pub const Error = error {
|
||||||
UnavailableResource,
|
UnavailableResource,
|
||||||
};
|
};
|
||||||
|
@ -111,7 +107,7 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type
|
||||||
pub const NullWritable = struct {
|
pub const NullWritable = struct {
|
||||||
written: usize = 0,
|
written: usize = 0,
|
||||||
|
|
||||||
pub fn write(self: *NullWritable, buffer: []const Byte) Error!usize {
|
pub fn write(self: *NullWritable, buffer: []const u8) Error!usize {
|
||||||
self.written += buffer.len;
|
self.written += buffer.len;
|
||||||
|
|
||||||
return buffer.len;
|
return buffer.len;
|
||||||
|
@ -122,12 +118,12 @@ pub const NullWritable = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Reader = Generator(Error!usize, &.{[]Byte});
|
pub const Reader = Generator(Error!usize, &.{[]u8});
|
||||||
|
|
||||||
pub const Writer = Generator(Error!usize, &.{[]const Byte});
|
pub const Writer = Generator(Error!usize, &.{[]const u8});
|
||||||
|
|
||||||
pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte {
|
pub fn alloc_read(input: ona.io.Reader, allocator: std.mem.Allocator) []u8 {
|
||||||
const buffer = coral.Stack(coral.Byte){.allocator = allocator};
|
const buffer = ona.stack.Sequential(u8){.allocator = allocator};
|
||||||
|
|
||||||
errdefer buffer.deinit();
|
errdefer buffer.deinit();
|
||||||
|
|
||||||
|
@ -139,7 +135,7 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.
|
||||||
pub const bits_per_byte = 8;
|
pub const bits_per_byte = 8;
|
||||||
|
|
||||||
pub fn skip_n(input: Reader, distance: u64) Error!void {
|
pub fn skip_n(input: Reader, distance: u64) Error!void {
|
||||||
var buffer = @as([512]coral.Byte, undefined);
|
var buffer = @as([512]u8, undefined);
|
||||||
var remaining = distance;
|
var remaining = distance;
|
||||||
|
|
||||||
while (remaining != 0) {
|
while (remaining != 0) {
|
||||||
|
@ -154,7 +150,7 @@ pub fn skip_n(input: Reader, distance: u64) Error!void {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stream_all(input: Reader, output: Writer) Error!usize {
|
pub fn stream_all(input: Reader, output: Writer) Error!usize {
|
||||||
var buffer = @as([512]coral.Byte, undefined);
|
var buffer = @as([512]u8, undefined);
|
||||||
var copied = @as(usize, 0);
|
var copied = @as(usize, 0);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
@ -173,7 +169,7 @@ pub fn stream_all(input: Reader, output: Writer) Error!usize {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize {
|
pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize {
|
||||||
var buffer = @as([512]coral.Byte, undefined);
|
var buffer = @as([512]u8, undefined);
|
||||||
var remaining = limit;
|
var remaining = limit;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
|
@ -1,18 +1,5 @@
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn Matrix(comptime n: usize, comptime Element: type) type {
|
|
||||||
return [n]@Vector(n, Element);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const ProjectionMatrix = Matrix(4, f32);
|
|
||||||
|
|
||||||
pub const Rect = struct {
|
|
||||||
left: f32,
|
|
||||||
top: f32,
|
|
||||||
right: f32,
|
|
||||||
bottom: f32,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn cross(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child {
|
pub fn cross(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child {
|
||||||
const multipled = v1 * v2;
|
const multipled = v1 * v2;
|
||||||
const vector_info = @typeInfo(@TypeOf(v1)).Vector;
|
const vector_info = @typeInfo(@TypeOf(v1)).Vector;
|
||||||
|
@ -62,15 +49,3 @@ pub fn normal(v: anytype) @TypeOf(v) {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn orthographic_projection(near: f32, far: f32, viewport: Rect) Matrix(4, f32) {
|
|
||||||
const width = viewport.right - viewport.left;
|
|
||||||
const height = viewport.bottom - viewport.top;
|
|
||||||
|
|
||||||
return .{
|
|
||||||
.{2 / width, 0, 0, 0},
|
|
||||||
.{0, 2 / height, 0, 0},
|
|
||||||
.{0, 0, 1 / (far - near), 0},
|
|
||||||
.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,8 +1,4 @@
|
||||||
const coral = @import("./coral.zig");
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
const hashes = @import("./hashes.zig");
|
|
||||||
|
|
||||||
const io = @import("./io.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
@ -250,13 +246,17 @@ pub fn enum_traits(comptime Enum: type) Traits(Enum) {
|
||||||
|
|
||||||
pub const string_traits = init: {
|
pub const string_traits = init: {
|
||||||
const strings = struct {
|
const strings = struct {
|
||||||
|
fn are_equal(a: []const u8, b: []const u8) bool {
|
||||||
|
return std.mem.eql(u8, a, b);
|
||||||
|
}
|
||||||
|
|
||||||
fn hash(value: []const u8) usize {
|
fn hash(value: []const u8) usize {
|
||||||
return hashes.djb2(@typeInfo(usize).Int, value);
|
return ona.hashes.djb2(@typeInfo(usize).Int, value);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
break: init Traits([]const u8){
|
break: init Traits([]const u8){
|
||||||
.are_equal = coral.io.are_equal,
|
.are_equal = strings.are_equal,
|
||||||
.hash = strings.hash,
|
.hash = strings.hash,
|
||||||
};
|
};
|
||||||
};
|
};
|
134
src/ona/msg.zig
134
src/ona/msg.zig
|
@ -1,134 +0,0 @@
|
||||||
const coral = @import("coral");
|
|
||||||
|
|
||||||
const flow = @import("flow");
|
|
||||||
|
|
||||||
const ona = @import("./ona.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
|
||||||
|
|
||||||
fn Channel(comptime Message: type) type {
|
|
||||||
return struct {
|
|
||||||
buffers: [2]coral.stack.Sequential(Message),
|
|
||||||
swap_index: u1 = 0,
|
|
||||||
ticks: u1 = 0,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
fn cleanup(channel: flow.Write(Self)) void {
|
|
||||||
channel.res.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn deinit(self: *Self) void {
|
|
||||||
for (&self.buffers) |*buffer| {
|
|
||||||
buffer.deinit();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.* = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn swap(channel: flow.Write(Self)) void {
|
|
||||||
channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0;
|
|
||||||
|
|
||||||
if (channel.res.ticks == 0) {
|
|
||||||
channel.res.swap_index ^= 1;
|
|
||||||
|
|
||||||
channel.res.buffers[channel.res.swap_index].clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn init(allocator: std.mem.Allocator) Self {
|
|
||||||
return .{
|
|
||||||
.buffers = .{
|
|
||||||
.{.allocator = allocator},
|
|
||||||
.{.allocator = allocator},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
fn messages(self: Self) []const Message {
|
|
||||||
return self.buffers[self.swap_index ^ 1].values;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
|
|
||||||
try self.buffers[self.swap_index].push_grow(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Receive(comptime Message: type) type {
|
|
||||||
const TypedChannel = Channel(Message);
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
channel: *const TypedChannel,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub const Param = struct {
|
|
||||||
channel: *const TypedChannel,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param {
|
|
||||||
return .{
|
|
||||||
.channel = (try context.register_readable_state_access(TypedChannel)) orelse set: {
|
|
||||||
try context.world.set_state(TypedChannel.init(coral.heap.allocator));
|
|
||||||
|
|
||||||
break: set (try context.register_readable_state_access(TypedChannel)).?;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(param: *Param) Self {
|
|
||||||
return .{
|
|
||||||
.channel = param.channel,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn messages(self: Self) []const Message {
|
|
||||||
return self.channel.messages();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn Send(comptime Message: type) type {
|
|
||||||
const TypedChannel = Channel(Message);
|
|
||||||
|
|
||||||
return struct {
|
|
||||||
channel: *TypedChannel,
|
|
||||||
|
|
||||||
const Self = @This();
|
|
||||||
|
|
||||||
pub const Param = struct {
|
|
||||||
channel: *TypedChannel,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param {
|
|
||||||
return .{
|
|
||||||
.channel = (try context.register_writable_state_access(TypedChannel)) orelse set: {
|
|
||||||
try context.world.set_state(TypedChannel.init(coral.heap.allocator));
|
|
||||||
|
|
||||||
const app = context.world.get_state(ona.App).?;
|
|
||||||
|
|
||||||
try context.world.on_event(app.events.post_update, flow.system_fn(TypedChannel.swap), .{
|
|
||||||
.label = "swap channel of " ++ @typeName(Message),
|
|
||||||
});
|
|
||||||
|
|
||||||
try context.world.on_event(app.events.exit, flow.system_fn(TypedChannel.cleanup), .{
|
|
||||||
.label = "clean up channel of " ++ @typeName(Message),
|
|
||||||
});
|
|
||||||
|
|
||||||
break: set (try context.register_writable_state_access(TypedChannel)).?;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init(param: *Param) Self {
|
|
||||||
return .{
|
|
||||||
.channel = param.channel,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(self: Self, message: Message) std.mem.Allocator.Error!void {
|
|
||||||
try self.channel.push(message);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
773
src/ona/ona.zig
773
src/ona/ona.zig
|
@ -1,17 +1,33 @@
|
||||||
pub const act = @import("./act.zig");
|
pub const ascii = @import("./ascii.zig");
|
||||||
|
|
||||||
const coral = @import("coral");
|
pub const asyncio = @import("./asyncio.zig");
|
||||||
|
|
||||||
const ext = @import("./ext.zig");
|
pub const dag = @import("./dag.zig");
|
||||||
|
|
||||||
const flow = @import("flow");
|
pub const hashes = @import("./hashes.zig");
|
||||||
|
|
||||||
pub const gfx = @import("./gfx.zig");
|
pub const heap = @import("./heap.zig");
|
||||||
|
|
||||||
pub const msg = @import("./msg.zig");
|
pub const files = @import("./files.zig");
|
||||||
|
|
||||||
|
pub const io = @import("./io.zig");
|
||||||
|
|
||||||
|
pub const map = @import("./map.zig");
|
||||||
|
|
||||||
|
pub const scalars = @import("./scalars.zig");
|
||||||
|
|
||||||
|
pub const slices = @import("./slices.zig");
|
||||||
|
|
||||||
|
pub const stack = @import("./stack.zig");
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub const utf8 = @import("./utf8.zig");
|
||||||
|
|
||||||
|
pub const World = @import("./World.zig");
|
||||||
|
|
||||||
|
pub const lina = @import("./lina.zig");
|
||||||
|
|
||||||
pub const App = struct {
|
pub const App = struct {
|
||||||
events: *const Events,
|
events: *const Events,
|
||||||
target_frame_time: f64,
|
target_frame_time: f64,
|
||||||
|
@ -19,13 +35,13 @@ pub const App = struct {
|
||||||
is_running: bool,
|
is_running: bool,
|
||||||
|
|
||||||
pub const Events = struct {
|
pub const Events = struct {
|
||||||
load: flow.World.Event,
|
load: World.Event,
|
||||||
pre_update: flow.World.Event,
|
pre_update: World.Event,
|
||||||
update: flow.World.Event,
|
update: World.Event,
|
||||||
post_update: flow.World.Event,
|
post_update: World.Event,
|
||||||
render: flow.World.Event,
|
render: World.Event,
|
||||||
finish: flow.World.Event,
|
finish: World.Event,
|
||||||
exit: flow.World.Event,
|
exit: World.Event,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub fn quit(self: *App) void {
|
pub fn quit(self: *App) void {
|
||||||
|
@ -33,18 +49,151 @@ pub const App = struct {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Flag = enum {
|
fn Channel(comptime Message: type) type {
|
||||||
|
return struct {
|
||||||
|
buffers: [2]stack.Sequential(Message),
|
||||||
|
swap_index: u1 = 0,
|
||||||
|
ticks: u1 = 0,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
fn cleanup(channel: Write(Self)) void {
|
||||||
|
channel.state.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
for (&self.buffers) |*buffer| {
|
||||||
|
buffer.deinit();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn swap(channel: Write(Self)) void {
|
||||||
|
channel.state.ticks = scalars.add(channel.state.ticks, 1) orelse 0;
|
||||||
|
|
||||||
|
if (channel.state.ticks == 0) {
|
||||||
|
channel.state.swap_index ^= 1;
|
||||||
|
|
||||||
|
channel.state.buffers[channel.state.swap_index].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(allocator: std.mem.Allocator) Self {
|
||||||
|
return .{
|
||||||
|
.buffers = .{
|
||||||
|
.{.allocator = allocator},
|
||||||
|
.{.allocator = allocator},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn messages(self: Self) []const Message {
|
||||||
|
return self.buffers[self.swap_index ^ 1].values;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
|
||||||
|
try self.buffers[self.swap_index].push_grow(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Exclusive(comptime values: []const type) type {
|
||||||
|
comptime var qualifieds: [values.len]type = undefined;
|
||||||
|
|
||||||
|
for (&qualifieds, values) |*qualified, Value| {
|
||||||
|
qualified.* = switch (@typeInfo(Value)) {
|
||||||
|
.Optional => @Type(.{
|
||||||
|
.Optional = .{
|
||||||
|
.child = .{
|
||||||
|
.Pointer = .{
|
||||||
|
.is_allowzero = false,
|
||||||
|
.sentinel = null,
|
||||||
|
.address_space = .generic,
|
||||||
|
.is_volatile = false,
|
||||||
|
.alignment = @alignOf(Value),
|
||||||
|
.size = .One,
|
||||||
|
.child = Value,
|
||||||
|
.is_const = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
else => @Type(.{
|
||||||
|
.Pointer = .{
|
||||||
|
.is_allowzero = false,
|
||||||
|
.sentinel = null,
|
||||||
|
.address_space = .generic,
|
||||||
|
.is_volatile = false,
|
||||||
|
.alignment = @alignOf(Value),
|
||||||
|
.size = .One,
|
||||||
|
.child = Value,
|
||||||
|
.is_const = false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const States = std.meta.Tuple(&qualifieds);
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
states: States,
|
||||||
|
world: *World,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Param = struct {
|
||||||
|
states: States,
|
||||||
|
world: *World,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
|
||||||
|
var param = Param{
|
||||||
|
.world = context.world,
|
||||||
|
.states = undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
inline for (¶m.states, values) |*state, Value| {
|
||||||
|
const has_state = try context.register_writable_state_access(Value);
|
||||||
|
|
||||||
|
state.* = switch (@typeInfo(Value)) {
|
||||||
|
.Optional => has_state,
|
||||||
|
|
||||||
|
else => has_state orelse {
|
||||||
|
@panic(std.fmt.comptimePrint("attempt to use exclusive {s} that has not yet been set", .{
|
||||||
|
@typeName(Value),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return param;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(param: *Param) Self {
|
||||||
|
return .{
|
||||||
|
.states = param.states,
|
||||||
|
.world = param.world,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const thread_restriction = .main;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const LaunchFlag = enum {
|
||||||
dump_shader_translations,
|
dump_shader_translations,
|
||||||
|
|
||||||
var launch_args = [_]?[]const u8{null} ** std.enums.values(Flag).len;
|
var args = [_]?[]const u8{null} ** std.enums.values(LaunchFlag).len;
|
||||||
|
|
||||||
const values = std.enums.values(Flag);
|
const values = std.enums.values(LaunchFlag);
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Options = struct {
|
pub const Options = struct {
|
||||||
tick_rate: u64,
|
tick_rate: u64,
|
||||||
execution: Execution,
|
execution: Execution,
|
||||||
middlewares: []const *const Setup = default_middlewares,
|
middleware: []const *const Setup,
|
||||||
|
|
||||||
pub const Execution = union (enum) {
|
pub const Execution = union (enum) {
|
||||||
single_threaded,
|
single_threaded,
|
||||||
|
@ -52,29 +201,498 @@ pub const Options = struct {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Read = flow.Read;
|
pub fn Params(comptime Value: type) type {
|
||||||
|
if (!@hasDecl(Value, "Param")) {
|
||||||
|
@compileError("System parameters must have a Params type declaration");
|
||||||
|
}
|
||||||
|
|
||||||
pub const Setup = fn (*flow.World, App.Events) anyerror!void;
|
return struct {
|
||||||
|
has_head: ?*Node = null,
|
||||||
|
has_tail: ?*Node = null,
|
||||||
|
|
||||||
pub const World = flow.World;
|
pub const Node = struct {
|
||||||
|
param: Value.Param,
|
||||||
pub const Write = flow.Write;
|
has_prev: ?*Node = null,
|
||||||
|
has_next: ?*Node = null,
|
||||||
pub const default_middlewares = &.{
|
};
|
||||||
gfx.setup,
|
};
|
||||||
act.setup,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn launch_arg(flag: Flag) ?[]const u8 {
|
|
||||||
return Flag.launch_args[@intFromEnum(flag)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
|
pub fn Pool(comptime Value: type) type {
|
||||||
|
return struct {
|
||||||
|
entries: stack.Sequential(Entry),
|
||||||
|
first_free_index: usize = 0,
|
||||||
|
|
||||||
|
const Entry = union (enum) {
|
||||||
|
free_index: usize,
|
||||||
|
occupied: Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const Values = struct {
|
||||||
|
cursor: usize = 0,
|
||||||
|
pool: *const Self,
|
||||||
|
|
||||||
|
pub fn next(self: *Values) ?*Value {
|
||||||
|
while (self.cursor < self.pool.entries.len()) {
|
||||||
|
defer self.cursor += 1;
|
||||||
|
|
||||||
|
switch (self.pool.entries.values[self.cursor]) {
|
||||||
|
.free_index => {
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
|
||||||
|
.occupied => |*occupied| {
|
||||||
|
return occupied;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub fn deinit(self: *Self) void {
|
||||||
|
self.entries.deinit();
|
||||||
|
|
||||||
|
self.* = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(self: *Self, key: usize) ?*Value {
|
||||||
|
if (key >= self.entries.len()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return switch (self.entries.values[key]) {
|
||||||
|
.free_index => null,
|
||||||
|
.occupied => |*occupied| occupied,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(allocator: std.mem.Allocator) Self {
|
||||||
|
return .{
|
||||||
|
.entries = .{.allocator = allocator},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
|
||||||
|
const entries_count = self.entries.len();
|
||||||
|
|
||||||
|
if (self.first_free_index == entries_count) {
|
||||||
|
try self.entries.push_grow(.{.occupied = value});
|
||||||
|
|
||||||
|
self.first_free_index += 1;
|
||||||
|
|
||||||
|
return entries_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
const insersion_index = self.first_free_index;
|
||||||
|
|
||||||
|
self.first_free_index = self.entries.values[self.first_free_index].free_index;
|
||||||
|
self.entries.values[insersion_index] = .{.occupied = value};
|
||||||
|
|
||||||
|
return insersion_index;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove(self: *Self, key: usize) ?Value {
|
||||||
|
if (key >= self.entries.len()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (self.entries.values[key]) {
|
||||||
|
.free_index => {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
.occupied => |occupied| {
|
||||||
|
self.entries.values[key] = .{.free_index = self.first_free_index};
|
||||||
|
self.first_free_index = key;
|
||||||
|
|
||||||
|
return occupied;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn values(self: *const Self) Values {
|
||||||
|
return .{
|
||||||
|
.pool = self,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
test "pooling" {
|
||||||
|
var pool = Pool(i32).init(std.testing.allocator);
|
||||||
|
|
||||||
|
defer pool.deinit();
|
||||||
|
|
||||||
|
try std.testing.expectEqual(0, pool.first_free_index);
|
||||||
|
try std.testing.expectEqual(null, pool.get(0));
|
||||||
|
try std.testing.expectEqual(null, pool.remove(0));
|
||||||
|
try std.testing.expectEqual(0, try pool.insert(69));
|
||||||
|
try std.testing.expectEqual(1, pool.first_free_index);
|
||||||
|
try std.testing.expectEqual(1, try pool.insert(420));
|
||||||
|
try std.testing.expectEqual(69, pool.get(0).?.*);
|
||||||
|
try std.testing.expectEqual(420, pool.get(1).?.*);
|
||||||
|
try std.testing.expectEqual(69, pool.remove(0).?);
|
||||||
|
try std.testing.expectEqual(0, pool.first_free_index);
|
||||||
|
try std.testing.expectEqual(null, pool.get(0));
|
||||||
|
try std.testing.expectEqual(null, pool.remove(0));
|
||||||
|
try std.testing.expectEqual(0, try pool.insert(36));
|
||||||
|
try std.testing.expectEqual(36, pool.get(0).?.*);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Read(comptime Value: type) type {
|
||||||
|
return Shared(Value, .{
|
||||||
|
.thread_restriction = thread_restriction(Value),
|
||||||
|
.read_only = true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Receive(comptime Message: type) type {
|
||||||
|
const TypedChannel = Channel(Message);
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
channel: *const TypedChannel,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Param = struct {
|
||||||
|
channel: *const TypedChannel,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
|
||||||
|
return .{
|
||||||
|
.channel = (try context.register_readable_state_access(TypedChannel)) orelse set: {
|
||||||
|
try context.world.set_state(TypedChannel.init(heap.allocator));
|
||||||
|
|
||||||
|
break: set (try context.register_readable_state_access(TypedChannel)).?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(param: *Param) Self {
|
||||||
|
return .{
|
||||||
|
.channel = param.channel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn messages(self: Self) []const Message {
|
||||||
|
return self.channel.messages();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn Send(comptime Message: type) type {
|
||||||
|
const TypedChannel = Channel(Message);
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
channel: *TypedChannel,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Param = struct {
|
||||||
|
channel: *TypedChannel,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
|
||||||
|
return .{
|
||||||
|
.channel = (try context.register_writable_state_access(TypedChannel)) orelse set: {
|
||||||
|
try context.world.set_state(TypedChannel.init(heap.allocator));
|
||||||
|
|
||||||
|
const app = context.world.get_state(App) orelse {
|
||||||
|
@panic("Send system parameters depend on a " ++ @typeName(App) ++ " state to work");
|
||||||
|
};
|
||||||
|
|
||||||
|
try context.world.on_event(app.events.post_update, system_fn(TypedChannel.swap), .{
|
||||||
|
.label = "swap channel of " ++ @typeName(Message),
|
||||||
|
});
|
||||||
|
|
||||||
|
try context.world.on_event(app.events.exit, system_fn(TypedChannel.cleanup), .{
|
||||||
|
.label = "clean up channel of " ++ @typeName(Message),
|
||||||
|
});
|
||||||
|
|
||||||
|
break: set (try context.register_writable_state_access(TypedChannel)).?;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(param: *Param) Self {
|
||||||
|
return .{
|
||||||
|
.channel = param.channel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push(self: Self, message: Message) std.mem.Allocator.Error!void {
|
||||||
|
try self.channel.push(message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const Setup = fn (*World, App.Events) anyerror!void;
|
||||||
|
|
||||||
|
pub const ShareInfo = struct {
|
||||||
|
thread_restriction: ThreadRestriction,
|
||||||
|
read_only: bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn Shared(comptime Value: type, comptime info: ShareInfo) type {
|
||||||
|
const value_info = @typeInfo(Value);
|
||||||
|
|
||||||
|
const Qualified = switch (value_info) {
|
||||||
|
.Optional => @Type(.{
|
||||||
|
.Optional = .{
|
||||||
|
.child = .{
|
||||||
|
.Pointer = .{
|
||||||
|
.is_allowzero = false,
|
||||||
|
.sentinel = null,
|
||||||
|
.address_space = .generic,
|
||||||
|
.is_volatile = false,
|
||||||
|
.alignment = @alignOf(Value),
|
||||||
|
.size = .One,
|
||||||
|
.child = Value,
|
||||||
|
.is_const = info.read_only,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
else => @Type(.{
|
||||||
|
.Pointer = .{
|
||||||
|
.is_allowzero = false,
|
||||||
|
.sentinel = null,
|
||||||
|
.address_space = .generic,
|
||||||
|
.is_volatile = false,
|
||||||
|
.alignment = @alignOf(Value),
|
||||||
|
.size = .One,
|
||||||
|
.child = Value,
|
||||||
|
.is_const = info.read_only,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
return struct {
|
||||||
|
state: Qualified,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub const Param = struct {
|
||||||
|
state: Qualified,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
|
||||||
|
const thread_restriction_name = switch (info.thread_restriction) {
|
||||||
|
.main => "main thread-restricted ",
|
||||||
|
.none => ""
|
||||||
|
};
|
||||||
|
|
||||||
|
const state = switch (info.read_only) {
|
||||||
|
true => (try context.register_readable_state_access(Value)),
|
||||||
|
false => (try context.register_writable_state_access(Value)),
|
||||||
|
};
|
||||||
|
|
||||||
|
return .{
|
||||||
|
.state = switch (value_info) {
|
||||||
|
.Optional => state,
|
||||||
|
|
||||||
|
else => state orelse {
|
||||||
|
@panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{
|
||||||
|
thread_restriction_name,
|
||||||
|
if (info.read_only) "read-only" else "read-write",
|
||||||
|
@typeName(Value),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(param: *Param) Self {
|
||||||
|
return .{
|
||||||
|
.state = param.state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const thread_restriction = info.thread_restriction;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const SystemInfo = struct {
|
||||||
|
execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void,
|
||||||
|
parameters: [max_parameters]*const Parameter = undefined,
|
||||||
|
parameter_count: u4 = 0,
|
||||||
|
thread_restriction: ThreadRestriction = .none,
|
||||||
|
|
||||||
|
pub const Parameter = struct {
|
||||||
|
thread_restriction: ThreadRestriction,
|
||||||
|
init: *const fn (*anyopaque, *anyopaque) void,
|
||||||
|
bind: *const fn (std.mem.Allocator, World.BindContext) std.mem.Allocator.Error!*anyopaque,
|
||||||
|
unbind: *const fn (std.mem.Allocator, *anyopaque, World.UnbindContext) void,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const max_parameters = 16;
|
||||||
|
|
||||||
|
pub fn used_parameters(self: *const SystemInfo) []const *const Parameter {
|
||||||
|
return self.parameters[0 .. self.parameter_count];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const SystemOrder = struct {
|
||||||
|
label: []const u8 = "",
|
||||||
|
run_after: []const *const SystemInfo = &.{},
|
||||||
|
run_before: []const *const SystemInfo = &.{},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const ThreadRestriction = enum {
|
||||||
|
none,
|
||||||
|
main,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub const TypeID = enum (usize) { _ };
|
||||||
|
|
||||||
|
pub fn Write(comptime Value: type) type {
|
||||||
|
return Shared(Value, .{
|
||||||
|
.thread_restriction = thread_restriction(Value),
|
||||||
|
.read_only = false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn launch_arg(flag: LaunchFlag) ?[]const u8 {
|
||||||
|
return LaunchFlag.args[@intFromEnum(flag)];
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parameter_type(comptime Value: type) *const SystemInfo.Parameter {
|
||||||
|
const ValueParams = Params(Value);
|
||||||
|
|
||||||
|
if (@sizeOf(Value) == 0) {
|
||||||
|
@compileError("System parameters must have a non-zero size");
|
||||||
|
}
|
||||||
|
|
||||||
|
const parameters = struct {
|
||||||
|
fn bind(allocator: std.mem.Allocator, context: World.BindContext) std.mem.Allocator.Error!*anyopaque {
|
||||||
|
const value_name = @typeName(Value);
|
||||||
|
|
||||||
|
if (!@hasDecl(Value, "bind")) {
|
||||||
|
@compileError(
|
||||||
|
"a `bind` declaration on " ++
|
||||||
|
value_name ++
|
||||||
|
" is requied for parameter types with a `Param` declaration");
|
||||||
|
}
|
||||||
|
|
||||||
|
const bind_type = @typeInfo(@TypeOf(Value.bind));
|
||||||
|
|
||||||
|
if (bind_type != .Fn) {
|
||||||
|
@compileError("`bind` declaration on " ++ value_name ++ " must be a fn");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bind_type.Fn.params.len != 1 or bind_type.Fn.params[0].type.? != World.BindContext) {
|
||||||
|
@compileError(
|
||||||
|
"`bind` fn on " ++
|
||||||
|
value_name ++
|
||||||
|
" must accept " ++
|
||||||
|
@typeName(World.BindContext) ++
|
||||||
|
" as it's one and only argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
const params_node = try allocator.create(ValueParams.Node);
|
||||||
|
|
||||||
|
params_node.* = .{
|
||||||
|
.param = switch (bind_type.Fn.return_type.?) {
|
||||||
|
Value.Param => Value.bind(context),
|
||||||
|
std.mem.Allocator.Error!Value.Param => try Value.bind(context),
|
||||||
|
|
||||||
|
else => @compileError(std.fmt.comptimePrint("`bind` fn on {s} must return {s} or {s}", .{
|
||||||
|
@typeName(Value),
|
||||||
|
@typeName(Value.Param),
|
||||||
|
@typeName(std.mem.Allocator.Error!Value.Param)
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (context.world.get_state(ValueParams)) |value_params| {
|
||||||
|
if (value_params.has_tail) |tail| {
|
||||||
|
tail.has_next = params_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
params_node.has_prev = value_params.has_tail;
|
||||||
|
value_params.has_tail = params_node;
|
||||||
|
} else {
|
||||||
|
try context.world.set_state(ValueParams{
|
||||||
|
.has_head = params_node,
|
||||||
|
.has_tail = params_node,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return @ptrCast(params_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init(argument: *anyopaque, erased_node: *anyopaque) void {
|
||||||
|
const value_name = @typeName(Value);
|
||||||
|
|
||||||
|
if (!@hasDecl(Value, "init")) {
|
||||||
|
@compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types");
|
||||||
|
}
|
||||||
|
|
||||||
|
const init_type = @typeInfo(@TypeOf(Value.init));
|
||||||
|
|
||||||
|
if (init_type != .Fn) {
|
||||||
|
@compileError("`init` declaration on " ++ value_name ++ " must be a fn");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (init_type.Fn.return_type.? != Value) {
|
||||||
|
@compileError("`init` fn on " ++ value_name ++ " must return a " ++ value_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const concrete_argument = @as(*Value, @ptrCast(@alignCast(argument)));
|
||||||
|
|
||||||
|
if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.Param) {
|
||||||
|
@compileError("`init` fn on " ++ value_name ++ " must accept a " ++ @typeName(*Value.Param));
|
||||||
|
}
|
||||||
|
|
||||||
|
concrete_argument.* = Value.init(&@as(*ValueParams.Node, @ptrCast(@alignCast(erased_node))).param);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unbind(allocator: std.mem.Allocator, erased_node: *anyopaque, context: World.UnbindContext) void {
|
||||||
|
if (@hasDecl(Value, "unbind")) {
|
||||||
|
const node = @as(*ValueParams.Node, @ptrCast(@alignCast(erased_node)));
|
||||||
|
|
||||||
|
if (node.has_prev) |prev| {
|
||||||
|
prev.has_next = node.has_next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.has_next) |next| {
|
||||||
|
next.has_prev = node.has_prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (context.world.get_state(ValueParams)) |params| {
|
||||||
|
if (node.has_prev == null) {
|
||||||
|
params.has_head = node.has_next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.has_next == null) {
|
||||||
|
params.has_tail = node.has_prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value.unbind(&node.param, context);
|
||||||
|
allocator.destroy(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return comptime &.{
|
||||||
|
.thread_restriction = if (@hasDecl(Value, "thread_restriction")) Value.thread_restriction else .none,
|
||||||
|
.init = parameters.init,
|
||||||
|
.bind = parameters.bind,
|
||||||
|
.unbind = parameters.unbind,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(comptime setup: Setup, comptime options: Options) fn () anyerror!void {
|
||||||
const Start = struct {
|
const Start = struct {
|
||||||
fn main() anyerror!void {
|
fn main() anyerror!void {
|
||||||
defer {
|
defer {
|
||||||
coral.heap.trace_leaks();
|
heap.trace_leaks();
|
||||||
ext.SDL_Quit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
parse_args: for (std.os.argv[1 ..]) |arg| {
|
parse_args: for (std.os.argv[1 ..]) |arg| {
|
||||||
|
@ -82,14 +700,14 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
|
||||||
const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len;
|
const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len;
|
||||||
const arg_name = arg_span[0 .. arg_split_index];
|
const arg_name = arg_span[0 .. arg_split_index];
|
||||||
|
|
||||||
for (Flag.values) |value| {
|
for (LaunchFlag.values) |value| {
|
||||||
const name = @tagName(value);
|
const name = @tagName(value);
|
||||||
|
|
||||||
if (!std.mem.eql(u8, arg_name, name)) {
|
if (!std.mem.eql(u8, arg_name, name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Flag.launch_args[@intFromEnum(value)] =
|
LaunchFlag.args[@intFromEnum(value)] =
|
||||||
if (arg_split_index == arg_span.len)
|
if (arg_split_index == arg_span.len)
|
||||||
name
|
name
|
||||||
else
|
else
|
||||||
|
@ -100,7 +718,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
|
||||||
}
|
}
|
||||||
|
|
||||||
var world = try switch (options.execution) {
|
var world = try switch (options.execution) {
|
||||||
.single_threaded => flow.World.init(0),
|
.single_threaded => World.init(0),
|
||||||
|
|
||||||
.thread_share => |thread_share| init: {
|
.thread_share => |thread_share| init: {
|
||||||
const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
|
const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
|
||||||
|
@ -111,7 +729,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
|
||||||
});
|
});
|
||||||
}, 0, std.math.maxInt(u32))));
|
}, 0, std.math.maxInt(u32))));
|
||||||
|
|
||||||
break: init flow.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0);
|
break: init World.init(scalars.fractional(cpu_count, thread_share) orelse 0);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -134,7 +752,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
|
||||||
.is_running = true,
|
.is_running = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (options.middlewares) |setup_middleware| {
|
inline for (options.middleware) |setup_middleware| {
|
||||||
try setup_middleware(&world, events);
|
try setup_middleware(&world, events);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,4 +790,81 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
|
||||||
return Start.main;
|
return Start.main;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const system_fn = flow.system_fn;
|
pub fn system_fn(comptime call: anytype) *const SystemInfo {
|
||||||
|
const Call = @TypeOf(call);
|
||||||
|
|
||||||
|
const system_info = comptime generate: {
|
||||||
|
switch (@typeInfo(Call)) {
|
||||||
|
.Fn => |call_fn| {
|
||||||
|
if (call_fn.params.len > SystemInfo.max_parameters) {
|
||||||
|
@compileError("number of parameters to `call` cannot be more than 16");
|
||||||
|
}
|
||||||
|
|
||||||
|
const systems = struct {
|
||||||
|
fn run(parameters: []const *const SystemInfo.Parameter, data: *const [SystemInfo.max_parameters]*anyopaque) anyerror!void {
|
||||||
|
var call_args = @as(std.meta.ArgsTuple(Call), undefined);
|
||||||
|
|
||||||
|
inline for (parameters, &call_args, data[0 .. parameters.len]) |parameter, *call_arg, state| {
|
||||||
|
parameter.init(call_arg, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (@typeInfo(call_fn.return_type.?)) {
|
||||||
|
.Void => @call(.auto, call, call_args),
|
||||||
|
.ErrorUnion => try @call(.auto, call, call_args),
|
||||||
|
else => @compileError("number of parameters to `call` must return void or !void"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var parameters = @as([SystemInfo.max_parameters]*const SystemInfo.Parameter, undefined);
|
||||||
|
var system_thread_restriction = ThreadRestriction.none;
|
||||||
|
|
||||||
|
for (0 .. call_fn.params.len) |index| {
|
||||||
|
const CallParam = call_fn.params[index].type.?;
|
||||||
|
const parameter = parameter_type(CallParam);
|
||||||
|
|
||||||
|
if (parameter.thread_restriction != .none) {
|
||||||
|
if (system_thread_restriction != .none and system_thread_restriction != parameter.thread_restriction) {
|
||||||
|
@compileError("a system may not have conflicting thread restrictions");
|
||||||
|
}
|
||||||
|
|
||||||
|
system_thread_restriction = parameter.thread_restriction;
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters[index] = parameter;
|
||||||
|
}
|
||||||
|
|
||||||
|
break: generate &.{
|
||||||
|
.parameters = parameters,
|
||||||
|
.parameter_count = call_fn.params.len,
|
||||||
|
.execute = systems.run,
|
||||||
|
.thread_restriction = system_thread_restriction,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
else => @compileError("parameter `call` must be a function"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return system_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn type_id(comptime T: type) TypeID {
|
||||||
|
const TypeHandle = struct {
|
||||||
|
comptime {
|
||||||
|
_ = T;
|
||||||
|
}
|
||||||
|
|
||||||
|
var byte: u8 = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
return @enumFromInt(@intFromPtr(&TypeHandle.byte));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn thread_restriction(comptime State: type) ThreadRestriction {
|
||||||
|
if (@hasDecl(State, "thread_restriction")) {
|
||||||
|
return State.thread_restriction;
|
||||||
|
}
|
||||||
|
|
||||||
|
return .none;
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const io = @import("./io.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn ElementPtr(comptime Slice: type) type {
|
pub fn ElementPtr(comptime Slice: type) type {
|
||||||
|
@ -25,7 +23,7 @@ pub fn Parallel(comptime Type: type) type {
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
len: usize = 0,
|
len: usize = 0,
|
||||||
ptrs: [fields.len][*]align (alignment) io.Byte = undefined,
|
ptrs: [fields.len][*]align (alignment) u8 = undefined,
|
||||||
|
|
||||||
pub fn Element(comptime field: Field) type {
|
pub fn Element(comptime field: Field) type {
|
||||||
return fields[@intFromEnum(field)].type;
|
return fields[@intFromEnum(field)].type;
|
||||||
|
@ -118,7 +116,7 @@ pub fn get_ptr(slice: anytype, index: usize) ?ElementPtr(@TypeOf(slice)) {
|
||||||
pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) {
|
pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) {
|
||||||
const alignment = @alignOf(Element);
|
const alignment = @alignOf(Element);
|
||||||
const Slices = Parallel(Element);
|
const Slices = Parallel(Element);
|
||||||
var buffers = @as([std.enums.values(Slices.Field).len][]align (alignment) io.Byte, undefined);
|
var buffers = @as([std.enums.values(Slices.Field).len][]align (alignment) u8, undefined);
|
||||||
var buffers_allocated = @as(usize, 0);
|
var buffers_allocated = @as(usize, 0);
|
||||||
var allocated = Slices{.len = n};
|
var allocated = Slices{.len = n};
|
||||||
|
|
||||||
|
@ -131,7 +129,7 @@ pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: u
|
||||||
const fields = @typeInfo(Element).Struct.fields;
|
const fields = @typeInfo(Element).Struct.fields;
|
||||||
|
|
||||||
inline for (0 .. fields.len) |i| {
|
inline for (0 .. fields.len) |i| {
|
||||||
buffers[i] = try allocator.alignedAlloc(io.Byte, alignment, @sizeOf(fields[i].type) * n);
|
buffers[i] = try allocator.alignedAlloc(u8, alignment, @sizeOf(fields[i].type) * n);
|
||||||
buffers_allocated += 1;
|
buffers_allocated += 1;
|
||||||
allocated.ptrs[i] = buffers[i].ptr;
|
allocated.ptrs[i] = buffers[i].ptr;
|
||||||
}
|
}
|
|
@ -1,14 +1,10 @@
|
||||||
const io = @import("./io.zig");
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
const scalars = @import("./scalars.zig");
|
|
||||||
|
|
||||||
const slices = @import("./slices.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
pub fn Sequential(comptime Value: type) type {
|
pub fn Sequential(comptime Value: type) type {
|
||||||
return struct {
|
return struct {
|
||||||
allocator: std.mem.Allocator,
|
allocator: std.mem.Allocator = ona.heap.allocator,
|
||||||
values: []Value = &.{},
|
values: []Value = &.{},
|
||||||
cap: usize = 0,
|
cap: usize = 0,
|
||||||
|
|
||||||
|
@ -82,7 +78,7 @@ pub fn Sequential(comptime Value: type) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_many(self: *Self, n: usize) bool {
|
pub fn pop_many(self: *Self, n: usize) bool {
|
||||||
const new_length = scalars.sub(self.values.len, n) orelse {
|
const new_length = ona.scalars.sub(self.values.len, n) orelse {
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -190,12 +186,12 @@ pub fn Sequential(comptime Value: type) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const writer = switch (Value) {
|
pub const writer = switch (Value) {
|
||||||
io.Byte => struct {
|
u8 => struct {
|
||||||
fn writer(self: *Self) io.Writer {
|
fn writer(self: *Self) ona.io.Writer {
|
||||||
return io.Writer.bind(Self, self, write);
|
return ona.io.Writer.bind(Self, self, write);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write(self: *Self, buffer: []const io.Byte) io.Error!usize {
|
fn write(self: *Self, buffer: []const u8) ona.io.Error!usize {
|
||||||
self.push_all(buffer) catch return error.UnavailableResource;
|
self.push_all(buffer) catch return error.UnavailableResource;
|
||||||
|
|
||||||
return buffer.len;
|
return buffer.len;
|
||||||
|
@ -208,7 +204,7 @@ pub fn Sequential(comptime Value: type) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn Parallel(comptime Value: type) type {
|
pub fn Parallel(comptime Value: type) type {
|
||||||
const Slices = slices.Parallel(Value);
|
const Slices = ona.slices.Parallel(Value);
|
||||||
const alignment = @alignOf(Value);
|
const alignment = @alignOf(Value);
|
||||||
|
|
||||||
return struct {
|
return struct {
|
||||||
|
@ -229,7 +225,7 @@ pub fn Parallel(comptime Value: type) type {
|
||||||
|
|
||||||
capacity_slice.len = self.cap;
|
capacity_slice.len = self.cap;
|
||||||
|
|
||||||
slices.parallel_free(Value, self.allocator, capacity_slice);
|
ona.slices.parallel_free(Value, self.allocator, capacity_slice);
|
||||||
|
|
||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
@ -244,11 +240,11 @@ pub fn Parallel(comptime Value: type) type {
|
||||||
|
|
||||||
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
|
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
|
||||||
const grown_capacity = self.cap + additional;
|
const grown_capacity = self.cap + additional;
|
||||||
const buffer = try slices.parallel_alloc(Value, self.allocator, grown_capacity);
|
const buffer = try ona.slices.parallel_alloc(Value, self.allocator, grown_capacity);
|
||||||
|
|
||||||
if (self.cap != 0) {
|
if (self.cap != 0) {
|
||||||
slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values);
|
ona.slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values);
|
||||||
slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?);
|
ona.slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.cap = grown_capacity;
|
self.cap = grown_capacity;
|
|
@ -1,8 +1,4 @@
|
||||||
const ascii = @import("./ascii.zig");
|
const ona = @import("./ona.zig");
|
||||||
|
|
||||||
const coral = @import("./coral.zig");
|
|
||||||
|
|
||||||
const io = @import("./io.zig");
|
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
||||||
|
@ -12,26 +8,26 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
|
||||||
|
|
||||||
errdefer allocator.free(buffer);
|
errdefer allocator.free(buffer);
|
||||||
|
|
||||||
// TODO: This is dumb.
|
// TODO: This is messy.
|
||||||
return @constCast(print_formatted(buffer, format, args) catch unreachable);
|
return @constCast(print_formatted(buffer, format, args) catch unreachable);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
|
pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
|
||||||
var count = io.NullWritable{};
|
var count = ona.io.NullWritable{};
|
||||||
|
|
||||||
write_formatted(count.writer(), format, args) catch unreachable;
|
write_formatted(count.writer(), format, args) catch unreachable;
|
||||||
|
|
||||||
return count.written;
|
return count.written;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]const u8 {
|
pub fn print_formatted(buffer: [:0]u8, comptime format: []const u8, args: anytype) ona.io.Error![:0]const u8 {
|
||||||
const Seekable = struct {
|
const Seekable = struct {
|
||||||
buffer: []coral.io.Byte,
|
buffer: []u8,
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
|
|
||||||
const Self = @This();
|
const Self = @This();
|
||||||
|
|
||||||
fn write(self: *Self, input: []const coral.io.Byte) io.Error!usize {
|
fn write(self: *Self, input: []const u8) ona.io.Error!usize {
|
||||||
const range = @min(input.len, self.buffer.len - self.cursor);
|
const range = @min(input.len, self.buffer.len - self.cursor);
|
||||||
const tail = self.cursor + range;
|
const tail = self.cursor + range;
|
||||||
|
|
||||||
|
@ -54,7 +50,7 @@ pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, a
|
||||||
.cursor = 0,
|
.cursor = 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
try write_formatted(coral.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args);
|
try write_formatted(ona.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args);
|
||||||
|
|
||||||
if (buffer.len < len) {
|
if (buffer.len < len) {
|
||||||
buffer[len] = 0;
|
buffer[len] = 0;
|
||||||
|
@ -63,7 +59,7 @@ pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, a
|
||||||
return buffer[0 .. len:0];
|
return buffer[0 .. len:0];
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.Error!void {
|
pub fn write_formatted(writer: ona.io.Writer, comptime format: []const u8, args: anytype) ona.io.Error!void {
|
||||||
switch (@typeInfo(@TypeOf(args))) {
|
switch (@typeInfo(@TypeOf(args))) {
|
||||||
.Struct => |arguments_struct| {
|
.Struct => |arguments_struct| {
|
||||||
comptime var arg_index = 0;
|
comptime var arg_index = 0;
|
||||||
|
@ -80,7 +76,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
|
||||||
|
|
||||||
switch (format[tail]) {
|
switch (format[tail]) {
|
||||||
'{' => {
|
'{' => {
|
||||||
try io.print(writer, format[head .. (tail - 1)]);
|
try ona.io.print(writer, format[head .. (tail - 1)]);
|
||||||
|
|
||||||
tail += 1;
|
tail += 1;
|
||||||
head = tail;
|
head = tail;
|
||||||
|
@ -91,7 +87,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
|
||||||
@compileError("all format specifiers must be named when using a named struct");
|
@compileError("all format specifiers must be named when using a named struct");
|
||||||
}
|
}
|
||||||
|
|
||||||
try io.print(writer, args[arg_index]);
|
try ona.io.print(writer, args[arg_index]);
|
||||||
|
|
||||||
arg_index += 1;
|
arg_index += 1;
|
||||||
tail += 1;
|
tail += 1;
|
||||||
|
@ -103,7 +99,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
|
||||||
@compileError("format specifiers cannot be named when using a tuple struct");
|
@compileError("format specifiers cannot be named when using a tuple struct");
|
||||||
}
|
}
|
||||||
|
|
||||||
try io.write_all(writer, format[head .. (tail - 1)]);
|
try ona.io.write_all(writer, format[head .. (tail - 1)]);
|
||||||
|
|
||||||
head = tail;
|
head = tail;
|
||||||
tail += 1;
|
tail += 1;
|
||||||
|
@ -129,25 +125,27 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try io.write_all(writer, format[head .. ]);
|
try ona.io.write_all(writer, format[head .. ]);
|
||||||
},
|
},
|
||||||
|
|
||||||
else => @compileError("`arguments` must be a struct type"),
|
else => @compileError("`arguments` must be a struct type"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void {
|
noinline fn print_formatted_value(writer: ona.io.Writer, value: anytype) ona.io.Error!void {
|
||||||
const Value = @TypeOf(value);
|
const Value = @TypeOf(value);
|
||||||
|
const hexadecimal_format = ona.ascii.HexadecimalFormat{};
|
||||||
|
const decimal_format = ona.ascii.DecimalFormat{};
|
||||||
|
|
||||||
return switch (@typeInfo(Value)) {
|
return switch (@typeInfo(Value)) {
|
||||||
.Int => ascii.DecimalFormat.default.format(writer, value),
|
.Int => decimal_format.format(writer, value),
|
||||||
.Float => ascii.DecimalFormat.default.format(writer, value),
|
.Float => decimal_format.format(writer, value),
|
||||||
.Enum => io.print(writer, @tagName(value)),
|
.Enum => ona.io.write_all(writer, @tagName(value)),
|
||||||
|
|
||||||
.Pointer => |pointer| switch (pointer.size) {
|
.Pointer => |pointer| switch (pointer.size) {
|
||||||
.Many, .C => ascii.HexadecimalFormat.default.format(writer, @intFromPtr(value)),
|
.Many, .C => hexadecimal_format.format(writer, @intFromPtr(value)),
|
||||||
.One => if (pointer.child == []const u8) io.write_all(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)),
|
.One => if (pointer.child == []const u8) ona.io.write_all(writer, *value) else hexadecimal_format.format(writer, @intFromPtr(value)),
|
||||||
.Slice => if (pointer.child == u8) io.write_all(writer, value) else @compileError(unformattableMessage(Value)),
|
.Slice => if (pointer.child == u8) ona.io.write_all(writer, value) else @compileError(unformattableMessage(Value)),
|
||||||
},
|
},
|
||||||
|
|
||||||
else => @compileError(unformattableMessage(Value)),
|
else => @compileError(unformattableMessage(Value)),
|
Loading…
Reference in New Issue