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
|
||||
*.spv filter=lfs diff=lfs merge=lfs -text
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# Generated assets
|
||||
.zig-cache
|
||||
/zig-out
|
||||
*.spv
|
||||
/src/**/*.spv
|
||||
/demos/**/*.out
|
||||
/demos/**/*.exe
|
||||
|
|
|
@ -5,8 +5,8 @@
|
|||
"name": "Runner",
|
||||
"type": "gdb",
|
||||
"request": "launch",
|
||||
"target": "${workspaceRoot}/zig-out/bin/main",
|
||||
"cwd": "${workspaceRoot}/debug/",
|
||||
"target": "${workspaceRoot}/demos/effects.out",
|
||||
"cwd": "${workspaceRoot}/demos/",
|
||||
"valuesFormatting": "prettyPrinters",
|
||||
"preLaunchTask": "Build All"
|
||||
},
|
||||
|
|
384
build.zig
384
build.zig
|
@ -2,44 +2,215 @@ const builtin = @import("builtin");
|
|||
|
||||
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 {
|
||||
buffer: [max]u8 = [_]u8{0} ** max,
|
||||
unused: u8 = max,
|
||||
|
||||
const max = 255;
|
||||
pub const max = 255;
|
||||
|
||||
pub fn utf8(self: *const SubPath) [:0]const u8 {
|
||||
return self.buffer[0 .. (max - self.unused):0];
|
||||
pub fn append(self: *SubPath, component: []const u8) !void {
|
||||
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 {
|
||||
const target = b.standardTargetOptions(.{});
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
var project = Project{
|
||||
.imports = ImportList.init(b.allocator),
|
||||
.target = b.standardTargetOptions(.{}),
|
||||
.optimize = b.standardOptimizeOption(.{}),
|
||||
};
|
||||
|
||||
const sokol_dependency = b.dependency("sokol", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.target = project.target,
|
||||
.optimize = project.optimize,
|
||||
});
|
||||
|
||||
const coral_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/coral/coral.zig"),
|
||||
});
|
||||
|
||||
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 ona_module = try project.add_module(b, "ona", .{});
|
||||
const input_module = try project.add_module(b, "input", .{});
|
||||
|
||||
const coral_module = try project.add_module(b, "coral", .{
|
||||
.imports = &.{
|
||||
.{
|
||||
.name = "sokol",
|
||||
|
@ -47,20 +218,20 @@ pub fn build(b: *std.Build) !void {
|
|||
},
|
||||
|
||||
.{
|
||||
.name = "coral",
|
||||
.module = coral_module,
|
||||
.name = "ona",
|
||||
.module = ona_module,
|
||||
},
|
||||
|
||||
.{
|
||||
.name = "flow",
|
||||
.module = flow_module,
|
||||
.name = "input",
|
||||
.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 sources = [_][]const u8{
|
||||
|
@ -78,8 +249,8 @@ pub fn build(b: *std.Build) !void {
|
|||
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "spirvcross",
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
.target = project.target,
|
||||
.optimize = project.optimize,
|
||||
});
|
||||
|
||||
switch (lib.rootModuleTarget().abi) {
|
||||
|
@ -90,135 +261,62 @@ pub fn build(b: *std.Build) !void {
|
|||
inline for (sources) |src| {
|
||||
lib.addCSourceFile(.{
|
||||
.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;
|
||||
});
|
||||
|
||||
b.step("test", "Run unit tests").dependOn(tests: {
|
||||
const tests = b.addTest(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
break: tests &tests.step;
|
||||
coral_module.linkSystemLibrary("SDL2", .{
|
||||
.needed = true,
|
||||
.preferred_link_mode = .dynamic,
|
||||
});
|
||||
|
||||
b.installArtifact(add: {
|
||||
const exe = b.addExecutable(.{
|
||||
.name = "main",
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
coral_module.link_libc = true;
|
||||
|
||||
exe.root_module.addImport("ona", ona_module);
|
||||
exe.root_module.addImport("coral", coral_module);
|
||||
exe.linkLibC();
|
||||
exe.linkSystemLibrary("SDL2");
|
||||
try project.find_tests(b);
|
||||
try project.find_demos(b);
|
||||
}
|
||||
|
||||
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, .{
|
||||
.iterate = true,
|
||||
});
|
||||
|
||||
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;
|
||||
switch (@typeInfo(Components)) {
|
||||
.Struct => |@"struct"| {
|
||||
if (!@"struct".is_tuple) {
|
||||
@compileError("`components` must be a tuple");
|
||||
}
|
||||
|
||||
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) {
|
||||
continue: scan_shaders;
|
||||
}
|
||||
inline for (components, 0 .. components.len) |component, i| {
|
||||
path.appendSlice(component) catch {
|
||||
return error.PathTooBig;
|
||||
};
|
||||
|
||||
const shader_name = std.fs.path.stem(entry.path);
|
||||
|
||||
for (pending_shaders.items) |pending_shader| {
|
||||
if (std.mem.endsWith(u8, pending_shader.source_sub_path.utf8(), shader_name)) {
|
||||
continue: scan_shaders;
|
||||
if (i < last_component_index and !std.mem.endsWith(u8, component, "/")) {
|
||||
path.append('/') catch {
|
||||
return error.PathTooBig;
|
||||
};
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
var shader = Shader{};
|
||||
const source_sub_path = try std.fmt.bufPrint(&shader.source_sub_path.buffer, "{s}{s}", .{shaders_sub_path, shader_name});
|
||||
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;
|
||||
}
|
||||
else => {
|
||||
@compileError("`components` cannot be a " ++ @typeName(Components));
|
||||
}
|
||||
}
|
||||
|
||||
for (pending_shaders.items) |pending_shader| {
|
||||
var vertex_binary_sub_path = SubPath{};
|
||||
var fragment_binary_sub_path = SubPath{};
|
||||
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;
|
||||
});
|
||||
return .{
|
||||
.unused = SubPath.max - path.len,
|
||||
.buffer = path.buffer,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
.{
|
||||
.name = "Ona",
|
||||
.name = "ona",
|
||||
.version = "0.0.1",
|
||||
.minimum_zig_version = "0.13.0",
|
||||
|
||||
.paths = .{
|
||||
"src",
|
||||
"build.zig",
|
||||
|
@ -8,10 +10,15 @@
|
|||
"LICENSE",
|
||||
"README.md",
|
||||
},
|
||||
|
||||
.dependencies = .{
|
||||
.sokol = .{
|
||||
.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
|
||||
.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
|
||||
|
||||
// Adapted from: https://www.shadertoy.com/view/4sf3Dr
|
||||
|
||||
layout (binding = 0) uniform sampler2D sprite;
|
||||
|
||||
layout (location = 0) in vec4 color;
|
||||
|
@ -13,14 +15,12 @@ layout (binding = 0) uniform Effect {
|
|||
float time;
|
||||
};
|
||||
|
||||
vec3 scanline(vec2 coord, vec3 screen)
|
||||
{
|
||||
vec3 scanline(vec2 coord, vec3 screen) {
|
||||
screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02;
|
||||
return screen;
|
||||
}
|
||||
|
||||
vec2 crt(vec2 coord, float bend)
|
||||
{
|
||||
vec2 crt(vec2 coord, float bend) {
|
||||
// put in symmetrical coords
|
||||
coord = (coord - 0.5) * 2.0;
|
||||
|
||||
|
@ -36,17 +36,13 @@ vec2 crt(vec2 coord, float bend)
|
|||
return coord;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
void main() {
|
||||
vec2 crtCoords = crt(uv, 4.8);
|
||||
|
||||
// Split the color channels
|
||||
texel.rgb = texture(sprite, crtCoords).rgb;
|
||||
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);
|
||||
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");
|
||||
|
||||
|
@ -14,18 +14,18 @@ textures: TexturePool,
|
|||
pub const Effect = struct {
|
||||
shader: sokol.gfx.Shader,
|
||||
pipeline: sokol.gfx.Pipeline,
|
||||
properties: []coral.io.Byte,
|
||||
properties: []u8,
|
||||
|
||||
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.destroyShader(self.shader);
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn init(desc: gfx.Effect.Desc) spirv.Error!Effect {
|
||||
var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
|
||||
pub fn init(desc: coral.Effect.Desc) spirv.Error!Effect {
|
||||
var spirv_arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
|
||||
|
||||
defer {
|
||||
spirv_arena.deinit();
|
||||
|
@ -80,13 +80,13 @@ pub const Effect = struct {
|
|||
break: pipeline_desc pipeline_desc;
|
||||
});
|
||||
|
||||
const properties = try coral.heap.allocator.alloc(
|
||||
coral.io.Byte,
|
||||
const properties = try ona.heap.allocator.alloc(
|
||||
u8,
|
||||
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
|
||||
);
|
||||
|
||||
errdefer {
|
||||
coral.heap.allocator.free(properties);
|
||||
ona.heap.allocator.free(properties);
|
||||
sokol.gfx.destroyPipeline(pipeline);
|
||||
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();
|
||||
|
||||
|
@ -282,7 +282,7 @@ pub const Texture = struct {
|
|||
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) {
|
||||
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
|
||||
.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);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
errdefer texture.deinit();
|
||||
|
@ -422,7 +422,7 @@ pub fn deinit(self: *Self) void {
|
|||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
|
||||
pub fn destroy_effect(self: *Self, handle: coral.Effect) bool {
|
||||
switch (handle) {
|
||||
.default => {},
|
||||
|
||||
|
@ -438,7 +438,7 @@ pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
|
|||
return true;
|
||||
}
|
||||
|
||||
pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
|
||||
pub fn destroy_texture(self: *Self, handle: coral.Texture) bool {
|
||||
switch (handle) {
|
||||
.default => {},
|
||||
|
||||
|
@ -454,18 +454,18 @@ pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
|
|||
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));
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
pub fn init() !Self {
|
||||
var pools = Self{
|
||||
.effects = EffectPool.init(coral.heap.allocator),
|
||||
.textures = TexturePool.init(coral.heap.allocator),
|
||||
.effects = EffectPool.init(ona.heap.allocator),
|
||||
.textures = TexturePool.init(ona.heap.allocator),
|
||||
};
|
||||
|
||||
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")),
|
||||
}));
|
||||
|
||||
assert.is_handle(gfx.Texture.default, try pools.create_texture(.{
|
||||
assert.is_handle(coral.Texture.default, try pools.create_texture(.{
|
||||
.format = .rgba8,
|
||||
|
||||
.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,
|
||||
|
||||
.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");
|
||||
|
||||
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 rendering = @import("./rendering.zig");
|
||||
|
||||
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 {
|
||||
return struct {
|
||||
entries: stack.Sequential(Entry),
|
||||
first_free_index: usize = 0,
|
||||
pub const LoadError = std.mem.Allocator.Error;
|
||||
|
||||
const Entry = union (enum) {
|
||||
free_index: usize,
|
||||
occupied: Value,
|
||||
pub const LoadFileError = LoadError || ona.files.ReadAllError || error {
|
||||
FormatUnsupported,
|
||||
};
|
||||
|
||||
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 {
|
||||
cursor: usize = 0,
|
||||
pool: *const Self,
|
||||
const LoadTextureWork = struct {
|
||||
desc: Texture.Desc,
|
||||
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture),
|
||||
};
|
||||
|
||||
pub fn next(self: *Values) ?*Value {
|
||||
while (self.cursor < self.pool.entries.len()) {
|
||||
defer self.cursor += 1;
|
||||
const RenderFrameWork = struct {
|
||||
clear_color: Color,
|
||||
width: u16,
|
||||
height: u16,
|
||||
finished: *std.Thread.ResetEvent,
|
||||
has_command_params: ?*ona.Params(Commands).Node,
|
||||
};
|
||||
|
||||
switch (self.pool.entries.values[self.cursor]) {
|
||||
.free_index => {
|
||||
continue;
|
||||
},
|
||||
const UnloadEffectWork = struct {
|
||||
handle: Effect,
|
||||
};
|
||||
|
||||
.occupied => |*occupied| {
|
||||
return occupied;
|
||||
},
|
||||
}
|
||||
}
|
||||
const UnloadTextureWork = struct {
|
||||
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 {
|
||||
self.entries.deinit();
|
||||
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 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;
|
||||
}
|
||||
|
||||
pub fn get(self: *Self, key: usize) ?*Value {
|
||||
return switch (self.entries.values[key]) {
|
||||
.free_index => null,
|
||||
.occupied => |*occupied| occupied,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Self {
|
||||
fn init(allocator: std.mem.Allocator) List {
|
||||
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,
|
||||
.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(_: 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 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 spirv = @import("./spirv.zig");
|
||||
const ona = @import("ona");
|
||||
|
||||
const sokol = @import("sokol");
|
||||
|
||||
const spirv = @import("./spirv.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
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_vertex_buffer: sokol.gfx.Buffer,
|
||||
drawn_count: usize = 0,
|
||||
flushed_count: usize = 0,
|
||||
current_source_texture: gfx.Texture = .default,
|
||||
current_target_texture: gfx.Texture = .backbuffer,
|
||||
current_effect: gfx.Effect = .default,
|
||||
current_source_texture: coral.Texture = .default,
|
||||
current_target_texture: coral.Texture = .backbuffer,
|
||||
current_effect: coral.Effect = .default,
|
||||
|
||||
const DrawTexture = extern struct {
|
||||
transform: gfx.Transform2D,
|
||||
transform: coral.Transform2D,
|
||||
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
|
||||
depth: f32 = 0,
|
||||
texture_offset: @Vector(2, f32) = @splat(0),
|
||||
|
@ -65,15 +67,15 @@ const Frame = struct {
|
|||
});
|
||||
|
||||
return .{
|
||||
.texture_batch_buffers = .{.allocator = coral.heap.allocator},
|
||||
.texture_batch_buffers = .{.allocator = ona.heap.allocator},
|
||||
.quad_index_buffer = quad_index_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) {
|
||||
self.flush(pools);
|
||||
self.flush(resources);
|
||||
}
|
||||
|
||||
self.current_source_texture = command.texture;
|
||||
|
@ -99,8 +101,8 @@ const Frame = struct {
|
|||
self.drawn_count += 1;
|
||||
}
|
||||
|
||||
pub fn finish(self: *Frame, pools: *Resources) void {
|
||||
self.flush(pools);
|
||||
pub fn finish(self: *Frame, resources: *Resources) void {
|
||||
self.flush(resources);
|
||||
|
||||
self.drawn_count = 0;
|
||||
self.flushed_count = 0;
|
||||
|
@ -109,7 +111,7 @@ const Frame = struct {
|
|||
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) {
|
||||
return;
|
||||
}
|
||||
|
@ -120,7 +122,7 @@ const Frame = struct {
|
|||
|
||||
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| {
|
||||
bindings.fs.images[0] = render.color_image;
|
||||
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);
|
||||
|
||||
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,
|
||||
.top = 0,
|
||||
.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) {
|
||||
self.flush(pools);
|
||||
self.flush(resources);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
var pass = sokol.gfx.Pass{
|
||||
|
@ -212,7 +214,7 @@ const Frame = struct {
|
|||
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"),
|
||||
.empty => @panic("Cannot render to empty textures"),
|
||||
.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;
|
||||
|
||||
const vertex_indices = .{
|
||||
|
@ -231,7 +237,19 @@ const vertex_indices = .{
|
|||
.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: {
|
||||
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({
|
||||
@cInclude("spirv-cross/spirv_cross_c.h");
|
||||
});
|
||||
|
||||
const ona = @import("../ona.zig");
|
||||
const ona = @import("ona");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
|
@ -202,7 +202,7 @@ pub const Stage = struct {
|
|||
}
|
||||
|
||||
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,
|
||||
.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;
|
||||
|
||||
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)),
|
||||
.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 states = @import("./states.zig");
|
||||
const ona = @import("./ona.zig");
|
||||
|
||||
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 {
|
||||
node: dag.Node,
|
||||
node: ona.dag.Node,
|
||||
systems: *Schedule,
|
||||
world: *World,
|
||||
world: *Self,
|
||||
|
||||
pub const ResourceAccess = std.meta.Tag(Schedule.ResourceAccess);
|
||||
|
||||
pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: states.TypeID) bool {
|
||||
pub fn accesses_state(self: BindContext, access: std.meta.Tag(StateAccess), id: ona.TypeID) bool {
|
||||
const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses;
|
||||
|
||||
for (resource_accesses.values) |resource_access| {
|
||||
|
@ -42,23 +40,23 @@ pub const BindContext = struct {
|
|||
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});
|
||||
}
|
||||
|
||||
const read_write_resource_nodes = lazily_create: {
|
||||
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, .{
|
||||
.allocator = coral.heap.allocator,
|
||||
.allocator = ona.heap.allocator,
|
||||
}));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -70,23 +68,23 @@ pub const BindContext = struct {
|
|||
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});
|
||||
}
|
||||
|
||||
const read_only_resource_nodes = lazily_create: {
|
||||
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, .{
|
||||
.allocator = coral.heap.allocator,
|
||||
.allocator = ona.heap.allocator,
|
||||
}));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -94,70 +92,48 @@ pub const BindContext = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub const Info = 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: states.ThreadRestriction = .none,
|
||||
const Dependency = struct {
|
||||
kind: Kind,
|
||||
id: usize,
|
||||
|
||||
pub const Parameter = struct {
|
||||
thread_restriction: states.ThreadRestriction,
|
||||
init: *const fn (*anyopaque, *anyopaque) void,
|
||||
bind: *const fn (std.mem.Allocator, BindContext) std.mem.Allocator.Error!*anyopaque,
|
||||
unbind: *const fn (std.mem.Allocator, *anyopaque, UnbindContext) void,
|
||||
const Kind = enum {
|
||||
after,
|
||||
before,
|
||||
};
|
||||
|
||||
pub fn used_parameters(self: *const Info) []const *const Parameter {
|
||||
return self.parameters[0 .. self.parameter_count];
|
||||
}
|
||||
};
|
||||
|
||||
pub const Order = struct {
|
||||
label: []const u8 = "",
|
||||
run_after: []const *const Info = &.{},
|
||||
run_before: []const *const Info = &.{},
|
||||
const Graph = ona.dag.Graph(struct {
|
||||
info: *const ona.SystemInfo,
|
||||
label: [:0]u8,
|
||||
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,
|
||||
graph: Graph,
|
||||
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_only_resource_id_nodes: ResourceNodeBundle,
|
||||
parallel_work_bundles: ParallelNodeBundles,
|
||||
blocking_work: NodeBundle,
|
||||
|
||||
const Dependency = struct {
|
||||
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 {
|
||||
pub fn deinit(self: *Schedule, world: *Self) void {
|
||||
{
|
||||
var nodes = self.system_id_nodes.entries();
|
||||
|
||||
|
@ -194,8 +170,8 @@ pub const Schedule = struct {
|
|||
}
|
||||
|
||||
system.resource_accesses.deinit();
|
||||
coral.heap.allocator.free(system.dependencies);
|
||||
coral.heap.allocator.free(system.label);
|
||||
ona.heap.allocator.free(system.dependencies);
|
||||
ona.heap.allocator.free(system.label);
|
||||
}
|
||||
|
||||
for (self.parallel_work_bundles.values) |*bundle| {
|
||||
|
@ -211,7 +187,7 @@ pub const Schedule = struct {
|
|||
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()) {
|
||||
const work = struct {
|
||||
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));
|
||||
|
||||
for (graph.edge_nodes(node).?) |edge| {
|
||||
|
@ -300,7 +276,7 @@ pub const Schedule = struct {
|
|||
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().?;
|
||||
|
||||
|
@ -381,21 +357,21 @@ pub const Schedule = struct {
|
|||
}
|
||||
|
||||
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();
|
||||
|
||||
const duped_label = try arena.allocator().dupeZ(u8, label);
|
||||
|
||||
return .{
|
||||
.graph = Graph.init(coral.heap.allocator),
|
||||
.graph = Graph.init(ona.heap.allocator),
|
||||
.label = duped_label,
|
||||
.arena = arena,
|
||||
.system_id_nodes = .{.allocator = coral.heap.allocator},
|
||||
.read_write_resource_id_nodes = .{.allocator = coral.heap.allocator},
|
||||
.read_only_resource_id_nodes = .{.allocator = coral.heap.allocator},
|
||||
.parallel_work_bundles = .{.allocator = coral.heap.allocator},
|
||||
.blocking_work = .{.allocator = coral.heap.allocator},
|
||||
.system_id_nodes = .{.allocator = ona.heap.allocator},
|
||||
.read_write_resource_id_nodes = .{.allocator = ona.heap.allocator},
|
||||
.read_only_resource_id_nodes = .{.allocator = ona.heap.allocator},
|
||||
.parallel_work_bundles = .{.allocator = ona.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();
|
||||
}
|
||||
|
||||
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 system_id = @intFromPtr(info);
|
||||
|
||||
|
@ -428,7 +404,7 @@ pub const Schedule = struct {
|
|||
|
||||
const dependencies = init: {
|
||||
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);
|
||||
|
||||
for (order.run_after) |after_system| {
|
||||
|
@ -452,17 +428,17 @@ pub const Schedule = struct {
|
|||
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(.{
|
||||
.info = info,
|
||||
.label = label,
|
||||
.dependencies = dependencies,
|
||||
.resource_accesses = .{.allocator = coral.heap.allocator},
|
||||
.resource_accesses = .{.allocator = ona.heap.allocator},
|
||||
});
|
||||
|
||||
const system = self.graph.get_ptr(node).?;
|
||||
|
@ -491,8 +467,144 @@ pub const Schedule = struct {
|
|||
}
|
||||
};
|
||||
|
||||
pub const UnbindContext = struct {
|
||||
world: *World,
|
||||
const Self = @This();
|
||||
|
||||
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 io = @import("./io.zig");
|
||||
|
||||
const scalars = @import("./scalars.zig");
|
||||
const ona = @import("./ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const DecimalFormat = struct {
|
||||
delimiter: []const coral.io.Byte,
|
||||
positive_prefix: enum {none, plus, space},
|
||||
|
||||
pub const default = DecimalFormat{
|
||||
.delimiter = "",
|
||||
.positive_prefix = .none,
|
||||
};
|
||||
delimiter: []const u8 = "",
|
||||
positive_prefix: enum {none, plus, space} = .none,
|
||||
|
||||
pub fn parse(self: DecimalFormat, utf8: []const u8, comptime Decimal: type) ?Decimal {
|
||||
if (utf8.len == 0) {
|
||||
|
@ -35,14 +26,14 @@ pub const DecimalFormat = struct {
|
|||
|
||||
switch (code) {
|
||||
'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 = scalars.add(result, offset_code) orelse return null;
|
||||
result = ona.scalars.mul(result, radix) orelse return null;
|
||||
result = ona.scalars.add(result, offset_code) orelse return null;
|
||||
},
|
||||
|
||||
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;
|
||||
}
|
||||
},
|
||||
|
@ -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) {
|
||||
return io.write_all(writer, switch (self.positive_prefix) {
|
||||
return ona.io.write_all(writer, switch (self.positive_prefix) {
|
||||
.none => "0",
|
||||
.plus => "+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| {
|
||||
if (value < 0) {
|
||||
try io.write_all(writer, "-");
|
||||
try ona.io.write_all(writer, "-");
|
||||
}
|
||||
|
||||
const Float = @TypeOf(value);
|
||||
|
@ -164,7 +155,7 @@ pub const DecimalFormat = struct {
|
|||
const integer = @as(Int, @intFromFloat(value));
|
||||
|
||||
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)));
|
||||
},
|
||||
|
||||
|
@ -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 {
|
||||
delimiter: []const u8 = "",
|
||||
positive_prefix: enum {none, plus, space} = .none,
|
||||
|
@ -184,7 +181,7 @@ pub const HexadecimalFormat = struct {
|
|||
.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.
|
||||
_ = self;
|
||||
_ = writer;
|
|
@ -1,4 +1,4 @@
|
|||
const coral = @import("coral");
|
||||
const ona = @import("./ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
|
@ -7,9 +7,9 @@ pub fn Graph(comptime Payload: type) type {
|
|||
node_count: usize = 0,
|
||||
table: NodeTables,
|
||||
|
||||
const NodeTables = coral.stack.Parallel(struct {
|
||||
const NodeTables = ona.stack.Parallel(struct {
|
||||
payload: Payload,
|
||||
edges: coral.stack.Sequential(Node),
|
||||
edges: ona.stack.Sequential(Node),
|
||||
is_occupied: bool = true,
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]io.Byte {
|
||||
const buffer = try allocator.alloc(io.Byte, @min((try self.stat(path)).size, options.limit));
|
||||
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(u8, @min((try self.stat(path)).size, options.limit));
|
||||
|
||||
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 {
|
||||
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) {
|
||||
return null;
|
||||
|
@ -105,7 +105,7 @@ pub const bundle = init: {
|
|||
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| {
|
||||
return switch (open_error) {
|
||||
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 coral = @import("./coral.zig");
|
||||
|
||||
const slices = @import("./slices.zig");
|
||||
const ona = @import("./ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub const Byte = u8;
|
||||
|
||||
pub const Error = error {
|
||||
UnavailableResource,
|
||||
};
|
||||
|
@ -111,7 +107,7 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type
|
|||
pub const NullWritable = struct {
|
||||
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;
|
||||
|
||||
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 {
|
||||
const buffer = coral.Stack(coral.Byte){.allocator = allocator};
|
||||
pub fn alloc_read(input: ona.io.Reader, allocator: std.mem.Allocator) []u8 {
|
||||
const buffer = ona.stack.Sequential(u8){.allocator = allocator};
|
||||
|
||||
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 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;
|
||||
|
||||
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 {
|
||||
var buffer = @as([512]coral.Byte, undefined);
|
||||
var buffer = @as([512]u8, undefined);
|
||||
var copied = @as(usize, 0);
|
||||
|
||||
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 {
|
||||
var buffer = @as([512]coral.Byte, undefined);
|
||||
var buffer = @as([512]u8, undefined);
|
||||
var remaining = limit;
|
||||
|
||||
while (true) {
|
|
@ -1,18 +1,5 @@
|
|||
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 {
|
||||
const multipled = v1 * v2;
|
||||
const vector_info = @typeInfo(@TypeOf(v1)).Vector;
|
||||
|
@ -62,15 +49,3 @@ pub fn normal(v: anytype) @TypeOf(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 hashes = @import("./hashes.zig");
|
||||
|
||||
const io = @import("./io.zig");
|
||||
const ona = @import("./ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
|
@ -250,13 +246,17 @@ pub fn enum_traits(comptime Enum: type) Traits(Enum) {
|
|||
|
||||
pub const string_traits = init: {
|
||||
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 {
|
||||
return hashes.djb2(@typeInfo(usize).Int, value);
|
||||
return ona.hashes.djb2(@typeInfo(usize).Int, value);
|
||||
}
|
||||
};
|
||||
|
||||
break: init Traits([]const u8){
|
||||
.are_equal = coral.io.are_equal,
|
||||
.are_equal = strings.are_equal,
|
||||
.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");
|
||||
|
||||
pub const utf8 = @import("./utf8.zig");
|
||||
|
||||
pub const World = @import("./World.zig");
|
||||
|
||||
pub const lina = @import("./lina.zig");
|
||||
|
||||
pub const App = struct {
|
||||
events: *const Events,
|
||||
target_frame_time: f64,
|
||||
|
@ -19,13 +35,13 @@ pub const App = struct {
|
|||
is_running: bool,
|
||||
|
||||
pub const Events = struct {
|
||||
load: flow.World.Event,
|
||||
pre_update: flow.World.Event,
|
||||
update: flow.World.Event,
|
||||
post_update: flow.World.Event,
|
||||
render: flow.World.Event,
|
||||
finish: flow.World.Event,
|
||||
exit: flow.World.Event,
|
||||
load: World.Event,
|
||||
pre_update: World.Event,
|
||||
update: World.Event,
|
||||
post_update: World.Event,
|
||||
render: World.Event,
|
||||
finish: World.Event,
|
||||
exit: World.Event,
|
||||
};
|
||||
|
||||
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,
|
||||
|
||||
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 {
|
||||
tick_rate: u64,
|
||||
execution: Execution,
|
||||
middlewares: []const *const Setup = default_middlewares,
|
||||
middleware: []const *const Setup,
|
||||
|
||||
pub const Execution = union (enum) {
|
||||
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 Write = flow.Write;
|
||||
|
||||
pub const default_middlewares = &.{
|
||||
gfx.setup,
|
||||
act.setup,
|
||||
};
|
||||
|
||||
pub fn launch_arg(flag: Flag) ?[]const u8 {
|
||||
return Flag.launch_args[@intFromEnum(flag)];
|
||||
pub const Node = struct {
|
||||
param: Value.Param,
|
||||
has_prev: ?*Node = null,
|
||||
has_next: ?*Node = null,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
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 {
|
||||
fn main() anyerror!void {
|
||||
defer {
|
||||
coral.heap.trace_leaks();
|
||||
ext.SDL_Quit();
|
||||
heap.trace_leaks();
|
||||
}
|
||||
|
||||
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_name = arg_span[0 .. arg_split_index];
|
||||
|
||||
for (Flag.values) |value| {
|
||||
for (LaunchFlag.values) |value| {
|
||||
const name = @tagName(value);
|
||||
|
||||
if (!std.mem.eql(u8, arg_name, name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Flag.launch_args[@intFromEnum(value)] =
|
||||
LaunchFlag.args[@intFromEnum(value)] =
|
||||
if (arg_split_index == arg_span.len)
|
||||
name
|
||||
else
|
||||
|
@ -100,7 +718,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
|
|||
}
|
||||
|
||||
var world = try switch (options.execution) {
|
||||
.single_threaded => flow.World.init(0),
|
||||
.single_threaded => World.init(0),
|
||||
|
||||
.thread_share => |thread_share| init: {
|
||||
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))));
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
for (options.middlewares) |setup_middleware| {
|
||||
inline for (options.middleware) |setup_middleware| {
|
||||
try setup_middleware(&world, events);
|
||||
}
|
||||
|
||||
|
@ -172,4 +790,81 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
|
|||
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");
|
||||
|
||||
pub fn ElementPtr(comptime Slice: type) type {
|
||||
|
@ -25,7 +23,7 @@ pub fn Parallel(comptime Type: type) type {
|
|||
|
||||
return struct {
|
||||
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 {
|
||||
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) {
|
||||
const alignment = @alignOf(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 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;
|
||||
|
||||
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;
|
||||
allocated.ptrs[i] = buffers[i].ptr;
|
||||
}
|
|
@ -1,14 +1,10 @@
|
|||
const io = @import("./io.zig");
|
||||
|
||||
const scalars = @import("./scalars.zig");
|
||||
|
||||
const slices = @import("./slices.zig");
|
||||
const ona = @import("./ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
pub fn Sequential(comptime Value: type) type {
|
||||
return struct {
|
||||
allocator: std.mem.Allocator,
|
||||
allocator: std.mem.Allocator = ona.heap.allocator,
|
||||
values: []Value = &.{},
|
||||
cap: usize = 0,
|
||||
|
||||
|
@ -82,7 +78,7 @@ pub fn Sequential(comptime Value: type) type {
|
|||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
|
@ -190,12 +186,12 @@ pub fn Sequential(comptime Value: type) type {
|
|||
}
|
||||
|
||||
pub const writer = switch (Value) {
|
||||
io.Byte => struct {
|
||||
fn writer(self: *Self) io.Writer {
|
||||
return io.Writer.bind(Self, self, write);
|
||||
u8 => struct {
|
||||
fn writer(self: *Self) ona.io.Writer {
|
||||
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;
|
||||
|
||||
return buffer.len;
|
||||
|
@ -208,7 +204,7 @@ pub fn Sequential(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);
|
||||
|
||||
return struct {
|
||||
|
@ -229,7 +225,7 @@ pub fn Parallel(comptime Value: type) type {
|
|||
|
||||
capacity_slice.len = self.cap;
|
||||
|
||||
slices.parallel_free(Value, self.allocator, capacity_slice);
|
||||
ona.slices.parallel_free(Value, self.allocator, capacity_slice);
|
||||
|
||||
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 {
|
||||
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) {
|
||||
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_copy(Value, buffer.slice_all(0, self.values.len).?, self.values);
|
||||
ona.slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?);
|
||||
}
|
||||
|
||||
self.cap = grown_capacity;
|
|
@ -1,8 +1,4 @@
|
|||
const ascii = @import("./ascii.zig");
|
||||
|
||||
const coral = @import("./coral.zig");
|
||||
|
||||
const io = @import("./io.zig");
|
||||
const ona = @import("./ona.zig");
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
|
@ -12,26 +8,26 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
|
|||
|
||||
errdefer allocator.free(buffer);
|
||||
|
||||
// TODO: This is dumb.
|
||||
// TODO: This is messy.
|
||||
return @constCast(print_formatted(buffer, format, args) catch unreachable);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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 {
|
||||
buffer: []coral.io.Byte,
|
||||
buffer: []u8,
|
||||
cursor: usize,
|
||||
|
||||
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 tail = self.cursor + range;
|
||||
|
||||
|
@ -54,7 +50,7 @@ pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, a
|
|||
.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) {
|
||||
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];
|
||||
}
|
||||
|
||||
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))) {
|
||||
.Struct => |arguments_struct| {
|
||||
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]) {
|
||||
'{' => {
|
||||
try io.print(writer, format[head .. (tail - 1)]);
|
||||
try ona.io.print(writer, format[head .. (tail - 1)]);
|
||||
|
||||
tail += 1;
|
||||
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");
|
||||
}
|
||||
|
||||
try io.print(writer, args[arg_index]);
|
||||
try ona.io.print(writer, args[arg_index]);
|
||||
|
||||
arg_index += 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");
|
||||
}
|
||||
|
||||
try io.write_all(writer, format[head .. (tail - 1)]);
|
||||
try ona.io.write_all(writer, format[head .. (tail - 1)]);
|
||||
|
||||
head = tail;
|
||||
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"),
|
||||
}
|
||||
}
|
||||
|
||||
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 hexadecimal_format = ona.ascii.HexadecimalFormat{};
|
||||
const decimal_format = ona.ascii.DecimalFormat{};
|
||||
|
||||
return switch (@typeInfo(Value)) {
|
||||
.Int => ascii.DecimalFormat.default.format(writer, value),
|
||||
.Float => ascii.DecimalFormat.default.format(writer, value),
|
||||
.Enum => io.print(writer, @tagName(value)),
|
||||
.Int => decimal_format.format(writer, value),
|
||||
.Float => decimal_format.format(writer, value),
|
||||
.Enum => ona.io.write_all(writer, @tagName(value)),
|
||||
|
||||
.Pointer => |pointer| switch (pointer.size) {
|
||||
.Many, .C => ascii.HexadecimalFormat.default.format(writer, @intFromPtr(value)),
|
||||
.One => if (pointer.child == []const u8) io.write_all(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)),
|
||||
.Slice => if (pointer.child == u8) io.write_all(writer, value) else @compileError(unformattableMessage(Value)),
|
||||
.Many, .C => hexadecimal_format.format(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) ona.io.write_all(writer, value) else @compileError(unformattableMessage(Value)),
|
||||
},
|
||||
|
||||
else => @compileError(unformattableMessage(Value)),
|
Loading…
Reference in New Issue