Convert project into Zig package and add demos (#55)
continuous-integration/drone/push Build is failing Details

This commit is contained in:
kayomn 2024-07-24 00:25:18 +01:00
parent 9323cad130
commit 6ec24c765c
43 changed files with 2226 additions and 4184 deletions

1
.gitattributes vendored
View File

@ -1 +1,2 @@
*.bmp filter=lfs diff=lfs merge=lfs -text *.bmp filter=lfs diff=lfs merge=lfs -text
*.spv filter=lfs diff=lfs merge=lfs -text

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
# Generated assets # Generated assets
.zig-cache .zig-cache
/zig-out /zig-out
*.spv /src/**/*.spv
/demos/**/*.out
/demos/**/*.exe

4
.vscode/launch.json vendored
View File

@ -5,8 +5,8 @@
"name": "Runner", "name": "Runner",
"type": "gdb", "type": "gdb",
"request": "launch", "request": "launch",
"target": "${workspaceRoot}/zig-out/bin/main", "target": "${workspaceRoot}/demos/effects.out",
"cwd": "${workspaceRoot}/debug/", "cwd": "${workspaceRoot}/demos/",
"valuesFormatting": "prettyPrinters", "valuesFormatting": "prettyPrinters",
"preLaunchTask": "Build All" "preLaunchTask": "Build All"
}, },

384
build.zig
View File

@ -2,44 +2,215 @@ const builtin = @import("builtin");
const std = @import("std"); const std = @import("std");
const ImportList = std.ArrayList(struct {
name: []const u8,
module: *std.Build.Module
});
const Project = struct {
target: std.Build.ResolvedTarget,
optimize: std.builtin.OptimizeMode,
imports: ImportList,
pub fn find_demos(self: Project, b: *std.Build) !void {
const demos = b.step("demos", "Build demos");
var dir = try std.fs.cwd().openDir("demos/", .{
.iterate = true,
});
defer {
dir.close();
}
var entries = try dir.walk(b.allocator);
defer {
entries.deinit();
}
while (try entries.next()) |entry| {
if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) {
continue;
}
const source_path = try sub_path(.{"demos", entry.basename});
var path_buffer = [_:0]u8{0} ** 255;
const demo = b.addExecutable(.{
.name = try std.fmt.bufPrint(&path_buffer, "{s}.out", .{std.fs.path.stem(entry.basename)}),
.root_source_file = b.path(source_path.bytes()),
.target = self.target,
.optimize = self.optimize,
});
for (self.imports.items) |import| {
demo.root_module.addImport(import.name, import.module);
}
demos.dependOn(&b.addInstallArtifact(demo, .{
.dest_dir = .{
.override = .{
.custom = "../demos/",
},
},
}).step);
}
}
pub fn find_tests(self: Project, b: *std.Build) !void {
const tests = b.step("test", "Build and run tests");
for (self.imports.items) |import| {
tests.dependOn(&b.addRunArtifact(b.addTest(.{
.root_source_file = import.module.root_source_file.?,
.target = self.target,
.optimize = self.optimize,
})).step);
}
}
pub fn add_module(self: *Project, b: *std.Build, comptime name: []const u8, options: std.Build.Module.CreateOptions) !*std.Build.Module {
const cwd = std.fs.cwd();
var corrected_options = options;
if (corrected_options.root_source_file == null) {
corrected_options.root_source_file = b.path("src/" ++ name ++ "/" ++ name ++ ".zig");
}
if (corrected_options.target == null) {
corrected_options.target = self.target;
}
if (corrected_options.optimize == null) {
corrected_options.optimize = self.optimize;
}
const module = b.addModule(name, corrected_options);
try self.imports.append(.{
.name = name,
.module = module
});
// TODO: Probably want to make it search the same path as any explicit root_source_path override for shaders.
const shaders_path = "src/" ++ name ++ "/shaders/";
var shaders_dir = cwd.openDir(shaders_path, .{.iterate = true}) catch |open_error| {
return switch (open_error) {
error.FileNotFound, error.NotDir => module,
else => open_error,
};
};
defer {
shaders_dir.close();
}
var shaders_entries = try shaders_dir.walk(b.allocator);
defer {
shaders_entries.deinit();
}
while (try shaders_entries.next()) |entry| {
if (entry.kind != .file) {
continue;
}
const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
if (!is_shader_file) {
continue;
}
var binary_buffer = [_:0]u8{0} ** 255;
const binary_name = try std.fmt.bufPrint(&binary_buffer, "{s}.spv", .{entry.path});
const full_source_path = try sub_path(.{shaders_path, entry.path});
const full_binary_path = try sub_path(.{shaders_path, binary_name});
const glslang_validator_args = [_][]const u8{
"glslangValidator",
"-V",
full_source_path.bytes(),
"-o",
full_binary_path.bytes(),
};
shaders_dir.access(binary_name, .{.mode = .read_only}) catch {
const output = b.run(&glslang_validator_args);
std.log.info("{s}", .{output[0 .. output.len - 1]});
continue;
};
if ((try shaders_dir.statFile(entry.path)).mtime > (try shaders_dir.statFile(binary_name)).mtime) {
const output = b.run(&glslang_validator_args);
std.log.info("{s}", .{output[0 .. output.len - 1]});
continue;
}
}
return module;
}
};
const SubPath = struct { const SubPath = struct {
buffer: [max]u8 = [_]u8{0} ** max, buffer: [max]u8 = [_]u8{0} ** max,
unused: u8 = max, unused: u8 = max,
const max = 255; pub const max = 255;
pub fn utf8(self: *const SubPath) [:0]const u8 { pub fn append(self: *SubPath, component: []const u8) !void {
return self.buffer[0 .. (max - self.unused):0]; const used = max - self.unused;
if (used != 0 and self.buffer[used - 1] != '/') {
if (component.len > self.unused) {
return error.PathTooBig;
}
@memcpy(self.buffer[used .. (used + component.len)], component);
self.unused -= @intCast(component.len);
} else {
const required_len = component.len + 1;
if (required_len > self.unused) {
return error.PathTooBig;
}
@memcpy(self.buffer[used .. (used + component.len)], component);
self.buffer[component.len] = '/';
self.unused -= @intCast(required_len);
}
}
pub fn bytes(self: *const SubPath) [:0]const u8 {
return @ptrCast(self.buffer[0 .. max - self.unused]);
} }
}; };
pub fn build(b: *std.Build) !void { pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{}); var project = Project{
const optimize = b.standardOptimizeOption(.{}); .imports = ImportList.init(b.allocator),
.target = b.standardTargetOptions(.{}),
.optimize = b.standardOptimizeOption(.{}),
};
const sokol_dependency = b.dependency("sokol", .{ const sokol_dependency = b.dependency("sokol", .{
.target = target, .target = project.target,
.optimize = optimize, .optimize = project.optimize,
}); });
const coral_module = b.createModule(.{ const ona_module = try project.add_module(b, "ona", .{});
.root_source_file = b.path("src/coral/coral.zig"), const input_module = try project.add_module(b, "input", .{});
});
const flow_module = b.createModule(.{
.root_source_file = b.path("src/flow/flow.zig"),
.imports = &.{
.{
.name = "coral",
.module = coral_module,
},
}
});
const ona_module = b.createModule(.{
.root_source_file = b.path("src/ona/ona.zig"),
const coral_module = try project.add_module(b, "coral", .{
.imports = &.{ .imports = &.{
.{ .{
.name = "sokol", .name = "sokol",
@ -47,20 +218,20 @@ pub fn build(b: *std.Build) !void {
}, },
.{ .{
.name = "coral", .name = "ona",
.module = coral_module, .module = ona_module,
}, },
.{ .{
.name = "flow", .name = "input",
.module = flow_module, .module = input_module,
}, },
}, },
}); });
ona_module.addIncludePath(b.path("ext/")); coral_module.addIncludePath(b.path("ext/"));
ona_module.linkLibrary(spirv_cross: { coral_module.linkLibrary(spirv_cross: {
const dir = "ext/spirv-cross/"; const dir = "ext/spirv-cross/";
const sources = [_][]const u8{ const sources = [_][]const u8{
@ -78,8 +249,8 @@ pub fn build(b: *std.Build) !void {
const lib = b.addStaticLibrary(.{ const lib = b.addStaticLibrary(.{
.name = "spirvcross", .name = "spirvcross",
.target = target, .target = project.target,
.optimize = optimize, .optimize = project.optimize,
}); });
switch (lib.rootModuleTarget().abi) { switch (lib.rootModuleTarget().abi) {
@ -90,135 +261,62 @@ pub fn build(b: *std.Build) !void {
inline for (sources) |src| { inline for (sources) |src| {
lib.addCSourceFile(.{ lib.addCSourceFile(.{
.file = b.path(dir ++ src), .file = b.path(dir ++ src),
.flags = &.{"-fstrict-aliasing", "-DSPIRV_CROSS_C_API_GLSL", "-DSPIRV_CROSS_C_API_HLSL", "-DSPIRV_CROSS_C_API_MSL"},
.flags = &.{
"-fstrict-aliasing",
"-DSPIRV_CROSS_C_API_GLSL",
"-DSPIRV_CROSS_C_API_HLSL",
"-DSPIRV_CROSS_C_API_MSL",
},
}); });
} }
break: spirv_cross lib; break: spirv_cross lib;
}); });
b.step("test", "Run unit tests").dependOn(tests: { coral_module.linkSystemLibrary("SDL2", .{
const tests = b.addTest(.{ .needed = true,
.root_source_file = b.path("src/main.zig"), .preferred_link_mode = .dynamic,
.target = target,
.optimize = optimize,
}); });
break: tests &tests.step; coral_module.link_libc = true;
});
b.installArtifact(add: { try project.find_tests(b);
const exe = b.addExecutable(.{ try project.find_demos(b);
.name = "main", }
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("ona", ona_module); fn sub_path(components: anytype) !SubPath {
exe.root_module.addImport("coral", coral_module); var path = comptime try std.BoundedArray(u8, SubPath.max).init(0);
exe.linkLibC(); const Components = @TypeOf(components);
exe.linkSystemLibrary("SDL2");
const shaders_sub_path = "src/ona/gfx/shaders/"; switch (@typeInfo(Components)) {
.Struct => |@"struct"| {
if (!@"struct".is_tuple) {
@compileError("`components` must be a tuple");
}
var shaders_dir = try std.fs.cwd().openDir(shaders_sub_path, .{ const last_component_index = components.len - 1;
.iterate = true,
});
defer shaders_dir.close(); inline for (components, 0 .. components.len) |component, i| {
path.appendSlice(component) catch {
var shaders_walker = try shaders_dir.walk(b.allocator); return error.PathTooBig;
defer shaders_walker.deinit();
const Shader = struct {
source_sub_path: SubPath = .{},
binary_sub_path: SubPath = .{},
}; };
var pending_shaders = std.ArrayList(Shader).init(b.allocator); if (i < last_component_index and !std.mem.endsWith(u8, component, "/")) {
path.append('/') catch {
defer pending_shaders.deinit(); return error.PathTooBig;
scan_shaders: while (try shaders_walker.next()) |entry| {
if (entry.kind != .file) {
continue: scan_shaders;
}
const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
if (!is_shader_file) {
continue: scan_shaders;
}
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;
}
}
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) { else => {
shader.source_sub_path.unused -= @intCast(source_sub_path.len); @compileError("`components` cannot be a " ++ @typeName(Components));
shader.binary_sub_path.unused -= @intCast(binary_sub_path.len);
try pending_shaders.append(shader);
continue: scan_shaders;
} }
} }
for (pending_shaders.items) |pending_shader| { return .{
var vertex_binary_sub_path = SubPath{}; .unused = SubPath.max - path.len,
var fragment_binary_sub_path = SubPath{}; .buffer = path.buffer,
const source_sub_path_utf8 = pending_shader.source_sub_path.utf8(); };
vertex_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&vertex_binary_sub_path.buffer, "{s}.vert.spv", .{source_sub_path_utf8})).len);
fragment_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&fragment_binary_sub_path.buffer, "{s}.frag.spv", .{source_sub_path_utf8})).len);
const vertex_binary_sub_path_utf8 = vertex_binary_sub_path.utf8();
const fragment_binary_sub_path_utf8 = fragment_binary_sub_path.utf8();
exe.step.dependOn(compile_vertex: {
const compile_command = b.addSystemCommand(&.{
"glslangValidator",
"-V",
vertex_binary_sub_path_utf8[0 .. vertex_binary_sub_path_utf8.len - 4],
"-o",
vertex_binary_sub_path_utf8,
});
break: compile_vertex &compile_command.step;
});
exe.step.dependOn(compile_fragment: {
const compile_command = b.addSystemCommand(&.{
"glslangValidator",
"-V",
fragment_binary_sub_path_utf8[0 .. fragment_binary_sub_path_utf8.len - 4],
"-o",
fragment_binary_sub_path_utf8,
});
break: compile_fragment &compile_command.step;
});
}
break: add exe;
});
} }

View File

@ -1,6 +1,8 @@
.{ .{
.name = "Ona", .name = "ona",
.version = "0.0.1", .version = "0.0.1",
.minimum_zig_version = "0.13.0",
.paths = .{ .paths = .{
"src", "src",
"build.zig", "build.zig",
@ -8,10 +10,15 @@
"LICENSE", "LICENSE",
"README.md", "README.md",
}, },
.dependencies = .{ .dependencies = .{
.sokol = .{ .sokol = .{
.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27", .url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569", .hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
}, },
.@"spirv-cross" = . {
.path = "./ext/spirv-cross",
},
}, },
} }

BIN
debug/actor.bmp (Stored with Git LFS)

Binary file not shown.

View File

@ -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);
}

