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
*.spv filter=lfs diff=lfs merge=lfs -text

4
.gitignore vendored
View File

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

4
.vscode/launch.json vendored
View File

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

384
build.zig
View File

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

View File

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

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

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

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

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");
pub const io = @import("./io.zig");
pub const lina = @import("./lina.zig");
pub const map = @import("./map.zig");
pub const scalars = @import("./scalars.zig");
pub const slices = @import("./slices.zig");
pub const stack = @import("./stack.zig");
const rendering = @import("./rendering.zig");
const std = @import("std");
pub const utf8 = @import("./utf8.zig");
pub const Assets = struct {
window: *ext.SDL_Window,
texture_formats: ona.stack.Sequential(TextureFormat),
frame_rendered: std.Thread.ResetEvent = .{},
pending_work: WorkQueue = .{},
has_worker_thread: ?std.Thread = null,
pub fn Pool(comptime Value: type) type {
return struct {
entries: stack.Sequential(Entry),
first_free_index: usize = 0,
pub const LoadError = std.mem.Allocator.Error;
const Entry = union (enum) {
free_index: usize,
occupied: Value,
pub const LoadFileError = LoadError || ona.files.ReadAllError || error {
FormatUnsupported,
};
pub const TextureFormat = struct {
extension: []const u8,
load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc,
};
pub const WorkQueue = ona.asyncio.BlockingQueue(1024, union (enum) {
load_effect: LoadEffectWork,
load_texture: LoadTextureWork,
render_frame: RenderFrameWork,
shutdown,
unload_effect: UnloadEffectWork,
unload_texture: UnloadTextureWork,
const LoadEffectWork = struct {
desc: Effect.Desc,
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect),
};
pub const Values = struct {
cursor: usize = 0,
pool: *const Self,
const LoadTextureWork = struct {
desc: Texture.Desc,
loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture),
};
pub fn next(self: *Values) ?*Value {
while (self.cursor < self.pool.entries.len()) {
defer self.cursor += 1;
const RenderFrameWork = struct {
clear_color: Color,
width: u16,
height: u16,
finished: *std.Thread.ResetEvent,
has_command_params: ?*ona.Params(Commands).Node,
};
switch (self.pool.entries.values[self.cursor]) {
.free_index => {
continue;
},
const UnloadEffectWork = struct {
handle: Effect,
};
.occupied => |*occupied| {
return occupied;
},
}
}
const UnloadTextureWork = struct {
handle: Texture,
};
});
return null;
fn deinit(self: *Assets) void {
self.pending_work.enqueue(.shutdown);
if (self.has_worker_thread) |worker_thread| {
worker_thread.join();
}
self.texture_formats.deinit();
self.* = undefined;
}
fn init() !Assets {
const window = create: {
const position = ext.SDL_WINDOWPOS_CENTERED;
const flags = ext.SDL_WINDOW_OPENGL;
const width = 640;
const height = 480;
break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
return error.Unsupported;
};
};
errdefer {
ext.SDL_DestroyWindow(window);
}
return .{
.texture_formats = .{.allocator = ona.heap.allocator},
.window = window,
};
}
pub fn load_effect_file(self: *Assets, storage: ona.files.Storage, path: []const u8) LoadFileError!Effect {
if (!std.mem.endsWith(u8, path, ".spv")) {
return error.FormatUnsupported;
}
const fragment_file_stat = try storage.stat(path);
const fragment_spirv_ops = try ona.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
defer {
ona.heap.allocator.free(fragment_spirv_ops);
}
const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{});
std.debug.assert(bytes_read.len == fragment_file_stat.size);
var loaded = ona.asyncio.Future(std.mem.Allocator.Error!Effect){};
self.pending_work.enqueue(.{
.load_effect = .{
.desc = .{
.fragment_spirv_ops = fragment_spirv_ops,
},
.loaded = &loaded,
},
});
return loaded.get().*;
}
pub fn load_texture(self: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture {
var loaded = ona.asyncio.Future(std.mem.Allocator.Error!Texture){};
self.pending_work.enqueue(.{
.load_texture = .{
.desc = desc,
.loaded = &loaded,
},
});
return loaded.get().*;
}
pub fn load_texture_file(self: *Assets, storage: ona.files.Storage, path: []const u8) LoadFileError!Texture {
var arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
defer {
arena.deinit();
}
for (self.texture_formats.values) |format| {
if (!std.mem.endsWith(u8, path, format.extension)) {
continue;
}
};
const Self = @This();
return self.load_texture(try format.load_file(&arena, storage, path));
}
pub fn deinit(self: *Self) void {
self.entries.deinit();
return error.FormatUnsupported;
}
pub const thread_restriction = .main;
};
pub const Color = @Vector(4, f32);
pub const Commands = struct {
pending: *List,
const Command = union (enum) {
draw_texture: DrawTextureCommand,
set_effect: SetEffectCommand,
set_target: SetTargetCommand,
};
pub const DrawTextureCommand = struct {
texture: Texture,
transform: Transform2D,
};
pub const SetEffectCommand = struct {
effect: Effect,
properties: []const u8,
};
pub const SetTargetCommand = struct {
texture: Texture,
clear_color: ?Color,
clear_depth: ?f32,
clear_stencil: ?u8,
};
pub const List = struct {
arena: std.heap.ArenaAllocator,
stack: ona.stack.Sequential(Command),
fn clear(self: *List) void {
self.stack.clear();
if (!self.arena.reset(.retain_capacity)) {
std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
}
}
fn deinit(self: *List) void {
self.arena.deinit();
self.stack.deinit();
self.* = undefined;
}
pub fn get(self: *Self, key: usize) ?*Value {
return switch (self.entries.values[key]) {
.free_index => null,
.occupied => |*occupied| occupied,
};
}
pub fn init(allocator: std.mem.Allocator) Self {
fn init(allocator: std.mem.Allocator) List {
return .{
.entries = .{.allocator = allocator},
};
}
pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
const entries_count = self.entries.len();
if (self.first_free_index == entries_count) {
try self.entries.push_grow(.{.occupied = value});
self.first_free_index += 1;
return entries_count;
}
const insersion_index = self.first_free_index;
self.first_free_index = self.entries.values[self.first_free_index].free_index;
self.entries.values[insersion_index] = .{.occupied = value};
return insersion_index;
}
pub fn remove(self: *Self, key: usize) ?Value {
if (key >= self.entries.len()) {
return null;
}
switch (self.entries.values[key]) {
.free_index => {
return null;
},
.occupied => |occupied| {
self.entries.values[key] = .{.free_index = self.first_free_index};
self.first_free_index = key;
return occupied;
},
}
}
pub fn values(self: *const Self) Values {
return .{
.pool = self,
.arena = std.heap.ArenaAllocator.init(allocator),
.stack = .{.allocator = allocator},
};
}
};
pub const Param = struct {
swap_lists: [2]List,
swap_state: u1 = 0,
fn deinit(self: *Param) void {
for (&self.swap_lists) |*list| {
list.deinit();
}
self.* = undefined;
}
fn pending_list(self: *Param) *List {
return &self.swap_lists[self.swap_state];
}
fn rotate(self: *Param) void {
const swapped_state = self.swap_state ^ 1;
self.swap_lists[swapped_state].clear();
self.swap_state = swapped_state;
}
pub fn submitted_commands(self: Param) []const Command {
return self.swap_lists[self.swap_state ^ 1].stack.values;
}
};
pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param {
return .{
.swap_lists = .{
List.init(ona.heap.allocator),
List.init(ona.heap.allocator),
},
};
}
pub fn init(param: *Param) Commands {
return .{
.pending = param.pending_list(),
};
}
pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void {
try self.pending.stack.push_grow(.{.draw_texture = command});
}
pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void {
try self.pending.stack.push_grow(.{
.set_effect = .{
.properties = try self.pending.arena.allocator().dupe(u8, command.properties),
.effect = command.effect,
},
});
}
pub fn unbind(param: *Param, _: ona.World.UnbindContext) void {
param.deinit();
}
pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void {
try self.pending.stack.push_grow(.{.set_target = command});
}
};
pub const Display = struct {
width: u16 = 1280,
height: u16 = 720,
clear_color: Color = colors.black,
};
pub const Effect = enum (u32) {
default,
_,
pub const Desc = struct {
fragment_spirv_ops: []const u32,
};
};
pub const Rect = struct {
left: f32,
top: f32,
right: f32,
bottom: f32,
};
pub const Texture = enum (u32) {
default,
backbuffer,
_,
pub const Desc = struct {
format: Format,
access: Access,
pub const Access = union (enum) {
static: StaticAccess,
render: RenderAccess,
};
pub const StaticAccess = struct {
width: u16,
data: []const u8,
};
pub const RenderAccess = struct {
width: u16,
height: u16,
};
};
pub const Format = enum {
rgba8,
bgra8,
pub fn byte_size(self: Format) usize {
return switch (self) {
.rgba8, .bgra8 => 4,
};
}
};
};
pub const Transform2D = extern struct {
xbasis: Vector = .{1, 0},
ybasis: Vector = .{0, 1},
origin: Vector = @splat(0),
const Vector = @Vector(2, f32);
};
fn load_bmp_texture(arena: *std.heap.ArenaAllocator, storage: ona.files.Storage, path: []const u8) !Texture.Desc {
const header = try storage.read_little(path, 0, extern struct {
type: [2]u8 align (1),
file_size: u32 align (1),
reserved: [2]u16 align (1),
image_offset: u32 align (1),
header_size: u32 align (1),
pixel_width: i32 align (1),
pixel_height: i32 align (1),
color_planes: u16 align (1),
bits_per_pixel: u16 align (1),
compression_method: u32 align (1),
image_size: u32 align(1),
pixels_per_meter_x: i32 align (1),
pixels_per_meter_y: i32 align (1),
palette_colors_used: u32 align (1),
important_colors_used: u32 align (1),
}) orelse {
return error.FormatUnsupported;
};
if (!std.mem.eql(u8, &header.type, "BM")) {
return error.FormatUnsupported;
}
const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
return error.FormatUnsupported;
};
const pixels = try arena.allocator().alloc(u8, header.image_size);
const bytes_per_pixel = header.bits_per_pixel / @bitSizeOf(u8);
const alignment = 4;
const byte_stride = pixel_width * bytes_per_pixel;
const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
const byte_padding = ona.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
var buffer_offset: usize = 0;
var file_offset = @as(usize, header.image_offset);
switch (header.bits_per_pixel) {
32 => {
while (buffer_offset < pixels.len) {
const line = pixels[buffer_offset .. buffer_offset + byte_stride];
if (try storage.read(path, line, file_offset) != byte_stride) {
return error.FormatUnsupported;
}
for (0 .. pixel_width) |i| {
const line_offset = i * 4;
const pixel = line[line_offset .. line_offset + 4];
std.mem.swap(u8, &pixel[0], &pixel[2]);
}
file_offset += line.len + byte_padding;
buffer_offset += padded_byte_stride;
}
},
else => return error.FormatUnsupported,
}
return .{
.format = .rgba8,
.access = .{
.static = .{
.width = pixel_width,
.data = pixels,
},
},
};
}
pub fn poll(app: ona.Write(ona.App), events: ona.Send(input.Event)) !void {
var event = @as(ext.SDL_Event, undefined);
while (ext.SDL_PollEvent(&event) != 0) {
switch (event.type) {
ext.SDL_QUIT => app.state.quit(),
ext.SDL_KEYUP => try events.push(.{.key_up = @enumFromInt(event.key.keysym.scancode)}),
ext.SDL_KEYDOWN => try events.push(.{.key_down = @enumFromInt(event.key.keysym.scancode)}),
else => {},
}
}
}
pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) {
return error.Unsupported;
}
const assets = create: {
var assets = try Assets.init();
errdefer {
assets.deinit();
}
break: create try world.set_get_state(assets);
};
assets.frame_rendered.set();
errdefer {
assets.deinit();
}
assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{
&assets.pending_work,
assets.window,
});
const builtin_texture_formats = [_]Assets.TextureFormat{
.{
.extension = "bmp",
.load_file = load_bmp_texture,
},
};
for (builtin_texture_formats) |format| {
try assets.texture_formats.push_grow(format);
}
try world.set_state(Display{});
try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll coral"});
try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop coral"});
try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize coral"});
}
pub fn stop(assets: ona.Write(Assets)) void {
assets.state.deinit();
}
pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void {
const assets, const display = exclusive.states;
assets.frame_rendered.wait();
assets.frame_rendered.reset();
{
var has_command_param = exclusive.world.get_params(Commands).has_head;
while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
command_param.param.rotate();
}
}
var display_width, var display_height = [_]c_int{0, 0};
ext.SDL_GL_GetDrawableSize(assets.window, &display_width, &display_height);
if (display.width != display_width or display.height != display_height) {
ext.SDL_SetWindowSize(assets.window, display.width, display.height);
}
if (exclusive.world.get_params(Commands).has_head) |command_param| {
assets.pending_work.enqueue(.{
.render_frame = .{
.has_command_params = command_param,
.width = display.width,
.height = display.height,
.clear_color = display.clear_color,
.finished = &assets.frame_rendered,
},
});
} else {
assets.frame_rendered.set();
}
}

View File

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

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

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 states = @import("./states.zig");
const ona = @import("./ona.zig");
const std = @import("std");
const World = @import("./World.zig");
thread_pool: ?*std.Thread.Pool = null,
thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable,
event_systems: ona.stack.Sequential(Schedule),
pub const BindContext = struct {
node: dag.Node,
node: ona.dag.Node,
systems: *Schedule,
world: *World,
world: *Self,
pub const ResourceAccess = std.meta.Tag(Schedule.ResourceAccess);
pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: states.TypeID) bool {
pub fn accesses_state(self: BindContext, access: std.meta.Tag(StateAccess), id: ona.TypeID) bool {
const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses;
for (resource_accesses.values) |resource_access| {
@ -42,23 +40,23 @@ pub const BindContext = struct {
return null;
};
const id = states.type_id(Resource);
const id = ona.type_id(Resource);
if (!self.accesses_resource(.read_write, id)) {
if (!self.accesses_state(.read_write, id)) {
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id});
}
const read_write_resource_nodes = lazily_create: {
break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: {
std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
.allocator = coral.heap.allocator,
.allocator = ona.heap.allocator,
}));
break: insert self.systems.read_write_resource_id_nodes.get(id).?;
};
};
if (std.mem.indexOfScalar(dag.Node, read_write_resource_nodes.values, self.node) == null) {
if (std.mem.indexOfScalar(ona.dag.Node, read_write_resource_nodes.values, self.node) == null) {
try read_write_resource_nodes.push_grow(self.node);
}
@ -70,23 +68,23 @@ pub const BindContext = struct {
return null;
};
const id = states.type_id(Resource);
const id = ona.type_id(Resource);
if (!self.accesses_resource(.read_only, id)) {
if (!self.accesses_state(.read_only, id)) {
try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id});
}
const read_only_resource_nodes = lazily_create: {
break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: {
std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
.allocator = coral.heap.allocator,
.allocator = ona.heap.allocator,
}));
break: insert self.systems.read_only_resource_id_nodes.get(id).?;
};
};
if (std.mem.indexOfScalar(dag.Node, read_only_resource_nodes.values, self.node) == null) {
if (std.mem.indexOfScalar(ona.dag.Node, read_only_resource_nodes.values, self.node) == null) {
try read_only_resource_nodes.push_grow(self.node);
}
@ -94,70 +92,48 @@ pub const BindContext = struct {
}
};
pub const Info = struct {
execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void,
parameters: [max_parameters]*const Parameter = undefined,
parameter_count: u4 = 0,
thread_restriction: states.ThreadRestriction = .none,
const Dependency = struct {
kind: Kind,
id: usize,
pub const Parameter = struct {
thread_restriction: states.ThreadRestriction,
init: *const fn (*anyopaque, *anyopaque) void,
bind: *const fn (std.mem.Allocator, BindContext) std.mem.Allocator.Error!*anyopaque,
unbind: *const fn (std.mem.Allocator, *anyopaque, UnbindContext) void,
const Kind = enum {
after,
before,
};
pub fn used_parameters(self: *const Info) []const *const Parameter {
return self.parameters[0 .. self.parameter_count];
}
};
pub const Order = struct {
label: []const u8 = "",
run_after: []const *const Info = &.{},
run_before: []const *const Info = &.{},
const Graph = ona.dag.Graph(struct {
info: *const ona.SystemInfo,
label: [:0]u8,
dependencies: []Dependency,
parameter_states: [ona.SystemInfo.max_parameters]*anyopaque = [_]*anyopaque{undefined} ** ona.SystemInfo.max_parameters,
resource_accesses: ona.stack.Sequential(StateAccess),
});
pub const Event = enum (usize) { _ };
const ParallelNodeBundles = ona.stack.Sequential(NodeBundle);
const NodeBundle = ona.stack.Sequential(ona.dag.Node);
const StateAccess = union (enum) {
read_only: ona.TypeID,
read_write: ona.TypeID,
};
pub const Schedule = struct {
const ResourceNodeBundle = ona.map.Hashed(ona.TypeID, NodeBundle, ona.map.enum_traits(ona.TypeID));
const Schedule = struct {
label: [:0]const u8,
graph: Graph,
arena: std.heap.ArenaAllocator,
system_id_nodes: coral.map.Hashed(usize, NodeBundle, coral.map.usize_traits),
system_id_nodes: ona.map.Hashed(usize, NodeBundle, ona.map.usize_traits),
read_write_resource_id_nodes: ResourceNodeBundle,
read_only_resource_id_nodes: ResourceNodeBundle,
parallel_work_bundles: ParallelNodeBundles,
blocking_work: NodeBundle,
const Dependency = struct {
kind: Kind,
id: usize,
const Kind = enum {
after,
before,
};
};
const Graph = dag.Graph(struct {
info: *const Info,
label: [:0]u8,
dependencies: []Dependency,
parameter_states: [max_parameters]*anyopaque = [_]*anyopaque{undefined} ** max_parameters,
resource_accesses: coral.stack.Sequential(ResourceAccess),
});
const NodeBundle = coral.stack.Sequential(dag.Node);
const ParallelNodeBundles = coral.stack.Sequential(NodeBundle);
const ResourceAccess = union (enum) {
read_only: states.TypeID,
read_write: states.TypeID,
};
const ResourceNodeBundle = coral.map.Hashed(states.TypeID, NodeBundle, coral.map.enum_traits(states.TypeID));
pub fn deinit(self: *Schedule, world: *World) void {
pub fn deinit(self: *Schedule, world: *Self) void {
{
var nodes = self.system_id_nodes.entries();
@ -194,8 +170,8 @@ pub const Schedule = struct {
}
system.resource_accesses.deinit();
coral.heap.allocator.free(system.dependencies);
coral.heap.allocator.free(system.label);
ona.heap.allocator.free(system.dependencies);
ona.heap.allocator.free(system.label);
}
for (self.parallel_work_bundles.values) |*bundle| {
@ -211,7 +187,7 @@ pub const Schedule = struct {
self.arena.deinit();
}
pub fn run(self: *Schedule, world: *World) anyerror!void {
pub fn run(self: *Schedule, world: *Self) anyerror!void {
if (self.is_invalidated()) {
const work = struct {
fn regenerate_graph(schedule: *Schedule) !void {
@ -276,7 +252,7 @@ pub const Schedule = struct {
}
}
fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: dag.Node) !void {
fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: ona.dag.Node) !void {
std.debug.assert(graph.mark_visited(node));
for (graph.edge_nodes(node).?) |edge| {
@ -300,7 +276,7 @@ pub const Schedule = struct {
continue;
}
try schedule.parallel_work_bundles.push_grow(.{.allocator = coral.heap.allocator});
try schedule.parallel_work_bundles.push_grow(.{.allocator = ona.heap.allocator});
const bundle = schedule.parallel_work_bundles.get_ptr().?;
@ -381,21 +357,21 @@ pub const Schedule = struct {
}
pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule {
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
var arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
errdefer arena.deinit();
const duped_label = try arena.allocator().dupeZ(u8, label);
return .{
.graph = Graph.init(coral.heap.allocator),
.graph = Graph.init(ona.heap.allocator),
.label = duped_label,
.arena = arena,
.system_id_nodes = .{.allocator = coral.heap.allocator},
.read_write_resource_id_nodes = .{.allocator = coral.heap.allocator},
.read_only_resource_id_nodes = .{.allocator = coral.heap.allocator},
.parallel_work_bundles = .{.allocator = coral.heap.allocator},
.blocking_work = .{.allocator = coral.heap.allocator},
.system_id_nodes = .{.allocator = ona.heap.allocator},
.read_write_resource_id_nodes = .{.allocator = ona.heap.allocator},
.read_only_resource_id_nodes = .{.allocator = ona.heap.allocator},
.parallel_work_bundles = .{.allocator = ona.heap.allocator},
.blocking_work = .{.allocator = ona.heap.allocator},
};
}
@ -413,7 +389,7 @@ pub const Schedule = struct {
return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty();
}
pub fn then(self: *Schedule, world: *World, info: *const Info, order: Order) std.mem.Allocator.Error!void {
pub fn then(self: *Schedule, world: *Self, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
const nodes = lazily_create: {
const system_id = @intFromPtr(info);
@ -428,7 +404,7 @@ pub const Schedule = struct {
const dependencies = init: {
const total_run_orders = order.run_after.len + order.run_before.len;
const dependencies = try coral.heap.allocator.alloc(Dependency, total_run_orders);
const dependencies = try ona.heap.allocator.alloc(Dependency, total_run_orders);
var dependencies_written = @as(usize, 0);
for (order.run_after) |after_system| {
@ -452,17 +428,17 @@ pub const Schedule = struct {
break: init dependencies;
};
errdefer coral.heap.allocator.free(dependencies);
errdefer ona.heap.allocator.free(dependencies);
const label = try coral.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
const label = try ona.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
errdefer coral.heap.allocator.free(label);
errdefer ona.heap.allocator.free(label);
const node = try self.graph.append(.{
.info = info,
.label = label,
.dependencies = dependencies,
.resource_accesses = .{.allocator = coral.heap.allocator},
.resource_accesses = .{.allocator = ona.heap.allocator},
});
const system = self.graph.get_ptr(node).?;
@ -491,8 +467,144 @@ pub const Schedule = struct {
}
};
pub const UnbindContext = struct {
world: *World,
const Self = @This();
const StateTable = struct {
arena: std.heap.ArenaAllocator,
table: ona.map.Hashed(ona.TypeID, Entry, ona.map.enum_traits(ona.TypeID)),
const Entry = struct {
ptr: *anyopaque,
};
fn deinit(self: *StateTable) void {
self.table.deinit();
self.arena.deinit();
self.* = undefined;
}
fn get(self: StateTable, comptime Resource: type) ?*Resource {
if (self.table.get(ona.type_id(Resource))) |entry| {
return @ptrCast(@alignCast(entry.ptr));
}
return null;
}
fn init() StateTable {
return .{
.arena = std.heap.ArenaAllocator.init(ona.heap.allocator),
.table = .{.allocator = ona.heap.allocator},
};
}
fn set_get(self: *StateTable, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
try self.set(value);
return self.get(@TypeOf(value)).?;
}
fn set(self: *StateTable, value: anytype) std.mem.Allocator.Error!void {
const Value = @TypeOf(value);
const value_id = ona.type_id(Value);
if (self.table.get(value_id)) |entry| {
@as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value;
} else {
const resource_allocator = self.arena.allocator();
const allocated_resource = try resource_allocator.create(Value);
errdefer resource_allocator.destroy(allocated_resource);
std.debug.assert(try self.table.emplace(value_id, .{
.ptr = allocated_resource,
}));
allocated_resource.* = value;
}
}
};
pub const max_parameters = 16;
pub const UnbindContext = struct {
world: *Self,
};
pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event {
var systems = try Schedule.init(label);
errdefer systems.deinit(self);
const index = self.event_systems.len();
try self.event_systems.push_grow(systems);
return @enumFromInt(index);
}
pub fn deinit(self: *Self) void {
for (self.event_systems.values) |*schedule| {
schedule.deinit(self);
}
for (&self.thread_restricted_resources) |*resources| {
resources.deinit();
}
if (self.thread_pool) |thread_pool| {
thread_pool.deinit();
ona.heap.allocator.destroy(thread_pool);
}
self.event_systems.deinit();
self.* = undefined;
}
pub fn get_params(self: Self, comptime Value: type) ona.Params(Value) {
const params = self.get_state(ona.Params(Value)) orelse {
return .{};
};
return params.*;
}
pub fn get_state(self: Self, comptime Value: type) ?*Value {
return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(Value))].get(Value)));
}
pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
return self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(@TypeOf(value)))].set_get(value);
}
pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
var world = Self{
.thread_restricted_resources = .{StateTable.init(), StateTable.init()},
.event_systems = .{.allocator = ona.heap.allocator},
};
if (thread_count != 0 and !builtin.single_threaded) {
const thread_pool = try ona.heap.allocator.create(std.Thread.Pool);
try thread_pool.init(.{
.allocator = ona.heap.allocator,
.n_jobs = thread_count,
});
world.thread_pool = thread_pool;
}
return world;
}
pub fn on_event(self: *Self, event: Event, action: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
try self.event_systems.values[@intFromEnum(event)].then(self, action, order);
}
pub fn run_event(self: *Self, event: Event) anyerror!void {
try self.event_systems.values[@intFromEnum(event)].run(self);
}
pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void {
try self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(@TypeOf(value)))].set(value);
}

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

View File

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

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

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

View File

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

View File

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

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

View File

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

View File

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

View File

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