View File

@ -1,5 +1,7 @@
#version 430 #version 430
// Adapted from: https://www.shadertoy.com/view/4sf3Dr
layout (binding = 0) uniform sampler2D sprite; layout (binding = 0) uniform sampler2D sprite;
layout (location = 0) in vec4 color; layout (location = 0) in vec4 color;
@ -13,14 +15,12 @@ layout (binding = 0) uniform Effect {
float time; float time;
}; };
vec3 scanline(vec2 coord, vec3 screen) vec3 scanline(vec2 coord, vec3 screen) {
{
screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02; screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02;
return screen; return screen;
} }
vec2 crt(vec2 coord, float bend) vec2 crt(vec2 coord, float bend) {
{
// put in symmetrical coords // put in symmetrical coords
coord = (coord - 0.5) * 2.0; coord = (coord - 0.5) * 2.0;
@ -36,17 +36,13 @@ vec2 crt(vec2 coord, float bend)
return coord; return coord;
} }
void main() void main() {
{
vec2 crtCoords = crt(uv, 4.8); vec2 crtCoords = crt(uv, 4.8);
// Split the color channels // Split the color channels
texel.rgb = texture(sprite, crtCoords).rgb; texel.rgb = texture(sprite, crtCoords).rgb;
texel.a = 1; texel.a = 1;
// HACK: this bend produces a shitty moire pattern.
// Up the bend for the scanline
vec2 screenSpace = crtCoords * vec2(screen_width, screen_height); vec2 screenSpace = crtCoords * vec2(screen_width, screen_height);
texel.rgb = scanline(screenSpace, texel.rgb); texel.rgb = scanline(screenSpace, texel.rgb);
} }

BIN
demos/crt.frag.spv (Stored with Git LFS) Normal file

Binary file not shown.

92
demos/effects.zig Normal file
View File

@ -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"});
}

View File

@ -1,6 +1,6 @@
const coral = @import("coral"); const coral = @import("./coral.zig");
const gfx = @import("../gfx.zig"); const ona = @import("ona");
const sokol = @import("sokol"); const sokol = @import("sokol");
@ -14,18 +14,18 @@ textures: TexturePool,
pub const Effect = struct { pub const Effect = struct {
shader: sokol.gfx.Shader, shader: sokol.gfx.Shader,
pipeline: sokol.gfx.Pipeline, pipeline: sokol.gfx.Pipeline,
properties: []coral.io.Byte, properties: []u8,
pub fn deinit(self: *Effect) void { pub fn deinit(self: *Effect) void {
coral.heap.allocator.free(self.properties); ona.heap.allocator.free(self.properties);
sokol.gfx.destroyPipeline(self.pipeline); sokol.gfx.destroyPipeline(self.pipeline);
sokol.gfx.destroyShader(self.shader); sokol.gfx.destroyShader(self.shader);
self.* = undefined; self.* = undefined;
} }
pub fn init(desc: gfx.Effect.Desc) spirv.Error!Effect { pub fn init(desc: coral.Effect.Desc) spirv.Error!Effect {
var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator); var spirv_arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
defer { defer {
spirv_arena.deinit(); spirv_arena.deinit();
@ -80,13 +80,13 @@ pub const Effect = struct {
break: pipeline_desc pipeline_desc; break: pipeline_desc pipeline_desc;
}); });
const properties = try coral.heap.allocator.alloc( const properties = try ona.heap.allocator.alloc(
coral.io.Byte, u8,
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0, if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
); );
errdefer { errdefer {
coral.heap.allocator.free(properties); ona.heap.allocator.free(properties);
sokol.gfx.destroyPipeline(pipeline); sokol.gfx.destroyPipeline(pipeline);
sokol.gfx.destroyShader(shader); sokol.gfx.destroyShader(shader);
} }
@ -239,7 +239,7 @@ pub const Effect = struct {
}; };
}; };
const EffectPool = coral.Pool(Effect); const EffectPool = ona.Pool(Effect);
const Self = @This(); const Self = @This();
@ -282,7 +282,7 @@ pub const Texture = struct {
self.* = undefined; self.* = undefined;
} }
pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture { pub fn init(desc: coral.Texture.Desc) std.mem.Allocator.Error!Texture {
const pixel_format = switch (desc.format) { const pixel_format = switch (desc.format) {
.rgba8 => sokol.gfx.PixelFormat.RGBA8, .rgba8 => sokol.gfx.PixelFormat.RGBA8,
.bgra8 => sokol.gfx.PixelFormat.BGRA8, .bgra8 => sokol.gfx.PixelFormat.BGRA8,
@ -384,9 +384,9 @@ pub const Texture = struct {
} }
}; };
const TexturePool = coral.Pool(Texture); const TexturePool = ona.Pool(Texture);
pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect { pub fn create_effect(self: *Self, desc: coral.Effect.Desc) !coral.Effect {
var effect = try Effect.init(desc); var effect = try Effect.init(desc);
errdefer effect.deinit(); errdefer effect.deinit();
@ -394,7 +394,7 @@ pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect {
return @enumFromInt(try self.effects.insert(effect)); return @enumFromInt(try self.effects.insert(effect));
} }
pub fn create_texture(self: *Self, desc: gfx.Texture.Desc) !gfx.Texture { pub fn create_texture(self: *Self, desc: coral.Texture.Desc) !coral.Texture {
var texture = try Texture.init(desc); var texture = try Texture.init(desc);
errdefer texture.deinit(); errdefer texture.deinit();
@ -422,7 +422,7 @@ pub fn deinit(self: *Self) void {
self.* = undefined; self.* = undefined;
} }
pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool { pub fn destroy_effect(self: *Self, handle: coral.Effect) bool {
switch (handle) { switch (handle) {
.default => {}, .default => {},
@ -438,7 +438,7 @@ pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
return true; return true;
} }
pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool { pub fn destroy_texture(self: *Self, handle: coral.Texture) bool {
switch (handle) { switch (handle) {
.default => {}, .default => {},
@ -454,18 +454,18 @@ pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
return true; return true;
} }
pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect { pub fn get_effect(self: *Self, handle: coral.Effect) ?*Effect {
return self.effects.get(@intFromEnum(handle)); return self.effects.get(@intFromEnum(handle));
} }
pub fn get_texture(self: *Self, handle: gfx.Texture) ?*Texture { pub fn get_texture(self: *Self, handle: coral.Texture) ?*Texture {
return self.textures.get(@intFromEnum(handle)); return self.textures.get(@intFromEnum(handle));
} }
pub fn init() !Self { pub fn init() !Self {
var pools = Self{ var pools = Self{
.effects = EffectPool.init(coral.heap.allocator), .effects = EffectPool.init(ona.heap.allocator),
.textures = TexturePool.init(coral.heap.allocator), .textures = TexturePool.init(ona.heap.allocator),
}; };
errdefer { errdefer {
@ -478,11 +478,11 @@ pub fn init() !Self {
} }
}; };
assert.is_handle(gfx.Effect.default, try pools.create_effect(.{ assert.is_handle(coral.Effect.default, try pools.create_effect(.{
.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), .fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
})); }));
assert.is_handle(gfx.Texture.default, try pools.create_texture(.{ assert.is_handle(coral.Texture.default, try pools.create_texture(.{
.format = .rgba8, .format = .rgba8,
.access = .{ .access = .{
@ -503,7 +503,7 @@ pub fn init() !Self {
}, },
})); }));
assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{ assert.is_handle(coral.Texture.backbuffer, try pools.create_texture(.{
.format = .rgba8, .format = .rgba8,
.access = .{ .access = .{

13
src/coral/colors.zig Normal file
View File

@ -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};
}

View File

@ -1,125 +1,524 @@
pub const ascii = @import("./ascii.zig"); pub const colors = @import("./colors.zig");
pub const asyncio = @import("./asyncio.zig"); const input = @import("input");
pub const files = @import("./files.zig"); const ona = @import("ona");
pub const hashes = @import("./hashes.zig"); const ext = @cImport({
@cInclude("SDL2/SDL.h");
});
pub const heap = @import("./heap.zig"); const rendering = @import("./rendering.zig");
pub const io = @import("./io.zig");
pub const lina = @import("./lina.zig");
pub const map = @import("./map.zig");
pub const scalars = @import("./scalars.zig");
pub const slices = @import("./slices.zig");
pub const stack = @import("./stack.zig");
const std = @import("std"); const std = @import("std");
pub const utf8 = @import("./utf8.zig"); pub const Assets = struct {
window: *ext.SDL_Window,
texture_formats: ona.stack.Sequential(TextureFormat),
frame_rendered: std.Thread.ResetEvent = .{},
pending_work: WorkQueue = .{},
has_worker_thread: ?std.Thread = null,
pub fn Pool(comptime Value: type) type { pub const LoadError = std.mem.Allocator.Error;
return struct {
entries: stack.Sequential(Entry),
first_free_index: usize = 0,
const Entry = union (enum) { pub const LoadFileError = LoadError || ona.files.ReadAllError || error {
free_index: usize, FormatUnsupported,
occupied: Value,
}; };
pub const Values = struct { pub const TextureFormat = struct {
cursor: usize = 0, extension: []const u8,
pool: *const Self, load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc,
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 const WorkQueue = ona.asyncio.BlockingQueue(1024, union (enum) {
load_effect: LoadEffectWork,
load_texture: LoadTextureWork,
render_frame: RenderFrameWork,
shutdown,
unload_effect: UnloadEffectWork,
unload_texture: UnloadTextureWork,
pub fn deinit(self: *Self) void { const LoadEffectWork = struct {
self.entries.deinit(); desc: Effect.Desc,
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect),
};
const LoadTextureWork = struct {
desc: Texture.Desc,
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture),
};
const RenderFrameWork = struct {
clear_color: Color,
width: u16,
height: u16,
finished: *std.Thread.ResetEvent,
has_command_params: ?*ona.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; self.* = undefined;
} }
pub fn get(self: *Self, key: usize) ?*Value { fn init() !Assets {
return switch (self.entries.values[key]) { const window = create: {
.free_index => null, const position = ext.SDL_WINDOWPOS_CENTERED;
.occupied => |*occupied| occupied, 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);
} }
pub fn init(allocator: std.mem.Allocator) Self {
return .{ return .{
.entries = .{.allocator = allocator}, .texture_formats = .{.allocator = ona.heap.allocator},
.window = window,
}; };
} }
pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize { pub fn load_effect_file(self: *Assets, storage: ona.files.Storage, path: []const u8) LoadFileError!Effect {
const entries_count = self.entries.len(); if (!std.mem.endsWith(u8, path, ".spv")) {
return error.FormatUnsupported;
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; const fragment_file_stat = try storage.stat(path);
const fragment_spirv_ops = try ona.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
self.first_free_index = self.entries.values[self.first_free_index].free_index; defer {
self.entries.values[insersion_index] = .{.occupied = value}; ona.heap.allocator.free(fragment_spirv_ops);
return insersion_index;
} }
pub fn remove(self: *Self, key: usize) ?Value { const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{});
if (key >= self.entries.len()) {
return null;
}
switch (self.entries.values[key]) { std.debug.assert(bytes_read.len == fragment_file_stat.size);
.free_index => {
return null; var loaded = ona.asyncio.Future(std.mem.Allocator.Error!Effect){};
self.pending_work.enqueue(.{
.load_effect = .{
.desc = .{
.fragment_spirv_ops = fragment_spirv_ops,
}, },
.occupied => |occupied| { .loaded = &loaded,
self.entries.values[key] = .{.free_index = self.first_free_index};
self.first_free_index = key;
return occupied;
}, },
});
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;
}
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 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", .{});
} }
} }
pub fn values(self: *const Self) Values { fn deinit(self: *List) void {
self.arena.deinit();
self.stack.deinit();
self.* = undefined;
}
fn init(allocator: std.mem.Allocator) List {
return .{ 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();
}
} }

View File

@ -1,29 +1,31 @@
const Resources = @import("./Resources.zig"); const Resources = @import("./Resources.zig");
const coral = @import("coral"); const coral = @import("./coral.zig");
const ext = @import("../ext.zig"); const ext = @cImport({
@cInclude("SDL2/SDL.h");
});
const gfx = @import("../gfx.zig"); const ona = @import("ona");
const spirv = @import("./spirv.zig");
const sokol = @import("sokol"); const sokol = @import("sokol");
const spirv = @import("./spirv.zig");
const std = @import("std"); const std = @import("std");
const Frame = struct { const Frame = struct {
texture_batch_buffers: coral.stack.Sequential(sokol.gfx.Buffer), texture_batch_buffers: ona.stack.Sequential(sokol.gfx.Buffer),
quad_index_buffer: sokol.gfx.Buffer, quad_index_buffer: sokol.gfx.Buffer,
quad_vertex_buffer: sokol.gfx.Buffer, quad_vertex_buffer: sokol.gfx.Buffer,
drawn_count: usize = 0, drawn_count: usize = 0,
flushed_count: usize = 0, flushed_count: usize = 0,
current_source_texture: gfx.Texture = .default, current_source_texture: coral.Texture = .default,
current_target_texture: gfx.Texture = .backbuffer, current_target_texture: coral.Texture = .backbuffer,
current_effect: gfx.Effect = .default, current_effect: coral.Effect = .default,
const DrawTexture = extern struct { const DrawTexture = extern struct {
transform: gfx.Transform2D, transform: coral.Transform2D,
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
depth: f32 = 0, depth: f32 = 0,
texture_offset: @Vector(2, f32) = @splat(0), texture_offset: @Vector(2, f32) = @splat(0),
@ -65,15 +67,15 @@ const Frame = struct {
}); });
return .{ return .{
.texture_batch_buffers = .{.allocator = coral.heap.allocator}, .texture_batch_buffers = .{.allocator = ona.heap.allocator},
.quad_index_buffer = quad_index_buffer, .quad_index_buffer = quad_index_buffer,
.quad_vertex_buffer = quad_vertex_buffer, .quad_vertex_buffer = quad_vertex_buffer,
}; };
} }
pub fn draw_texture(self: *Frame, pools: *Resources, command: gfx.Commands.DrawTextureCommand) !void { pub fn draw_texture(self: *Frame, resources: *Resources, command: coral.Commands.DrawTextureCommand) !void {
if (command.texture != self.current_source_texture) { if (command.texture != self.current_source_texture) {
self.flush(pools); self.flush(resources);
} }
self.current_source_texture = command.texture; self.current_source_texture = command.texture;
@ -99,8 +101,8 @@ const Frame = struct {
self.drawn_count += 1; self.drawn_count += 1;
} }
pub fn finish(self: *Frame, pools: *Resources) void { pub fn finish(self: *Frame, resources: *Resources) void {
self.flush(pools); self.flush(resources);
self.drawn_count = 0; self.drawn_count = 0;
self.flushed_count = 0; self.flushed_count = 0;
@ -109,7 +111,7 @@ const Frame = struct {
self.current_effect = .default; self.current_effect = .default;
} }
pub fn flush(self: *Frame, pools: *Resources) void { pub fn flush(self: *Frame, resources: *Resources) void {
if (self.flushed_count == self.drawn_count) { if (self.flushed_count == self.drawn_count) {
return; return;
} }
@ -120,7 +122,7 @@ const Frame = struct {
bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer; bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer;
switch (pools.get_texture(self.current_source_texture).?.access) { switch (resources.get_texture(self.current_source_texture).?.access) {
.render => |render| { .render => |render| {
bindings.fs.images[0] = render.color_image; bindings.fs.images[0] = render.color_image;
bindings.fs.samplers[0] = default_sampler; bindings.fs.samplers[0] = default_sampler;
@ -136,13 +138,13 @@ const Frame = struct {
}, },
} }
const effect = pools.get_effect(self.current_effect).?; const effect = resources.get_effect(self.current_effect).?;
sokol.gfx.applyPipeline(effect.pipeline); sokol.gfx.applyPipeline(effect.pipeline);
const texture = pools.get_texture(self.current_target_texture).?; const texture = resources.get_texture(self.current_target_texture).?;
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{ sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{
.left = 0, .left = 0,
.top = 0, .top = 0,
.right = @floatFromInt(texture.width), .right = @floatFromInt(texture.width),
@ -171,19 +173,19 @@ const Frame = struct {
} }
} }
pub fn set_effect(self: *Frame, pools: *Resources, command: gfx.Commands.SetEffectCommand) void { pub fn set_effect(self: *Frame, resources: *Resources, command: coral.Commands.SetEffectCommand) void {
if (command.effect != self.current_effect) { if (command.effect != self.current_effect) {
self.flush(pools); self.flush(resources);
} }
self.current_effect = command.effect; self.current_effect = command.effect;
if (pools.get_effect(self.current_effect)) |effect| { if (resources.get_effect(self.current_effect)) |effect| {
@memcpy(effect.properties, command.properties); @memcpy(effect.properties, command.properties);
} }
} }
pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void { pub fn set_target(self: *Frame, resources: *Resources, command: coral.Commands.SetTargetCommand) void {
sokol.gfx.endPass(); sokol.gfx.endPass();
var pass = sokol.gfx.Pass{ var pass = sokol.gfx.Pass{
@ -212,7 +214,7 @@ const Frame = struct {
pass.action.depth = .{.load_action = .LOAD}; pass.action.depth = .{.load_action = .LOAD};
} }
pass.attachments = switch (pools.get_texture(self.current_target_texture).?.access) { pass.attachments = switch (resources.get_texture(self.current_target_texture).?.access) {
.static => @panic("Cannot render to static textures"), .static => @panic("Cannot render to static textures"),
.empty => @panic("Cannot render to empty textures"), .empty => @panic("Cannot render to empty textures"),
.render => |render| render.attachments, .render => |render| render.attachments,
@ -224,6 +226,10 @@ const Frame = struct {
} }
}; };
fn Matrix(comptime n: usize, comptime Element: type) type {
return [n]@Vector(n, Element);
}
var default_sampler: sokol.gfx.Sampler = undefined; var default_sampler: sokol.gfx.Sampler = undefined;
const vertex_indices = .{ const vertex_indices = .{
@ -231,7 +237,19 @@ const vertex_indices = .{
.instance = 1, .instance = 1,
}; };
pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void { fn orthographic_projection(near: f32, far: f32, viewport: coral.Rect) Matrix(4, f32) {
const width = viewport.right - viewport.left;
const height = viewport.bottom - viewport.top;
return .{
.{2 / width, 0, 0, 0},
.{0, 2 / height, 0, 0},
.{0, 0, 1 / (far - near), 0},
.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
};
}
pub fn process_work(pending_work: *coral.Assets.WorkQueue, window: *ext.SDL_Window) !void {
const context = configure_and_create: { const context = configure_and_create: {
var result = @as(c_int, 0); var result = @as(c_int, 0);

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,10 @@
const coral = @import("coral"); const coral = @import("./coral.zig");
const ext = @cImport({ const ext = @cImport({
@cInclude("spirv-cross/spirv_cross_c.h"); @cInclude("spirv-cross/spirv_cross_c.h");
}); });
const ona = @import("../ona.zig"); const ona = @import("ona");
const std = @import("std"); const std = @import("std");
@ -202,7 +202,7 @@ pub const Stage = struct {
} }
uniform.* = .{ uniform.* = .{
.name = try coral.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{ .name = try ona.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{
.id = reflected_resource.id, .id = reflected_resource.id,
.member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)), .member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)),
}), }),
@ -357,7 +357,7 @@ fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Tar
var binding: u32 = 0; var binding: u32 = 0;
for (combined_image_samplers) |combined_image_sampler| { for (combined_image_samplers) |combined_image_sampler| {
const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{ const name = try ona.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)), .image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)), .sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
}); });

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

274
src/input/input.zig Normal file
View File

@ -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());
},
}
}
}

View File

@ -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"});
}

View File

@ -1,21 +1,19 @@
const coral = @import("coral"); const builtin = @import("builtin");
const dag = @import("./dag.zig"); const ona = @import("./ona.zig");
const states = @import("./states.zig");
const std = @import("std"); const std = @import("std");
const World = @import("./World.zig"); thread_pool: ?*std.Thread.Pool = null,
thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable,
event_systems: ona.stack.Sequential(Schedule),
pub const BindContext = struct { pub const BindContext = struct {
node: dag.Node, node: ona.dag.Node,
systems: *Schedule, systems: *Schedule,
world: *World, world: *Self,
pub const ResourceAccess = std.meta.Tag(Schedule.ResourceAccess); pub fn accesses_state(self: BindContext, access: std.meta.Tag(StateAccess), id: ona.TypeID) bool {
pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: states.TypeID) bool {
const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses; const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses;
for (resource_accesses.values) |resource_access| { for (resource_accesses.values) |resource_access| {
@ -42,23 +40,23 @@ pub const BindContext = struct {
return null; return null;
}; };
const id = states.type_id(Resource); const id = ona.type_id(Resource);
if (!self.accesses_resource(.read_write, id)) { if (!self.accesses_state(.read_write, id)) {
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id}); try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id});
} }
const read_write_resource_nodes = lazily_create: { const read_write_resource_nodes = lazily_create: {
break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: { break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: {
std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{ std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
.allocator = coral.heap.allocator, .allocator = ona.heap.allocator,
})); }));
break: insert self.systems.read_write_resource_id_nodes.get(id).?; break: insert self.systems.read_write_resource_id_nodes.get(id).?;
}; };
}; };
if (std.mem.indexOfScalar(dag.Node, read_write_resource_nodes.values, self.node) == null) { if (std.mem.indexOfScalar(ona.dag.Node, read_write_resource_nodes.values, self.node) == null) {
try read_write_resource_nodes.push_grow(self.node); try read_write_resource_nodes.push_grow(self.node);
} }
@ -70,23 +68,23 @@ pub const BindContext = struct {
return null; return null;
}; };
const id = states.type_id(Resource); const id = ona.type_id(Resource);
if (!self.accesses_resource(.read_only, id)) { if (!self.accesses_state(.read_only, id)) {
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id}); try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id});
} }
const read_only_resource_nodes = lazily_create: { const read_only_resource_nodes = lazily_create: {
break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: { break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: {
std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{ std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
.allocator = coral.heap.allocator, .allocator = ona.heap.allocator,
})); }));
break: insert self.systems.read_only_resource_id_nodes.get(id).?; break: insert self.systems.read_only_resource_id_nodes.get(id).?;
}; };
}; };
if (std.mem.indexOfScalar(dag.Node, read_only_resource_nodes.values, self.node) == null) { if (std.mem.indexOfScalar(ona.dag.Node, read_only_resource_nodes.values, self.node) == null) {
try read_only_resource_nodes.push_grow(self.node); try read_only_resource_nodes.push_grow(self.node);
} }
@ -94,40 +92,6 @@ 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,
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,
};
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 = &.{},
};
pub 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),
read_write_resource_id_nodes: ResourceNodeBundle,
read_only_resource_id_nodes: ResourceNodeBundle,
parallel_work_bundles: ParallelNodeBundles,
blocking_work: NodeBundle,
const Dependency = struct { const Dependency = struct {
kind: Kind, kind: Kind,
id: usize, id: usize,
@ -138,26 +102,38 @@ pub const Schedule = struct {
}; };
}; };
const Graph = dag.Graph(struct { const Graph = ona.dag.Graph(struct {
info: *const Info, info: *const ona.SystemInfo,
label: [:0]u8, label: [:0]u8,
dependencies: []Dependency, dependencies: []Dependency,
parameter_states: [max_parameters]*anyopaque = [_]*anyopaque{undefined} ** max_parameters, parameter_states: [ona.SystemInfo.max_parameters]*anyopaque = [_]*anyopaque{undefined} ** ona.SystemInfo.max_parameters,
resource_accesses: coral.stack.Sequential(ResourceAccess), resource_accesses: ona.stack.Sequential(StateAccess),
}); });
const NodeBundle = coral.stack.Sequential(dag.Node); pub const Event = enum (usize) { _ };
const ParallelNodeBundles = coral.stack.Sequential(NodeBundle); const ParallelNodeBundles = ona.stack.Sequential(NodeBundle);
const ResourceAccess = union (enum) { const NodeBundle = ona.stack.Sequential(ona.dag.Node);
read_only: states.TypeID,
read_write: states.TypeID, const StateAccess = union (enum) {
read_only: ona.TypeID,
read_write: ona.TypeID,
}; };
const ResourceNodeBundle = coral.map.Hashed(states.TypeID, NodeBundle, coral.map.enum_traits(states.TypeID)); const ResourceNodeBundle = ona.map.Hashed(ona.TypeID, NodeBundle, ona.map.enum_traits(ona.TypeID));
pub fn deinit(self: *Schedule, world: *World) void { const Schedule = struct {
label: [:0]const u8,
graph: Graph,
arena: std.heap.ArenaAllocator,
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,
pub fn deinit(self: *Schedule, world: *Self) void {
{ {
var nodes = self.system_id_nodes.entries(); var nodes = self.system_id_nodes.entries();
@ -194,8 +170,8 @@ pub const Schedule = struct {
} }
system.resource_accesses.deinit(); system.resource_accesses.deinit();
coral.heap.allocator.free(system.dependencies); ona.heap.allocator.free(system.dependencies);
coral.heap.allocator.free(system.label); ona.heap.allocator.free(system.label);
} }
for (self.parallel_work_bundles.values) |*bundle| { for (self.parallel_work_bundles.values) |*bundle| {
@ -211,7 +187,7 @@ pub const Schedule = struct {
self.arena.deinit(); self.arena.deinit();
} }
pub fn run(self: *Schedule, world: *World) anyerror!void { pub fn run(self: *Schedule, world: *Self) anyerror!void {
if (self.is_invalidated()) { if (self.is_invalidated()) {
const work = struct { const work = struct {
fn regenerate_graph(schedule: *Schedule) !void { fn regenerate_graph(schedule: *Schedule) !void {
@ -276,7 +252,7 @@ pub const Schedule = struct {
} }
} }
fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: dag.Node) !void { fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: ona.dag.Node) !void {
std.debug.assert(graph.mark_visited(node)); std.debug.assert(graph.mark_visited(node));
for (graph.edge_nodes(node).?) |edge| { for (graph.edge_nodes(node).?) |edge| {
@ -300,7 +276,7 @@ pub const Schedule = struct {
continue; continue;
} }
try schedule.parallel_work_bundles.push_grow(.{.allocator = coral.heap.allocator}); try schedule.parallel_work_bundles.push_grow(.{.allocator = ona.heap.allocator});
const bundle = schedule.parallel_work_bundles.get_ptr().?; const bundle = schedule.parallel_work_bundles.get_ptr().?;
@ -381,21 +357,21 @@ pub const Schedule = struct {
} }
pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule { pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule {
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator); var arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
errdefer arena.deinit(); errdefer arena.deinit();
const duped_label = try arena.allocator().dupeZ(u8, label); const duped_label = try arena.allocator().dupeZ(u8, label);
return .{ return .{
.graph = Graph.init(coral.heap.allocator), .graph = Graph.init(ona.heap.allocator),
.label = duped_label, .label = duped_label,
.arena = arena, .arena = arena,
.system_id_nodes = .{.allocator = coral.heap.allocator}, .system_id_nodes = .{.allocator = ona.heap.allocator},
.read_write_resource_id_nodes = .{.allocator = coral.heap.allocator}, .read_write_resource_id_nodes = .{.allocator = ona.heap.allocator},
.read_only_resource_id_nodes = .{.allocator = coral.heap.allocator}, .read_only_resource_id_nodes = .{.allocator = ona.heap.allocator},
.parallel_work_bundles = .{.allocator = coral.heap.allocator}, .parallel_work_bundles = .{.allocator = ona.heap.allocator},
.blocking_work = .{.allocator = coral.heap.allocator}, .blocking_work = .{.allocator = ona.heap.allocator},
}; };
} }
@ -413,7 +389,7 @@ pub const Schedule = struct {
return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty(); return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty();
} }
pub fn then(self: *Schedule, world: *World, info: *const Info, order: Order) std.mem.Allocator.Error!void { pub fn then(self: *Schedule, world: *Self, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
const nodes = lazily_create: { const nodes = lazily_create: {
const system_id = @intFromPtr(info); const system_id = @intFromPtr(info);
@ -428,7 +404,7 @@ pub const Schedule = struct {
const dependencies = init: { const dependencies = init: {
const total_run_orders = order.run_after.len + order.run_before.len; const total_run_orders = order.run_after.len + order.run_before.len;
const dependencies = try coral.heap.allocator.alloc(Dependency, total_run_orders); const dependencies = try ona.heap.allocator.alloc(Dependency, total_run_orders);
var dependencies_written = @as(usize, 0); var dependencies_written = @as(usize, 0);
for (order.run_after) |after_system| { for (order.run_after) |after_system| {
@ -452,17 +428,17 @@ pub const Schedule = struct {
break: init dependencies; break: init dependencies;
}; };
errdefer coral.heap.allocator.free(dependencies); errdefer ona.heap.allocator.free(dependencies);
const label = try coral.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label); const label = try ona.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
errdefer coral.heap.allocator.free(label); errdefer ona.heap.allocator.free(label);
const node = try self.graph.append(.{ const node = try self.graph.append(.{
.info = info, .info = info,
.label = label, .label = label,
.dependencies = dependencies, .dependencies = dependencies,
.resource_accesses = .{.allocator = coral.heap.allocator}, .resource_accesses = .{.allocator = ona.heap.allocator},
}); });
const system = self.graph.get_ptr(node).?; const system = self.graph.get_ptr(node).?;
@ -491,8 +467,144 @@ pub const Schedule = struct {
} }
}; };
pub const UnbindContext = struct { const Self = @This();
world: *World,
const StateTable = struct {
arena: std.heap.ArenaAllocator,
table: ona.map.Hashed(ona.TypeID, Entry, ona.map.enum_traits(ona.TypeID)),
const Entry = struct {
ptr: *anyopaque,
}; };
pub const max_parameters = 16; 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 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);
}

View File

@ -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());
},
}
}
}

View File

@ -1,19 +1,10 @@
const coral = @import("./coral.zig"); const ona = @import("./ona.zig");
const io = @import("./io.zig");
const scalars = @import("./scalars.zig");
const std = @import("std"); const std = @import("std");
pub const DecimalFormat = struct { pub const DecimalFormat = struct {
delimiter: []const coral.io.Byte, delimiter: []const u8 = "",
positive_prefix: enum {none, plus, space}, positive_prefix: enum {none, plus, space} = .none,
pub const default = DecimalFormat{
.delimiter = "",
.positive_prefix = .none,
};
pub fn parse(self: DecimalFormat, utf8: []const u8, comptime Decimal: type) ?Decimal { pub fn parse(self: DecimalFormat, utf8: []const u8, comptime Decimal: type) ?Decimal {
if (utf8.len == 0) { if (utf8.len == 0) {
@ -35,14 +26,14 @@ pub const DecimalFormat = struct {
switch (code) { switch (code) {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
const offset_code = scalars.sub(code, '0') orelse return null; const offset_code = ona.scalars.sub(code, '0') orelse return null;
result = scalars.mul(result, radix) orelse return null; result = ona.scalars.mul(result, radix) orelse return null;
result = scalars.add(result, offset_code) orelse return null; result = ona.scalars.add(result, offset_code) orelse return null;
}, },
else => { else => {
if (self.delimiter.len == 0 or !coral.are_equal(self.delimiter, utf8[index ..])) { if (self.delimiter.len == 0 or !std.mem.eql(u8, self.delimiter, utf8[index ..])) {
return null; return null;
} }
}, },
@ -115,9 +106,9 @@ pub const DecimalFormat = struct {
} }
} }
pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.Error!void { pub fn format(self: DecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void {
if (value == 0) { if (value == 0) {
return io.write_all(writer, switch (self.positive_prefix) { return ona.io.write_all(writer, switch (self.positive_prefix) {
.none => "0", .none => "0",
.plus => "+0", .plus => "+0",
.space => " 0", .space => " 0",
@ -151,12 +142,12 @@ pub const DecimalFormat = struct {
} }
} }
return io.write_all(writer, buffer[buffer_start ..]); return ona.io.write_all(writer, buffer[buffer_start ..]);
}, },
.Float => |float| { .Float => |float| {
if (value < 0) { if (value < 0) {
try io.write_all(writer, "-"); try ona.io.write_all(writer, "-");
} }
const Float = @TypeOf(value); const Float = @TypeOf(value);
@ -164,7 +155,7 @@ pub const DecimalFormat = struct {
const integer = @as(Int, @intFromFloat(value)); const integer = @as(Int, @intFromFloat(value));
try self.format(writer, integer); try self.format(writer, integer);
try io.write_all(writer, "."); try ona.io.write_all(writer, ".");
try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100))); try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
}, },
@ -173,6 +164,12 @@ pub const DecimalFormat = struct {
} }
}; };
test "decimal parsing" {
const format = DecimalFormat{};
try std.testing.expectEqual(format.parse("69", i64), 69);
}
pub const HexadecimalFormat = struct { pub const HexadecimalFormat = struct {
delimiter: []const u8 = "", delimiter: []const u8 = "",
positive_prefix: enum {none, plus, space} = .none, positive_prefix: enum {none, plus, space} = .none,
@ -184,7 +181,7 @@ pub const HexadecimalFormat = struct {
.casing = .lower, .casing = .lower,
}; };
pub fn format(self: HexadecimalFormat, writer: io.Writer, value: anytype) io.Error!void { pub fn format(self: HexadecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void {
// TODO: Implement. // TODO: Implement.
_ = self; _ = self;
_ = writer; _ = writer;

View File

@ -1,4 +1,4 @@
const coral = @import("coral"); const ona = @import("./ona.zig");
const std = @import("std"); const std = @import("std");
@ -7,9 +7,9 @@ pub fn Graph(comptime Payload: type) type {
node_count: usize = 0, node_count: usize = 0,
table: NodeTables, table: NodeTables,
const NodeTables = coral.stack.Parallel(struct { const NodeTables = ona.stack.Parallel(struct {
payload: Payload, payload: Payload,
edges: coral.stack.Sequential(Node), edges: ona.stack.Sequential(Node),
is_occupied: bool = true, is_occupied: bool = true,
is_visited: bool = false, is_visited: bool = false,
}); });

View File

@ -1,4 +0,0 @@
pub usingnamespace @cImport({
@cInclude("SDL2/SDL.h");
});

View File

@ -28,14 +28,14 @@ pub const Storage = struct {
pub const VTable = struct { pub const VTable = struct {
stat: *const fn (*anyopaque, []const u8) AccessError!Stat, stat: *const fn (*anyopaque, []const u8) AccessError!Stat,
read: *const fn (*anyopaque, []const u8, usize, []io.Byte) AccessError!usize, read: *const fn (*anyopaque, []const u8, usize, []u8) AccessError!usize,
}; };
pub fn read(self: Storage, path: []const u8, output: []io.Byte, offset: u64) AccessError!usize { pub fn read(self: Storage, path: []const u8, output: []u8, offset: u64) AccessError!usize {
return self.vtable.read(self.userdata, path, offset, output); return self.vtable.read(self.userdata, path, offset, output);
} }
pub fn read_all(self: Storage, path: []const u8, output: []io.Byte, options: ReadAllOptions) ReadAllError![]const io.Byte { pub fn read_all(self: Storage, path: []const u8, output: []u8, options: ReadAllOptions) ReadAllError![]const u8 {
const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output); const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output);
if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) { if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) {
@ -45,8 +45,8 @@ pub const Storage = struct {
return output[0 .. bytes_read]; return output[0 .. bytes_read];
} }
pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]io.Byte { pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]u8 {
const buffer = try allocator.alloc(io.Byte, @min((try self.stat(path)).size, options.limit)); const buffer = try allocator.alloc(u8, @min((try self.stat(path)).size, options.limit));
errdefer allocator.free(buffer); errdefer allocator.free(buffer);
@ -69,7 +69,7 @@ pub const Storage = struct {
} }
pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type { pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
var buffer = @as([@sizeOf(Type)]io.Byte, undefined); var buffer = @as([@sizeOf(Type)]u8, undefined);
if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) { if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
return null; return null;
@ -105,7 +105,7 @@ pub const bundle = init: {
return buffer; return buffer;
} }
fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) AccessError!usize { fn read(_: *anyopaque, path: []const u8, offset: usize, output: []u8) AccessError!usize {
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| { var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
return switch (open_error) { return switch (open_error) {
error.FileNotFound => error.FileNotFound, error.FileNotFound => error.FileNotFound,

View File

@ -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();
}
}

View File

@ -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};
}

View File

@ -1,13 +1,9 @@
const builtin = @import("builtin"); const builtin = @import("builtin");
const coral = @import("./coral.zig"); const ona = @import("./ona.zig");
const slices = @import("./slices.zig");
const std = @import("std"); const std = @import("std");
pub const Byte = u8;
pub const Error = error { pub const Error = error {
UnavailableResource, UnavailableResource,
}; };
@ -111,7 +107,7 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type
pub const NullWritable = struct { pub const NullWritable = struct {
written: usize = 0, written: usize = 0,
pub fn write(self: *NullWritable, buffer: []const Byte) Error!usize { pub fn write(self: *NullWritable, buffer: []const u8) Error!usize {
self.written += buffer.len; self.written += buffer.len;
return buffer.len; return buffer.len;
@ -122,12 +118,12 @@ pub const NullWritable = struct {
} }
}; };
pub const Reader = Generator(Error!usize, &.{[]Byte}); pub const Reader = Generator(Error!usize, &.{[]u8});
pub const Writer = Generator(Error!usize, &.{[]const Byte}); pub const Writer = Generator(Error!usize, &.{[]const u8});
pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte { pub fn alloc_read(input: ona.io.Reader, allocator: std.mem.Allocator) []u8 {
const buffer = coral.Stack(coral.Byte){.allocator = allocator}; const buffer = ona.stack.Sequential(u8){.allocator = allocator};
errdefer buffer.deinit(); errdefer buffer.deinit();
@ -139,7 +135,7 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.
pub const bits_per_byte = 8; pub const bits_per_byte = 8;
pub fn skip_n(input: Reader, distance: u64) Error!void { pub fn skip_n(input: Reader, distance: u64) Error!void {
var buffer = @as([512]coral.Byte, undefined); var buffer = @as([512]u8, undefined);
var remaining = distance; var remaining = distance;
while (remaining != 0) { while (remaining != 0) {
@ -154,7 +150,7 @@ pub fn skip_n(input: Reader, distance: u64) Error!void {
} }
pub fn stream_all(input: Reader, output: Writer) Error!usize { pub fn stream_all(input: Reader, output: Writer) Error!usize {
var buffer = @as([512]coral.Byte, undefined); var buffer = @as([512]u8, undefined);
var copied = @as(usize, 0); var copied = @as(usize, 0);
while (true) { while (true) {
@ -173,7 +169,7 @@ pub fn stream_all(input: Reader, output: Writer) Error!usize {
} }
pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize { pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize {
var buffer = @as([512]coral.Byte, undefined); var buffer = @as([512]u8, undefined);
var remaining = limit; var remaining = limit;
while (true) { while (true) {

View File

@ -1,18 +1,5 @@
const std = @import("std"); const std = @import("std");
pub fn Matrix(comptime n: usize, comptime Element: type) type {
return [n]@Vector(n, Element);
}
pub const ProjectionMatrix = Matrix(4, f32);
pub const Rect = struct {
left: f32,
top: f32,
right: f32,
bottom: f32,
};
pub fn cross(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child { pub fn cross(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child {
const multipled = v1 * v2; const multipled = v1 * v2;
const vector_info = @typeInfo(@TypeOf(v1)).Vector; const vector_info = @typeInfo(@TypeOf(v1)).Vector;
@ -62,15 +49,3 @@ pub fn normal(v: anytype) @TypeOf(v) {
return v; return v;
} }
pub fn orthographic_projection(near: f32, far: f32, viewport: Rect) Matrix(4, f32) {
const width = viewport.right - viewport.left;
const height = viewport.bottom - viewport.top;
return .{
.{2 / width, 0, 0, 0},
.{0, 2 / height, 0, 0},
.{0, 0, 1 / (far - near), 0},
.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
};
}

View File

@ -1,8 +1,4 @@
const coral = @import("./coral.zig"); const ona = @import("./ona.zig");
const hashes = @import("./hashes.zig");
const io = @import("./io.zig");
const std = @import("std"); const std = @import("std");
@ -250,13 +246,17 @@ pub fn enum_traits(comptime Enum: type) Traits(Enum) {
pub const string_traits = init: { pub const string_traits = init: {
const strings = struct { const strings = struct {
fn are_equal(a: []const u8, b: []const u8) bool {
return std.mem.eql(u8, a, b);
}
fn hash(value: []const u8) usize { fn hash(value: []const u8) usize {
return hashes.djb2(@typeInfo(usize).Int, value); return ona.hashes.djb2(@typeInfo(usize).Int, value);
} }
}; };
break: init Traits([]const u8){ break: init Traits([]const u8){
.are_equal = coral.io.are_equal, .are_equal = strings.are_equal,
.hash = strings.hash, .hash = strings.hash,
}; };
}; };

View File

@ -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);
}
};
}

View File

@ -1,17 +1,33 @@
pub const act = @import("./act.zig"); pub const ascii = @import("./ascii.zig");
const coral = @import("coral"); pub const asyncio = @import("./asyncio.zig");
const ext = @import("./ext.zig"); pub const dag = @import("./dag.zig");
const flow = @import("flow"); pub const hashes = @import("./hashes.zig");
pub const gfx = @import("./gfx.zig"); pub const heap = @import("./heap.zig");
pub const msg = @import("./msg.zig"); pub const files = @import("./files.zig");
pub const io = @import("./io.zig");
pub const map = @import("./map.zig");
pub const scalars = @import("./scalars.zig");
pub const slices = @import("./slices.zig");
pub const stack = @import("./stack.zig");
const std = @import("std"); const std = @import("std");
pub const utf8 = @import("./utf8.zig");
pub const World = @import("./World.zig");
pub const lina = @import("./lina.zig");
pub const App = struct { pub const App = struct {
events: *const Events, events: *const Events,
target_frame_time: f64, target_frame_time: f64,
@ -19,13 +35,13 @@ pub const App = struct {
is_running: bool, is_running: bool,
pub const Events = struct { pub const Events = struct {
load: flow.World.Event, load: World.Event,
pre_update: flow.World.Event, pre_update: World.Event,
update: flow.World.Event, update: World.Event,
post_update: flow.World.Event, post_update: World.Event,
render: flow.World.Event, render: World.Event,
finish: flow.World.Event, finish: World.Event,
exit: flow.World.Event, exit: World.Event,
}; };
pub fn quit(self: *App) void { pub fn quit(self: *App) void {
@ -33,18 +49,151 @@ pub const App = struct {
} }
}; };
pub const Flag = enum { fn Channel(comptime Message: type) type {
return struct {
buffers: [2]stack.Sequential(Message),
swap_index: u1 = 0,
ticks: u1 = 0,
const Self = @This();
fn cleanup(channel: Write(Self)) void {
channel.state.deinit();
}
pub fn deinit(self: *Self) void {
for (&self.buffers) |*buffer| {
buffer.deinit();
}
self.* = undefined;
}
fn swap(channel: Write(Self)) void {
channel.state.ticks = scalars.add(channel.state.ticks, 1) orelse 0;
if (channel.state.ticks == 0) {
channel.state.swap_index ^= 1;
channel.state.buffers[channel.state.swap_index].clear();
}
}
fn init(allocator: std.mem.Allocator) Self {
return .{
.buffers = .{
.{.allocator = allocator},
.{.allocator = allocator},
},
};
}
fn messages(self: Self) []const Message {
return self.buffers[self.swap_index ^ 1].values;
}
fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
try self.buffers[self.swap_index].push_grow(message);
}
};
}
pub fn Exclusive(comptime values: []const type) type {
comptime var qualifieds: [values.len]type = undefined;
for (&qualifieds, values) |*qualified, Value| {
qualified.* = switch (@typeInfo(Value)) {
.Optional => @Type(.{
.Optional = .{
.child = .{
.Pointer = .{
.is_allowzero = false,
.sentinel = null,
.address_space = .generic,
.is_volatile = false,
.alignment = @alignOf(Value),
.size = .One,
.child = Value,
.is_const = false,
},
},
},
}),
else => @Type(.{
.Pointer = .{
.is_allowzero = false,
.sentinel = null,
.address_space = .generic,
.is_volatile = false,
.alignment = @alignOf(Value),
.size = .One,
.child = Value,
.is_const = false,
},
}),
};
}
const States = std.meta.Tuple(&qualifieds);
return struct {
states: States,
world: *World,
const Self = @This();
pub const Param = struct {
states: States,
world: *World,
};
pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
var param = Param{
.world = context.world,
.states = undefined,
};
inline for (&param.states, values) |*state, Value| {
const has_state = try context.register_writable_state_access(Value);
state.* = switch (@typeInfo(Value)) {
.Optional => has_state,
else => has_state orelse {
@panic(std.fmt.comptimePrint("attempt to use exclusive {s} that has not yet been set", .{
@typeName(Value),
}));
},
};
}
return param;
}
pub fn init(param: *Param) Self {
return .{
.states = param.states,
.world = param.world,
};
}
pub const thread_restriction = .main;
};
}
pub const LaunchFlag = enum {
dump_shader_translations, dump_shader_translations,
var launch_args = [_]?[]const u8{null} ** std.enums.values(Flag).len; var args = [_]?[]const u8{null} ** std.enums.values(LaunchFlag).len;
const values = std.enums.values(Flag); const values = std.enums.values(LaunchFlag);
}; };
pub const Options = struct { pub const Options = struct {
tick_rate: u64, tick_rate: u64,
execution: Execution, execution: Execution,
middlewares: []const *const Setup = default_middlewares, middleware: []const *const Setup,
pub const Execution = union (enum) { pub const Execution = union (enum) {
single_threaded, single_threaded,
@ -52,29 +201,498 @@ pub const Options = struct {
}; };
}; };
pub const Read = flow.Read; pub fn Params(comptime Value: type) type {
if (!@hasDecl(Value, "Param")) {
pub const Setup = fn (*flow.World, App.Events) anyerror!void; @compileError("System parameters must have a Params type declaration");
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 fn start(comptime setup: Setup, options: Options) fn () anyerror!void { 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 Pool(comptime Value: type) type {
return struct {
entries: stack.Sequential(Entry),
first_free_index: usize = 0,
const Entry = union (enum) {
free_index: usize,
occupied: Value,
};
pub const Values = struct {
cursor: usize = 0,
pool: *const Self,
pub fn next(self: *Values) ?*Value {
while (self.cursor < self.pool.entries.len()) {
defer self.cursor += 1;
switch (self.pool.entries.values[self.cursor]) {
.free_index => {
continue;
},
.occupied => |*occupied| {
return occupied;
},
}
}
return null;
}
};
const Self = @This();
pub fn deinit(self: *Self) void {
self.entries.deinit();
self.* = undefined;
}
pub fn get(self: *Self, key: usize) ?*Value {
if (key >= self.entries.len()) {
return null;
}
return switch (self.entries.values[key]) {
.free_index => null,
.occupied => |*occupied| occupied,
};
}
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.entries = .{.allocator = allocator},
};
}
pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
const entries_count = self.entries.len();
if (self.first_free_index == entries_count) {
try self.entries.push_grow(.{.occupied = value});
self.first_free_index += 1;
return entries_count;
}
const insersion_index = self.first_free_index;
self.first_free_index = self.entries.values[self.first_free_index].free_index;
self.entries.values[insersion_index] = .{.occupied = value};
return insersion_index;
}
pub fn remove(self: *Self, key: usize) ?Value {
if (key >= self.entries.len()) {
return null;
}
switch (self.entries.values[key]) {
.free_index => {
return null;
},
.occupied => |occupied| {
self.entries.values[key] = .{.free_index = self.first_free_index};
self.first_free_index = key;
return occupied;
},
}
}
pub fn values(self: *const Self) Values {
return .{
.pool = self,
};
}
};
}
test "pooling" {
var pool = Pool(i32).init(std.testing.allocator);
defer pool.deinit();
try std.testing.expectEqual(0, pool.first_free_index);
try std.testing.expectEqual(null, pool.get(0));
try std.testing.expectEqual(null, pool.remove(0));
try std.testing.expectEqual(0, try pool.insert(69));
try std.testing.expectEqual(1, pool.first_free_index);
try std.testing.expectEqual(1, try pool.insert(420));
try std.testing.expectEqual(69, pool.get(0).?.*);
try std.testing.expectEqual(420, pool.get(1).?.*);
try std.testing.expectEqual(69, pool.remove(0).?);
try std.testing.expectEqual(0, pool.first_free_index);
try std.testing.expectEqual(null, pool.get(0));
try std.testing.expectEqual(null, pool.remove(0));
try std.testing.expectEqual(0, try pool.insert(36));
try std.testing.expectEqual(36, pool.get(0).?.*);
}
pub fn Read(comptime Value: type) type {
return Shared(Value, .{
.thread_restriction = thread_restriction(Value),
.read_only = true,
});
}
pub fn Receive(comptime Message: type) type {
const TypedChannel = Channel(Message);
return struct {
channel: *const TypedChannel,
const Self = @This();
pub const Param = struct {
channel: *const TypedChannel,
};
pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
return .{
.channel = (try context.register_readable_state_access(TypedChannel)) orelse set: {
try context.world.set_state(TypedChannel.init(heap.allocator));
break: set (try context.register_readable_state_access(TypedChannel)).?;
},
};
}
pub fn init(param: *Param) Self {
return .{
.channel = param.channel,
};
}
pub fn messages(self: Self) []const Message {
return self.channel.messages();
}
};
}
pub fn Send(comptime Message: type) type {
const TypedChannel = Channel(Message);
return struct {
channel: *TypedChannel,
const Self = @This();
pub const Param = struct {
channel: *TypedChannel,
};
pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
return .{
.channel = (try context.register_writable_state_access(TypedChannel)) orelse set: {
try context.world.set_state(TypedChannel.init(heap.allocator));
const app = context.world.get_state(App) orelse {
@panic("Send system parameters depend on a " ++ @typeName(App) ++ " state to work");
};
try context.world.on_event(app.events.post_update, system_fn(TypedChannel.swap), .{
.label = "swap channel of " ++ @typeName(Message),
});
try context.world.on_event(app.events.exit, system_fn(TypedChannel.cleanup), .{
.label = "clean up channel of " ++ @typeName(Message),
});
break: set (try context.register_writable_state_access(TypedChannel)).?;
},
};
}
pub fn init(param: *Param) Self {
return .{
.channel = param.channel,
};
}
pub fn push(self: Self, message: Message) std.mem.Allocator.Error!void {
try self.channel.push(message);
}
};
}
pub const Setup = fn (*World, App.Events) anyerror!void;
pub const ShareInfo = struct {
thread_restriction: ThreadRestriction,
read_only: bool,
};
pub fn Shared(comptime Value: type, comptime info: ShareInfo) type {
const value_info = @typeInfo(Value);
const Qualified = switch (value_info) {
.Optional => @Type(.{
.Optional = .{
.child = .{
.Pointer = .{
.is_allowzero = false,
.sentinel = null,
.address_space = .generic,
.is_volatile = false,
.alignment = @alignOf(Value),
.size = .One,
.child = Value,
.is_const = info.read_only,
},
},
},
}),
else => @Type(.{
.Pointer = .{
.is_allowzero = false,
.sentinel = null,
.address_space = .generic,
.is_volatile = false,
.alignment = @alignOf(Value),
.size = .One,
.child = Value,
.is_const = info.read_only,
},
}),
};
return struct {
state: Qualified,
const Self = @This();
pub const Param = struct {
state: Qualified,
};
pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
const thread_restriction_name = switch (info.thread_restriction) {
.main => "main thread-restricted ",
.none => ""
};
const state = switch (info.read_only) {
true => (try context.register_readable_state_access(Value)),
false => (try context.register_writable_state_access(Value)),
};
return .{
.state = switch (value_info) {
.Optional => state,
else => state orelse {
@panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{
thread_restriction_name,
if (info.read_only) "read-only" else "read-write",
@typeName(Value),
}));
},
},
};
}
pub fn init(param: *Param) Self {
return .{
.state = param.state,
};
}
pub const thread_restriction = info.thread_restriction;
};
}
pub const SystemInfo = struct {
execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void,
parameters: [max_parameters]*const Parameter = undefined,
parameter_count: u4 = 0,
thread_restriction: ThreadRestriction = .none,
pub const Parameter = struct {
thread_restriction: ThreadRestriction,
init: *const fn (*anyopaque, *anyopaque) void,
bind: *const fn (std.mem.Allocator, World.BindContext) std.mem.Allocator.Error!*anyopaque,
unbind: *const fn (std.mem.Allocator, *anyopaque, World.UnbindContext) void,
};
pub const max_parameters = 16;
pub fn used_parameters(self: *const SystemInfo) []const *const Parameter {
return self.parameters[0 .. self.parameter_count];
}
};
pub const SystemOrder = struct {
label: []const u8 = "",
run_after: []const *const SystemInfo = &.{},
run_before: []const *const SystemInfo = &.{},
};
pub const ThreadRestriction = enum {
none,
main,
};
pub const TypeID = enum (usize) { _ };
pub fn Write(comptime Value: type) type {
return Shared(Value, .{
.thread_restriction = thread_restriction(Value),
.read_only = false,
});
}
pub fn launch_arg(flag: LaunchFlag) ?[]const u8 {
return LaunchFlag.args[@intFromEnum(flag)];
}
fn parameter_type(comptime Value: type) *const SystemInfo.Parameter {
const ValueParams = Params(Value);
if (@sizeOf(Value) == 0) {
@compileError("System parameters must have a non-zero size");
}
const parameters = struct {
fn bind(allocator: std.mem.Allocator, context: World.BindContext) std.mem.Allocator.Error!*anyopaque {
const value_name = @typeName(Value);
if (!@hasDecl(Value, "bind")) {
@compileError(
"a `bind` declaration on " ++
value_name ++
" is requied for parameter types with a `Param` declaration");
}
const bind_type = @typeInfo(@TypeOf(Value.bind));
if (bind_type != .Fn) {
@compileError("`bind` declaration on " ++ value_name ++ " must be a fn");
}
if (bind_type.Fn.params.len != 1 or bind_type.Fn.params[0].type.? != World.BindContext) {
@compileError(
"`bind` fn on " ++
value_name ++
" must accept " ++
@typeName(World.BindContext) ++
" as it's one and only argument");
}
const params_node = try allocator.create(ValueParams.Node);
params_node.* = .{
.param = switch (bind_type.Fn.return_type.?) {
Value.Param => Value.bind(context),
std.mem.Allocator.Error!Value.Param => try Value.bind(context),
else => @compileError(std.fmt.comptimePrint("`bind` fn on {s} must return {s} or {s}", .{
@typeName(Value),
@typeName(Value.Param),
@typeName(std.mem.Allocator.Error!Value.Param)
})),
},
};
if (context.world.get_state(ValueParams)) |value_params| {
if (value_params.has_tail) |tail| {
tail.has_next = params_node;
}
params_node.has_prev = value_params.has_tail;
value_params.has_tail = params_node;
} else {
try context.world.set_state(ValueParams{
.has_head = params_node,
.has_tail = params_node,
});
}
return @ptrCast(params_node);
}
fn init(argument: *anyopaque, erased_node: *anyopaque) void {
const value_name = @typeName(Value);
if (!@hasDecl(Value, "init")) {
@compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types");
}
const init_type = @typeInfo(@TypeOf(Value.init));
if (init_type != .Fn) {
@compileError("`init` declaration on " ++ value_name ++ " must be a fn");
}
if (init_type.Fn.return_type.? != Value) {
@compileError("`init` fn on " ++ value_name ++ " must return a " ++ value_name);
}
const concrete_argument = @as(*Value, @ptrCast(@alignCast(argument)));
if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.Param) {
@compileError("`init` fn on " ++ value_name ++ " must accept a " ++ @typeName(*Value.Param));
}
concrete_argument.* = Value.init(&@as(*ValueParams.Node, @ptrCast(@alignCast(erased_node))).param);
}
fn unbind(allocator: std.mem.Allocator, erased_node: *anyopaque, context: World.UnbindContext) void {
if (@hasDecl(Value, "unbind")) {
const node = @as(*ValueParams.Node, @ptrCast(@alignCast(erased_node)));
if (node.has_prev) |prev| {
prev.has_next = node.has_next;
}
if (node.has_next) |next| {
next.has_prev = node.has_prev;
}
if (context.world.get_state(ValueParams)) |params| {
if (node.has_prev == null) {
params.has_head = node.has_next;
}
if (node.has_next == null) {
params.has_tail = node.has_prev;
}
}
Value.unbind(&node.param, context);
allocator.destroy(node);
}
}
};
return comptime &.{
.thread_restriction = if (@hasDecl(Value, "thread_restriction")) Value.thread_restriction else .none,
.init = parameters.init,
.bind = parameters.bind,
.unbind = parameters.unbind,
};
}
pub fn start(comptime setup: Setup, comptime options: Options) fn () anyerror!void {
const Start = struct { const Start = struct {
fn main() anyerror!void { fn main() anyerror!void {
defer { defer {
coral.heap.trace_leaks(); heap.trace_leaks();
ext.SDL_Quit();
} }
parse_args: for (std.os.argv[1 ..]) |arg| { parse_args: for (std.os.argv[1 ..]) |arg| {
@ -82,14 +700,14 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len; const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len;
const arg_name = arg_span[0 .. arg_split_index]; const arg_name = arg_span[0 .. arg_split_index];
for (Flag.values) |value| { for (LaunchFlag.values) |value| {
const name = @tagName(value); const name = @tagName(value);
if (!std.mem.eql(u8, arg_name, name)) { if (!std.mem.eql(u8, arg_name, name)) {
continue; continue;
} }
Flag.launch_args[@intFromEnum(value)] = LaunchFlag.args[@intFromEnum(value)] =
if (arg_split_index == arg_span.len) if (arg_split_index == arg_span.len)
name name
else else
@ -100,7 +718,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
} }
var world = try switch (options.execution) { var world = try switch (options.execution) {
.single_threaded => flow.World.init(0), .single_threaded => World.init(0),
.thread_share => |thread_share| init: { .thread_share => |thread_share| init: {
const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| { const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
@ -111,7 +729,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
}); });
}, 0, std.math.maxInt(u32)))); }, 0, std.math.maxInt(u32))));
break: init flow.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0); break: init World.init(scalars.fractional(cpu_count, thread_share) orelse 0);
}, },
}; };
@ -134,7 +752,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
.is_running = true, .is_running = true,
}); });
for (options.middlewares) |setup_middleware| { inline for (options.middleware) |setup_middleware| {
try setup_middleware(&world, events); try setup_middleware(&world, events);
} }
@ -172,4 +790,81 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
return Start.main; return Start.main;
} }
pub const system_fn = flow.system_fn; pub fn system_fn(comptime call: anytype) *const SystemInfo {
const Call = @TypeOf(call);
const system_info = comptime generate: {
switch (@typeInfo(Call)) {
.Fn => |call_fn| {
if (call_fn.params.len > SystemInfo.max_parameters) {
@compileError("number of parameters to `call` cannot be more than 16");
}
const systems = struct {
fn run(parameters: []const *const SystemInfo.Parameter, data: *const [SystemInfo.max_parameters]*anyopaque) anyerror!void {
var call_args = @as(std.meta.ArgsTuple(Call), undefined);
inline for (parameters, &call_args, data[0 .. parameters.len]) |parameter, *call_arg, state| {
parameter.init(call_arg, state);
}
switch (@typeInfo(call_fn.return_type.?)) {
.Void => @call(.auto, call, call_args),
.ErrorUnion => try @call(.auto, call, call_args),
else => @compileError("number of parameters to `call` must return void or !void"),
}
}
};
var parameters = @as([SystemInfo.max_parameters]*const SystemInfo.Parameter, undefined);
var system_thread_restriction = ThreadRestriction.none;
for (0 .. call_fn.params.len) |index| {
const CallParam = call_fn.params[index].type.?;
const parameter = parameter_type(CallParam);
if (parameter.thread_restriction != .none) {
if (system_thread_restriction != .none and system_thread_restriction != parameter.thread_restriction) {
@compileError("a system may not have conflicting thread restrictions");
}
system_thread_restriction = parameter.thread_restriction;
}
parameters[index] = parameter;
}
break: generate &.{
.parameters = parameters,
.parameter_count = call_fn.params.len,
.execute = systems.run,
.thread_restriction = system_thread_restriction,
};
},
else => @compileError("parameter `call` must be a function"),
}
};
return system_info;
}
pub fn type_id(comptime T: type) TypeID {
const TypeHandle = struct {
comptime {
_ = T;
}
var byte: u8 = 0;
};
return @enumFromInt(@intFromPtr(&TypeHandle.byte));
}
pub fn thread_restriction(comptime State: type) ThreadRestriction {
if (@hasDecl(State, "thread_restriction")) {
return State.thread_restriction;
}
return .none;
}

View File

@ -1,5 +1,3 @@
const io = @import("./io.zig");
const std = @import("std"); const std = @import("std");
pub fn ElementPtr(comptime Slice: type) type { pub fn ElementPtr(comptime Slice: type) type {
@ -25,7 +23,7 @@ pub fn Parallel(comptime Type: type) type {
return struct { return struct {
len: usize = 0, len: usize = 0,
ptrs: [fields.len][*]align (alignment) io.Byte = undefined, ptrs: [fields.len][*]align (alignment) u8 = undefined,
pub fn Element(comptime field: Field) type { pub fn Element(comptime field: Field) type {
return fields[@intFromEnum(field)].type; return fields[@intFromEnum(field)].type;
@ -118,7 +116,7 @@ pub fn get_ptr(slice: anytype, index: usize) ?ElementPtr(@TypeOf(slice)) {
pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) { pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) {
const alignment = @alignOf(Element); const alignment = @alignOf(Element);
const Slices = Parallel(Element); const Slices = Parallel(Element);
var buffers = @as([std.enums.values(Slices.Field).len][]align (alignment) io.Byte, undefined); var buffers = @as([std.enums.values(Slices.Field).len][]align (alignment) u8, undefined);
var buffers_allocated = @as(usize, 0); var buffers_allocated = @as(usize, 0);
var allocated = Slices{.len = n}; var allocated = Slices{.len = n};
@ -131,7 +129,7 @@ pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: u
const fields = @typeInfo(Element).Struct.fields; const fields = @typeInfo(Element).Struct.fields;
inline for (0 .. fields.len) |i| { inline for (0 .. fields.len) |i| {
buffers[i] = try allocator.alignedAlloc(io.Byte, alignment, @sizeOf(fields[i].type) * n); buffers[i] = try allocator.alignedAlloc(u8, alignment, @sizeOf(fields[i].type) * n);
buffers_allocated += 1; buffers_allocated += 1;
allocated.ptrs[i] = buffers[i].ptr; allocated.ptrs[i] = buffers[i].ptr;
} }

View File

@ -1,14 +1,10 @@
const io = @import("./io.zig"); const ona = @import("./ona.zig");
const scalars = @import("./scalars.zig");
const slices = @import("./slices.zig");
const std = @import("std"); const std = @import("std");
pub fn Sequential(comptime Value: type) type { pub fn Sequential(comptime Value: type) type {
return struct { return struct {
allocator: std.mem.Allocator, allocator: std.mem.Allocator = ona.heap.allocator,
values: []Value = &.{}, values: []Value = &.{},
cap: usize = 0, cap: usize = 0,
@ -82,7 +78,7 @@ pub fn Sequential(comptime Value: type) type {
} }
pub fn pop_many(self: *Self, n: usize) bool { pub fn pop_many(self: *Self, n: usize) bool {
const new_length = scalars.sub(self.values.len, n) orelse { const new_length = ona.scalars.sub(self.values.len, n) orelse {
return false; return false;
}; };
@ -190,12 +186,12 @@ pub fn Sequential(comptime Value: type) type {
} }
pub const writer = switch (Value) { pub const writer = switch (Value) {
io.Byte => struct { u8 => struct {
fn writer(self: *Self) io.Writer { fn writer(self: *Self) ona.io.Writer {
return io.Writer.bind(Self, self, write); return ona.io.Writer.bind(Self, self, write);
} }
fn write(self: *Self, buffer: []const io.Byte) io.Error!usize { fn write(self: *Self, buffer: []const u8) ona.io.Error!usize {
self.push_all(buffer) catch return error.UnavailableResource; self.push_all(buffer) catch return error.UnavailableResource;
return buffer.len; return buffer.len;
@ -208,7 +204,7 @@ pub fn Sequential(comptime Value: type) type {
} }
pub fn Parallel(comptime Value: type) type { pub fn Parallel(comptime Value: type) type {
const Slices = slices.Parallel(Value); const Slices = ona.slices.Parallel(Value);
const alignment = @alignOf(Value); const alignment = @alignOf(Value);
return struct { return struct {
@ -229,7 +225,7 @@ pub fn Parallel(comptime Value: type) type {
capacity_slice.len = self.cap; capacity_slice.len = self.cap;
slices.parallel_free(Value, self.allocator, capacity_slice); ona.slices.parallel_free(Value, self.allocator, capacity_slice);
self.* = undefined; self.* = undefined;
} }
@ -244,11 +240,11 @@ pub fn Parallel(comptime Value: type) type {
pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void { pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
const grown_capacity = self.cap + additional; const grown_capacity = self.cap + additional;
const buffer = try slices.parallel_alloc(Value, self.allocator, grown_capacity); const buffer = try ona.slices.parallel_alloc(Value, self.allocator, grown_capacity);
if (self.cap != 0) { if (self.cap != 0) {
slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values); ona.slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values);
slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?); ona.slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?);
} }
self.cap = grown_capacity; self.cap = grown_capacity;

View File

@ -1,8 +1,4 @@
const ascii = @import("./ascii.zig"); const ona = @import("./ona.zig");
const coral = @import("./coral.zig");
const io = @import("./io.zig");
const std = @import("std"); const std = @import("std");
@ -12,26 +8,26 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
errdefer allocator.free(buffer); errdefer allocator.free(buffer);
// TODO: This is dumb. // TODO: This is messy.
return @constCast(print_formatted(buffer, format, args) catch unreachable); return @constCast(print_formatted(buffer, format, args) catch unreachable);
} }
pub fn count_formatted(comptime format: []const u8, args: anytype) usize { pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
var count = io.NullWritable{}; var count = ona.io.NullWritable{};
write_formatted(count.writer(), format, args) catch unreachable; write_formatted(count.writer(), format, args) catch unreachable;
return count.written; return count.written;
} }
pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]const u8 { pub fn print_formatted(buffer: [:0]u8, comptime format: []const u8, args: anytype) ona.io.Error![:0]const u8 {
const Seekable = struct { const Seekable = struct {
buffer: []coral.io.Byte, buffer: []u8,
cursor: usize, cursor: usize,
const Self = @This(); const Self = @This();
fn write(self: *Self, input: []const coral.io.Byte) io.Error!usize { fn write(self: *Self, input: []const u8) ona.io.Error!usize {
const range = @min(input.len, self.buffer.len - self.cursor); const range = @min(input.len, self.buffer.len - self.cursor);
const tail = self.cursor + range; const tail = self.cursor + range;
@ -54,7 +50,7 @@ pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, a
.cursor = 0, .cursor = 0,
}; };
try write_formatted(coral.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args); try write_formatted(ona.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args);
if (buffer.len < len) { if (buffer.len < len) {
buffer[len] = 0; buffer[len] = 0;
@ -63,7 +59,7 @@ pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, a
return buffer[0 .. len:0]; return buffer[0 .. len:0];
} }
pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.Error!void { pub fn write_formatted(writer: ona.io.Writer, comptime format: []const u8, args: anytype) ona.io.Error!void {
switch (@typeInfo(@TypeOf(args))) { switch (@typeInfo(@TypeOf(args))) {
.Struct => |arguments_struct| { .Struct => |arguments_struct| {
comptime var arg_index = 0; comptime var arg_index = 0;
@ -80,7 +76,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
switch (format[tail]) { switch (format[tail]) {
'{' => { '{' => {
try io.print(writer, format[head .. (tail - 1)]); try ona.io.print(writer, format[head .. (tail - 1)]);
tail += 1; tail += 1;
head = tail; head = tail;
@ -91,7 +87,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
@compileError("all format specifiers must be named when using a named struct"); @compileError("all format specifiers must be named when using a named struct");
} }
try io.print(writer, args[arg_index]); try ona.io.print(writer, args[arg_index]);
arg_index += 1; arg_index += 1;
tail += 1; tail += 1;
@ -103,7 +99,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
@compileError("format specifiers cannot be named when using a tuple struct"); @compileError("format specifiers cannot be named when using a tuple struct");
} }
try io.write_all(writer, format[head .. (tail - 1)]); try ona.io.write_all(writer, format[head .. (tail - 1)]);
head = tail; head = tail;
tail += 1; tail += 1;
@ -129,25 +125,27 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
} }
} }
try io.write_all(writer, format[head .. ]); try ona.io.write_all(writer, format[head .. ]);
}, },
else => @compileError("`arguments` must be a struct type"), else => @compileError("`arguments` must be a struct type"),
} }
} }
noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void { noinline fn print_formatted_value(writer: ona.io.Writer, value: anytype) ona.io.Error!void {
const Value = @TypeOf(value); const Value = @TypeOf(value);
const hexadecimal_format = ona.ascii.HexadecimalFormat{};
const decimal_format = ona.ascii.DecimalFormat{};
return switch (@typeInfo(Value)) { return switch (@typeInfo(Value)) {
.Int => ascii.DecimalFormat.default.format(writer, value), .Int => decimal_format.format(writer, value),
.Float => ascii.DecimalFormat.default.format(writer, value), .Float => decimal_format.format(writer, value),
.Enum => io.print(writer, @tagName(value)), .Enum => ona.io.write_all(writer, @tagName(value)),
.Pointer => |pointer| switch (pointer.size) { .Pointer => |pointer| switch (pointer.size) {
.Many, .C => ascii.HexadecimalFormat.default.format(writer, @intFromPtr(value)), .Many, .C => hexadecimal_format.format(writer, @intFromPtr(value)),
.One => if (pointer.child == []const u8) io.write_all(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)), .One => if (pointer.child == []const u8) ona.io.write_all(writer, *value) else hexadecimal_format.format(writer, @intFromPtr(value)),
.Slice => if (pointer.child == u8) io.write_all(writer, value) else @compileError(unformattableMessage(Value)), .Slice => if (pointer.child == u8) ona.io.write_all(writer, value) else @compileError(unformattableMessage(Value)),
}, },
else => @compileError(unformattableMessage(Value)), else => @compileError(unformattableMessage(Value)),