renderer-mvp/post-processing #56

Merged
kayomn merged 30 commits from renderer-mvp/post-processing into main 2024-07-22 02:05:25 +02:00
52 changed files with 3040 additions and 4833 deletions

View File

@ -1,8 +1,25 @@
kind: pipeline
type: docker
name: continuous integration
environment:
ZIG_VERSION: 0.13.0
steps:
- name: build & test
image: chainguard/zig:latest
commands:
- zig build test
- name: submodules
image: ubuntu:latest
commands:
- apt-get update
- apt-get install -y git git-lfs
- git submodule update --init --recursive
- name: build & test
image: ubuntu:latest
commands:
- apt-get update
- apt-get install -y openssl xz-utils wget libsdl2-dev glslang-tools spirv-tools
- wget https://ziglang.org/download/$${ZIG_VERSION}/zig-linux-x86_64-$${ZIG_VERSION}.tar.xz
- tar -xf zig-linux-x86_64-$${ZIG_VERSION}.tar.xz
- export PATH=$PATH:$(pwd)/zig-linux-x86_64-$${ZIG_VERSION}
- zig build
- zig build test

2
.gitattributes vendored
View File

@ -1,3 +1 @@
tools/sokol-shdc filter=lfs diff=lfs merge=lfs -text
tools/sokol-shdc.exe filter=lfs diff=lfs merge=lfs -text
*.bmp filter=lfs diff=lfs merge=lfs -text

4
.gitignore vendored
View File

@ -1,4 +1,4 @@
# Generated assets
/.zig-cache
.zig-cache
/zig-out
*.glsl.zig
*.spv

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "ext/spirv-cross"]
path = ext/spirv-cross
url = https://github.com/KhronosGroup/SPIRV-Cross/

2
.vscode/launch.json vendored
View File

@ -7,7 +7,7 @@
"request": "launch",
"target": "${workspaceRoot}/zig-out/bin/main",
"cwd": "${workspaceRoot}/debug/",
"valuesFormatting": "parseText",
"valuesFormatting": "prettyPrinters",
"preLaunchTask": "Build All"
},
]

14
.vscode/settings.json vendored
View File

@ -10,5 +10,17 @@
"editor.detectIndentation": false,
"editor.insertSpaces": false,
"editor.rulers": [120],
}
},
"files.exclude": {
"**/.git": true,
"**/.svn": true,
"**/.hg": true,
"**/CVS": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
"**/.zig-cache": true,
"zig-out": true,
".drone.yml": true,
},
}

269
build.zig
View File

@ -2,136 +2,223 @@ const builtin = @import("builtin");
const std = @import("std");
const SubPath = struct {
buffer: [max]u8 = [_]u8{0} ** max,
unused: u8 = max,
const max = 255;
pub fn utf8(self: *const SubPath) [:0]const u8 {
return self.buffer[0 .. (max - self.unused):0];
}
};
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const sokol_dependency = b.dependency("sokol", .{
.target = target,
.optimize = optimize,
});
const coral_module = b.createModule(.{
.root_source_file = b.path("src/coral/coral.zig"),
});
const ona_module = add: {
const sokol_dependency = b.dependency("sokol", .{
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"),
.imports = &.{
.{
.name = "sokol",
.module = sokol_dependency.module("sokol"),
},
.{
.name = "coral",
.module = coral_module,
},
.{
.name = "flow",
.module = flow_module,
},
},
});
ona_module.addIncludePath(b.path("ext/"));
ona_module.linkLibrary(spirv_cross: {
const dir = "ext/spirv-cross/";
const sources = [_][]const u8{
"spirv_cross.cpp",
"spirv_parser.cpp",
"spirv_cross_parsed_ir.cpp",
"spirv_cfg.cpp",
"spirv_glsl.cpp",
"spirv_msl.cpp",
"spirv_hlsl.cpp",
"spirv_reflect.cpp",
"spirv_cross_util.cpp",
"spirv_cross_c.cpp",
};
const lib = b.addStaticLibrary(.{
.name = "spirvcross",
.target = target,
.optimize = optimize,
});
const module = b.addModule("ona", .{
.root_source_file = b.path("src/ona/ona.zig"),
switch (lib.rootModuleTarget().abi) {
.msvc => lib.linkLibC(),
else => lib.linkLibCpp(),
}
.imports = &.{
.{
.name = "sokol",
.module = sokol_dependency.module("sokol"),
},
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"},
});
}
.{
.name = "coral",
.module = coral_module,
},
},
});
break: spirv_cross lib;
});
break: add module;
};
b.step("test", "Run unit tests").dependOn(create: {
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: create &tests.step;
break: tests &tests.step;
});
b.installArtifact(add: {
const compile_step = b.addExecutable(.{
const exe = b.addExecutable(.{
.name = "main",
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});
compile_step.root_module.addImport("ona", ona_module);
compile_step.root_module.addImport("coral", coral_module);
compile_step.linkLibC();
compile_step.linkSystemLibrary("SDL2");
exe.root_module.addImport("ona", ona_module);
exe.root_module.addImport("coral", coral_module);
exe.linkLibC();
exe.linkSystemLibrary("SDL2");
try depend_on_shaders(b, target, "src/ona/gfx/shaders/", &compile_step.step);
const shaders_sub_path = "src/ona/gfx/shaders/";
break: add compile_step;
});
}
var shaders_dir = try std.fs.cwd().openDir(shaders_sub_path, .{
.iterate = true,
});
fn depend_on_shaders(
b: *std.Build,
target: std.Build.ResolvedTarget,
shader_dir_path: []const u8,
step: *std.Build.Step,
) !void {
var dir = try std.fs.cwd().openDir(shader_dir_path, .{ .iterate = true });
defer shaders_dir.close();
defer dir.close();
var shaders_walker = try shaders_dir.walk(b.allocator);
var walker = try dir.walk(b.allocator);
defer shaders_walker.deinit();
defer walker.deinit();
const shdc_path = switch (builtin.os.tag) {
.windows => "./tools/sokol-shdc.exe",
.linux => "./tools/sokol-shdc",
else => @compileError("cannot compile sokol shaders on this platform"),
};
const path_buffer_max = 255;
var input_path_buffer = [_]u8{undefined} ** path_buffer_max;
var output_path_buffer = [_]u8{undefined} ** path_buffer_max;
const glsl = if (target.result.isDarwin()) "glsl410" else "glsl430";
const slang = glsl ++ ":metal_macos:hlsl5:glsl300es:wgsl";
while (try walker.next()) |entry| {
if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".glsl")) {
continue;
}
const input_path = try std.fmt.bufPrint(&input_path_buffer, "{s}{s}", .{shader_dir_path, entry.path});
const output_path = try std.fmt.bufPrint(&output_path_buffer, "{s}.zig", .{input_path});
const output = std.fs.path.basename(output_path);
dir.access(output, .{.mode = .read_only}) catch {
const cmd = b.addSystemCommand(&.{
shdc_path,
"-i",
input_path,
"-o",
output_path,
"-l",
slang,
"-f",
"sokol_zig",
});
step.dependOn(&cmd.step);
continue;
const Shader = struct {
source_sub_path: SubPath = .{},
binary_sub_path: SubPath = .{},
};
if ((try dir.statFile(entry.path)).mtime > (try dir.statFile(output)).mtime) {
const cmd = b.addSystemCommand(&.{
shdc_path,
"-i",
input_path,
"-o",
output_path,
"-l",
slang,
"-f",
"sokol_zig",
var pending_shaders = std.ArrayList(Shader).init(b.allocator);
defer pending_shaders.deinit();
scan_shaders: while (try shaders_walker.next()) |entry| {
if (entry.kind != .file) {
continue: scan_shaders;
}
const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
if (!is_shader_file) {
continue: scan_shaders;
}
const shader_name = std.fs.path.stem(entry.path);
for (pending_shaders.items) |pending_shader| {
if (std.mem.endsWith(u8, pending_shader.source_sub_path.utf8(), shader_name)) {
continue: scan_shaders;
}
}
var shader = Shader{};
const source_sub_path = try std.fmt.bufPrint(&shader.source_sub_path.buffer, "{s}{s}", .{shaders_sub_path, shader_name});
const binary_sub_path = try std.fmt.bufPrint(&shader.binary_sub_path.buffer, "{s}.spv", .{source_sub_path});
shaders_dir.access(std.fs.path.basename(binary_sub_path), .{.mode = .read_only}) catch {
shader.source_sub_path.unused -= @intCast(source_sub_path.len);
shader.binary_sub_path.unused -= @intCast(binary_sub_path.len);
try pending_shaders.append(shader);
continue: scan_shaders;
};
if ((try shaders_dir.statFile(entry.basename)).mtime > (try shaders_dir.statFile(std.fs.path.basename(binary_sub_path))).mtime) {
shader.source_sub_path.unused -= @intCast(source_sub_path.len);
shader.binary_sub_path.unused -= @intCast(binary_sub_path.len);
try pending_shaders.append(shader);
continue: scan_shaders;
}
}
for (pending_shaders.items) |pending_shader| {
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;
});
step.dependOn(&cmd.step);
exe.step.dependOn(compile_fragment: {
const compile_command = b.addSystemCommand(&.{
"glslangValidator",
"-V",
fragment_binary_sub_path_utf8[0 .. fragment_binary_sub_path_utf8.len - 4],
"-o",
fragment_binary_sub_path_utf8,
});
break: compile_fragment &compile_command.step;
});
}
}
break: add exe;
});
}

View File

@ -1,17 +1,17 @@
.{
.name = "Ona",
.version = "0.0.1",
.paths = .{
"src",
"build.zig",
"build.zig.zon",
.name = "Ona",
.version = "0.0.1",
.paths = .{
"src",
"build.zig",
"build.zig.zon",
"LICENSE",
"README.md",
},
.dependencies = .{
.sokol = .{
.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
},
.dependencies = .{
.sokol = .{
.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
},
},
},
},
}

25
debug/ca.frag Normal file
View File

@ -0,0 +1,25 @@
#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);
}

52
debug/crt.frag Normal file
View File

@ -0,0 +1,52 @@
#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 screen_width;
float screen_height;
float time;
};
vec3 scanline(vec2 coord, vec3 screen)
{
screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02;
return screen;
}
vec2 crt(vec2 coord, float bend)
{
// put in symmetrical coords
coord = (coord - 0.5) * 2.0;
coord *= 1.0;
// deform coords
coord.x *= 1.0 + pow((abs(coord.y) / bend), 2.0);
coord.y *= 1.0 + pow((abs(coord.x) / bend), 2.0);
// transform back to 0.0 - 1.0 space
coord = (coord / 2.0) + 0.5;
return coord;
}
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
debug/test.bmp (Stored with Git LFS)

Binary file not shown.

1
ext/spirv-cross Submodule

@ -0,0 +1 @@
Subproject commit 6fd1f75636b1c424b809ad8a84804654cf5ae48b

View File

@ -33,6 +33,7 @@ Ona is also the Catalan word for "wave".
Ona currently depends the following third-party tools to build it:
* Platform support for SDL2 at version 2.0.20 or above.
* SPIR-V shader compilation system utilities, namely `glslangValidator`.
* Zig compiler toolchain.
As the project evolves, dependencies on libraries external to the project codebase will be minimized or removed outright to meet the goals of the project as closely as possible.

View File

@ -7,7 +7,7 @@ const scalars = @import("./scalars.zig");
const std = @import("std");
pub const DecimalFormat = struct {
delimiter: []const coral.Byte,
delimiter: []const coral.io.Byte,
positive_prefix: enum {none, plus, space},
pub const default = DecimalFormat{
@ -115,9 +115,9 @@ pub const DecimalFormat = struct {
}
}
pub fn print(self: DecimalFormat, writer: io.Writer, value: anytype) io.PrintError!void {
pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.Error!void {
if (value == 0) {
return io.print(writer, switch (self.positive_prefix) {
return io.write_all(writer, switch (self.positive_prefix) {
.none => "0",
.plus => "+0",
.space => " 0",
@ -151,21 +151,21 @@ pub const DecimalFormat = struct {
}
}
return io.print(writer, buffer[buffer_start ..]);
return io.write_all(writer, buffer[buffer_start ..]);
},
.Float => |float| {
if (value < 0) {
try io.print(writer, "-");
try io.write_all(writer, "-");
}
const Float = @TypeOf(value);
const Int = std.meta.Int(.unsigned, float.bits);
const integer = @as(Int, @intFromFloat(value));
try self.print(writer, integer);
try io.print(writer, ".");
try self.print(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
try self.format(writer, integer);
try io.write_all(writer, ".");
try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
},
else => @compileError("`" ++ @typeName(Value) ++ "` cannot be formatted to a decimal string"),
@ -178,13 +178,13 @@ pub const HexadecimalFormat = struct {
positive_prefix: enum {none, plus, space} = .none,
casing: enum {lower, upper} = .lower,
const default = HexadecimalFormat{
pub const default = HexadecimalFormat{
.delimiter = "",
.positive_prefix = .none,
.casing = .lower,
};
pub fn print(self: HexadecimalFormat, writer: io.Writer, value: anytype) ?usize {
pub fn format(self: HexadecimalFormat, writer: io.Writer, value: anytype) io.Error!void {
// TODO: Implement.
_ = self;
_ = writer;

76
src/coral/asyncio.zig Normal file
View File

@ -0,0 +1,76 @@
const std = @import("std");
pub fn BlockingQueue(comptime max: usize, comptime T: type) type {
return struct {
buffer: [max]T = undefined,
head: usize = 0,
tail: usize = 0,
mutex: std.Thread.Mutex = .{},
not_empty: std.Thread.Condition = .{},
not_full: std.Thread.Condition = .{},
const Self = @This();
pub fn enqueue(self: *Self, item: T) void {
self.mutex.lock();
defer self.mutex.unlock();
while (((self.tail + 1) % max) == self.head) {
self.not_full.wait(&self.mutex);
}
self.buffer[self.tail] = item;
self.tail = (self.tail + 1) % max;
self.not_empty.signal();
}
pub fn dequeue(self: *Self) T {
self.mutex.lock();
defer self.mutex.unlock();
while (self.head == self.tail) {
self.not_empty.wait(&self.mutex);
}
const item = self.buffer[self.head];
self.buffer[self.head] = undefined;
self.head = (self.head + 1) % max;
self.not_full.signal();
return item;
}
};
}
pub fn Future(comptime T: type) type {
return struct {
value: ?T = null,
resolved: std.Thread.ResetEvent = .{},
const Self = @This();
pub fn get(self: *Self) *T {
// TODO: Make async.
self.resolved.wait();
return &self.value.?;
}
pub fn resolve(self: *Self, value: T) bool {
if (self.resolved.isSet()) {
return false;
}
self.value = value;
self.resolved.set();
return true;
}
};
}

View File

@ -1,6 +1,6 @@
pub const ascii = @import("./ascii.zig");
pub const dag = @import("./dag.zig");
pub const asyncio = @import("./asyncio.zig");
pub const files = @import("./files.zig");
@ -10,302 +10,116 @@ 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 slots = @import("./slots.zig");
pub const stack = @import("./stack.zig");
pub const system = @import("./system.zig");
pub const utf8 = @import("./utf8.zig");
pub const vectors = @import("./vectors.zig");
pub const World = @import("./World.zig");
const std = @import("std");
pub const ResourceOptions = struct {
thread_restriction: World.ThreadRestriction,
read_only: bool = false,
};
pub fn Read(comptime Value: type) type {
return Resource(Value, .{
.thread_restriction = .none,
.read_only = true,
});
}
pub fn ReadBlocking(comptime Value: type) type {
return Resource(Value, .{
.thread_restriction = .main,
.read_only = true,
});
}
pub fn Resource(comptime Value: type, comptime options: ResourceOptions) 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 = options.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 = options.read_only,
},
}),
};
pub const utf8 = @import("./utf8.zig");
pub fn Pool(comptime Value: type) type {
return struct {
res: Qualified,
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 const State = struct {
res: Qualified,
};
pub fn deinit(self: *Self) void {
self.entries.deinit();
pub fn bind(context: system.BindContext) std.mem.Allocator.Error!State {
const thread_restriction_name = switch (thread_restriction) {
.main => "main thread-restricted ",
.none => ""
};
self.* = undefined;
}
const res = switch (options.read_only) {
true => (try context.register_read_only_resource_access(thread_restriction, Value)),
false => (try context.register_read_write_resource_access(thread_restriction, 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 (options.read_only) "read-only" else "read-write",
@typeName(Value),
}));
},
},
pub fn get(self: *Self, key: usize) ?*Value {
return switch (self.entries.values[key]) {
.free_index => null,
.occupied => |*occupied| occupied,
};
}
pub fn init(state: *State) Self {
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.res = state.res,
.entries = .{.allocator = allocator},
};
}
pub const thread_restriction = options.thread_restriction;
};
}
pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
const entries_count = self.entries.len();
pub fn Write(comptime Value: type) type {
return Resource(Value, .{
.thread_restriction = .none,
});
}
if (self.first_free_index == entries_count) {
try self.entries.push_grow(.{.occupied = value});
pub fn WriteBlocking(comptime Value: type) type {
return Resource(Value, .{
.thread_restriction = .main,
});
}
self.first_free_index += 1;
fn parameter_type(comptime Value: type) *const system.Info.Parameter {
const has_state = @hasDecl(Value, "State");
return entries_count;
}
if (@sizeOf(Value) == 0) {
@compileError("System parameters must have a non-zero size");
}
const insersion_index = self.first_free_index;
const parameters = struct {
fn bind(allocator: std.mem.Allocator, context: system.BindContext) std.mem.Allocator.Error!?*anyopaque {
if (has_state) {
const value_name = @typeName(Value);
self.first_free_index = self.entries.values[self.first_free_index].free_index;
self.entries.values[insersion_index] = .{.occupied = value};
if (!@hasDecl(Value, "bind")) {
@compileError(
"a `bind` declaration on " ++
value_name ++
" is requied for parameter types with a `State` declaration");
}
return insersion_index;
}
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 state = try allocator.create(Value.State);
state.* = switch (bind_type.Fn.return_type.?) {
Value.State => Value.bind(context),
std.mem.Allocator.Error!Value.State => try Value.bind(context),
else => @compileError(
"`bind` fn on " ++
@typeName(Value) ++
" must return " ++
@typeName(Value.State) ++
" or " ++
@typeName(std.mem.Allocator.Error!Value.State)),
};
return @ptrCast(state);
} else {
pub fn remove(self: *Self, key: usize) ?Value {
if (key >= self.entries.len()) {
return null;
}
}
fn init(argument: *anyopaque, state: ?*anyopaque) void {
const value_name = @typeName(Value);
switch (self.entries.values[key]) {
.free_index => {
return null;
},
if (!@hasDecl(Value, "init")) {
@compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types");
}
.occupied => |occupied| {
self.entries.values[key] = .{.free_index = self.first_free_index};
self.first_free_index = key;
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 (has_state) {
if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.State) {
@compileError("`init` fn on stateful " ++ value_name ++ " must accept a " ++ @typeName(*Value.State));
}
concrete_argument.* = Value.init(@ptrCast(@alignCast(state.?)));
} else {
if (init_type.Fn.params.len != 0) {
@compileError("`init` fn on statelss " ++ value_name ++ " cannot use parameters");
}
concrete_argument.* = Value.init();
return occupied;
},
}
}
fn unbind(allocator: std.mem.Allocator, state: ?*anyopaque) void {
if (@hasDecl(Value, "unbind")) {
if (has_state) {
const typed_state = @as(*Value.State, @ptrCast(@alignCast(state.?)));
Value.unbind(typed_state);
allocator.destroy(typed_state);
} else {
Value.unbind();
}
}
pub fn values(self: *const Self) Values {
return .{
.pool = self,
};
}
};
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, states: *const [system.max_parameters]?*anyopaque) anyerror!void {
var call_args = @as(std.meta.ArgsTuple(Call), undefined);
inline for (parameters, &call_args, states[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 = World.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

@ -4,11 +4,20 @@ const io = @import("./io.zig");
const std = @import("std");
pub const Error = error {
pub const AccessError = error {
FileNotFound,
FileInaccessible,
};
pub const ReadAllError = AccessError || error {
ReadIncomplete,
};
pub const ReadAllOptions = struct {
offset: u64 = 0,
limit: u64 = std.math.maxInt(u64),
};
pub const Stat = struct {
size: u64,
};
@ -18,15 +27,37 @@ pub const Storage = struct {
vtable: *const VTable,
pub const VTable = struct {
stat: *const fn (*anyopaque, []const u8) Error!Stat,
read: *const fn (*anyopaque, []const u8, usize, []io.Byte) Error!usize,
stat: *const fn (*anyopaque, []const u8) AccessError!Stat,
read: *const fn (*anyopaque, []const u8, usize, []io.Byte) AccessError!usize,
};
pub fn read_bytes(self: Storage, path: []const u8, offset: usize, output: []io.Byte) Error!usize {
pub fn read(self: Storage, path: []const u8, output: []io.Byte, offset: u64) AccessError!usize {
return self.vtable.read(self.userdata, path, offset, output);
}
pub fn read_foreign(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type {
pub fn read_all(self: Storage, path: []const u8, output: []io.Byte, options: ReadAllOptions) ReadAllError![]const io.Byte {
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) {
return error.ReadIncomplete;
}
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));
errdefer allocator.free(buffer);
if (try self.vtable.read(self.userdata, path, options.offset, buffer) != buffer.len) {
return error.ReadIncomplete;
}
return buffer;
}
pub fn read_foreign(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
const decoded = (try self.read_native(path, offset, Type)) orelse {
return null;
};
@ -37,7 +68,7 @@ pub const Storage = struct {
};
}
pub fn read_native(self: Storage, path: []const u8, offset: usize, comptime Type: type) Error!?Type {
pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
var buffer = @as([@sizeOf(Type)]io.Byte, undefined);
if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
@ -47,6 +78,10 @@ pub const Storage = struct {
return @as(*align(1) const Type, @ptrCast(&buffer)).*;
}
pub fn stat(self: Storage, path: []const u8) AccessError!Stat {
return self.vtable.stat(self.userdata, path);
}
pub const read_little = switch (native_endian) {
.little => read_native,
.big => read_foreign,
@ -60,7 +95,7 @@ pub const Storage = struct {
pub const bundle = init: {
const Bundle = struct {
fn full_path(path: []const u8) Error![4095:0]u8 {
fn full_path(path: []const u8) AccessError![4095:0]u8 {
var buffer = [_:0]u8{0} ** 4095;
_ = std.fs.cwd().realpath(path, &buffer) catch {
@ -70,7 +105,7 @@ pub const bundle = init: {
return buffer;
}
fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) Error!usize {
fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) 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,
@ -89,7 +124,7 @@ pub const bundle = init: {
return file.read(output) catch error.FileInaccessible;
}
fn stat(_: *anyopaque, path: []const u8) Error!Stat {
fn stat(_: *anyopaque, path: []const u8) AccessError!Stat {
const file_stat = get: {
var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
return switch (open_error) {

View File

@ -108,13 +108,23 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type
};
}
pub const PrintError = Error || error {
IncompleteWrite,
pub const NullWritable = struct {
written: usize = 0,
pub fn write(self: *NullWritable, buffer: []const Byte) Error!usize {
self.written += buffer.len;
return buffer.len;
}
pub fn writer(self: *NullWritable) Writer {
return Writer.bind(NullWritable, self, write);
}
};
pub const Reader = Generator(Error!usize, &.{[]coral.Byte});
pub const Reader = Generator(Error!usize, &.{[]Byte});
pub const Writer = Generator(Error!usize, &.{[]const coral.Byte});
pub const Writer = Generator(Error!usize, &.{[]const Byte});
pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte {
const buffer = coral.Stack(coral.Byte){.allocator = allocator};
@ -128,22 +138,6 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.
pub const bits_per_byte = 8;
pub fn bytes_of(value: anytype) []const Byte {
const pointer_info = @typeInfo(@TypeOf(value)).Pointer;
return switch (pointer_info.size) {
.One => @as([*]const Byte, @ptrCast(value))[0 .. @sizeOf(pointer_info.child)],
.Slice => @as([*]const Byte, @ptrCast(value.ptr))[0 .. @sizeOf(pointer_info.child) * value.len],
else => @compileError("`value` must be single-element pointer or slice type"),
};
}
pub fn print(writer: Writer, utf8: []const u8) PrintError!void {
if (try writer.yield(.{utf8}) != utf8.len) {
return error.IncompleteWrite;
}
}
pub fn skip_n(input: Reader, distance: u64) Error!void {
var buffer = @as([512]coral.Byte, undefined);
var remaining = distance;
@ -159,16 +153,6 @@ pub fn skip_n(input: Reader, distance: u64) Error!void {
}
}
pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) {
var len = @as(usize, 0);
while (ptr[len] != sen) {
len += 1;
}
return ptr[0 .. len:sen];
}
pub fn stream_all(input: Reader, output: Writer) Error!usize {
var buffer = @as([512]coral.Byte, undefined);
var copied = @as(usize, 0);
@ -206,3 +190,9 @@ pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize {
remaining -= read;
}
}
pub fn write_all(writer: Writer, utf8: []const u8) Error!void {
if (try writer.yield(.{utf8}) != utf8.len) {
return error.UnavailableResource;
}
}

View File

@ -1,5 +1,18 @@
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;
@ -48,3 +61,16 @@ 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

@ -147,7 +147,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(
self.* = undefined;
}
pub fn get_ptr(self: Self, key: Key) ?*Value {
pub fn get(self: Self, key: Key) ?*Value {
if (self.len == 0) {
return null;
}
@ -169,14 +169,6 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits(
return null;
}
pub fn get(self: Self, key: Key) ?Value {
if (self.get_ptr(key)) |value| {
return value.*;
}
return null;
}
pub fn emplace(self: *Self, key: Key, value: Value) std.mem.Allocator.Error!bool {
try self.rehash(load_max);

View File

@ -1,998 +0,0 @@
const coral = @import("coral");
const file = @import("../file.zig");
const script = @import("../script.zig");
const std = @import("std");
const tokens = @import("./tokens.zig");
const tree = @import("./tree.zig");
name: *script.Object,
arity: u8,
opcodes: coral.Stack(Opcode),
lines: coral.Stack(tokens.Line),
cursor: usize,
constants: coral.Stack(*script.Object),
bindings: []?*script.Object,
externals: *script.Object,
const Compiler = struct {
chunk: *Self,
env: *script.Runtime,
fn compile_argument(self: *const Compiler, environment: *const tree.Environment, initial_argument: ?*const tree.Expr) script.Error!u8 {
// TODO: Exceeding 255 arguments will make the VM crash.
var maybe_argument = initial_argument;
var argument_count = @as(u8, 0);
while (maybe_argument) |argument| {
try self.compile_expression(environment, argument, null);
maybe_argument = argument.next;
argument_count += 1;
}
return argument_count;
}
fn compile_expression(self: *const Compiler, environment: *const tree.Environment, expression: *const tree.Expr, name: ?[]const u8) script.Error!void {
const number_format = coral.utf8.DecimalFormat{
.delimiter = "_",
.positive_prefix = .none,
};
switch (expression.kind) {
.nil_literal => try self.chunk.write(expression.line, .push_nil),
.true_literal => try self.chunk.write(expression.line, .push_true),
.false_literal => try self.chunk.write(expression.line, .push_false),
.number_literal => |literal| {
for (literal) |codepoint| {
if (codepoint == '.') {
return self.chunk.write(expression.line, .{
.push_const = try self.declare_float(number_format.parse(literal, script.Float) orelse unreachable),
});
}
}
try self.chunk.write(expression.line, .{
.push_const = try self.declare_fixed(number_format.parse(literal, script.Fixed) orelse unreachable),
});
},
.string_literal => |literal| {
try self.chunk.write(expression.line, .{.push_const = try self.declare_string(literal)});
},
.vector2 => |vector2| {
try self.compile_expression(environment, vector2.x, null);
try self.compile_expression(environment, vector2.y, null);
try self.chunk.write(expression.line, .push_vector2);
},
.vector3 => |vector3| {
try self.compile_expression(environment, vector3.x, null);
try self.compile_expression(environment, vector3.y, null);
try self.compile_expression(environment, vector3.z, null);
try self.chunk.write(expression.line, .push_vector3);
},
.string_template => {
var current_expression = expression.next orelse {
return self.chunk.write(expression.line, .{.push_const = try self.declare_string("")});
};
var component_count = @as(u8, 0);
while (true) {
try self.compile_expression(environment, current_expression, null);
component_count += 1;
current_expression = current_expression.next orelse {
return self.chunk.write(expression.line, .{.push_concat = component_count});
};
}
},
.symbol_literal => |literal| {
try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(literal)});
},
.table => |table| {
var entries = table.nodes();
var num_entries = @as(u16, 0);
while (entries.next()) |entry| {
try self.compile_expression(environment, entry.key, null);
try self.compile_expression(environment, entry.value, null);
num_entries = coral.scalars.add(num_entries, 1) orelse {
return self.env.raise(error.OutOfMemory, "too many initializer values", .{});
};
}
try self.chunk.write(expression.line, .{.push_table = num_entries});
},
.lambda_construct => |lambda_construct| {
var chunk = try Self.init(self.env, name orelse "<lambda>", lambda_construct.environment, &.{});
errdefer chunk.deinit(self.env);
if (lambda_construct.environment.capture_count == 0) {
try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)});
} else {
const lambda_captures = lambda_construct.environment.get_captures();
var index = lambda_captures.len;
while (index != 0) {
index -= 1;
try self.chunk.write(expression.line, switch (lambda_captures[index]) {
.declaration_index => |declaration_index| .{.push_local = declaration_index},
.capture_index => |capture_index| .{.push_binding = capture_index},
});
}
try self.chunk.write(expression.line, .{.push_const = try self.declare_chunk(chunk)});
try self.chunk.write(expression.line, .{.bind = lambda_construct.environment.capture_count});
}
},
.binary_op => |binary_op| {
try self.compile_expression(environment, binary_op.lhs_operand, null);
try self.compile_expression(environment, binary_op.rhs_operand, null);
try self.chunk.write(expression.line, switch (binary_op.operation) {
.addition => .add,
.subtraction => .sub,
.multiplication => .mul,
.divsion => .div,
.greater_equals_comparison => .cge,
.greater_than_comparison => .cgt,
.equals_comparison => .eql,
.less_than_comparison => .clt,
.less_equals_comparison => .cle,
});
},
.unary_op => |unary_op| {
try self.compile_expression(environment, unary_op.operand, null);
try self.chunk.write(expression.line, switch (unary_op.operation) {
.boolean_negation => .not,
.numeric_negation => .neg,
});
},
.invoke => |invoke| {
const argument_count = try self.compile_argument(environment, invoke.argument);
try self.compile_expression(environment, invoke.object, null);
try self.chunk.write(expression.line, .{.call = argument_count});
},
.group => |group| try self.compile_expression(environment, group, null),
.declaration_get => |declaration_get| {
if (get_local_index(environment, declaration_get.declaration)) |index| {
if (is_declaration_boxed(declaration_get.declaration)) {
try self.chunk.write(expression.line, .{.push_local = index});
try self.chunk.write(expression.line, .get_box);
} else {
try self.chunk.write(expression.line, .{.push_local = index});
}
return;
}
if (try get_binding_index(environment, declaration_get.declaration)) |index| {
try self.chunk.write(expression.line, .{.push_binding = index});
if (is_declaration_boxed(declaration_get.declaration)) {
try self.chunk.write(expression.line, .get_box);
}
return;
}
return self.env.raise(error.IllegalState, "local out of scope", .{});
},
.declaration_set => |declaration_set| {
if (get_local_index(environment, declaration_set.declaration)) |index| {
if (is_declaration_boxed(declaration_set.declaration)) {
try self.chunk.write(expression.line, .{.push_local = index});
try self.compile_expression(environment, declaration_set.assign, null);
try self.chunk.write(expression.line, .set_box);
} else {
try self.compile_expression(environment, declaration_set.assign, null);
try self.chunk.write(expression.line, .{.set_local = index});
}
return;
}
if (try get_binding_index(environment, declaration_set.declaration)) |index| {
try self.compile_expression(environment, declaration_set.assign, null);
try self.chunk.write(expression.line, .{.push_binding = index});
if (is_declaration_boxed(declaration_set.declaration)) {
try self.chunk.write(expression.line, .set_box);
}
return;
}
return self.env.raise(error.IllegalState, "local out of scope", .{});
},
.field_get => |field_get| {
try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_get.identifier)});
try self.compile_expression(environment, field_get.object, null);
try self.chunk.write(expression.line, .get_dynamic);
},
.field_set => |field_set| {
try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(field_set.identifier)});
try self.compile_expression(environment, field_set.assign, null);
try self.compile_expression(environment, field_set.object, null);
try self.chunk.write(expression.line, .set_dynamic);
},
.subscript_get => |subscript_get| {
try self.compile_expression(environment, subscript_get.index, null);
try self.compile_expression(environment, subscript_get.object, null);
try self.chunk.write(expression.line, .get_dynamic);
},
.subscript_set => |subscript_set| {
try self.compile_expression(environment, subscript_set.index, null);
try self.compile_expression(environment, subscript_set.assign, null);
try self.compile_expression(environment, subscript_set.object, null);
try self.chunk.write(expression.line, .set_dynamic);
},
.external_get => |external_get| {
try self.chunk.write(expression.line, .{.push_const = try self.declare_symbol(external_get.name)});
try self.chunk.write(expression.line, .get_external);
},
}
}
pub fn compile_environment(self: *const Compiler, environment: *const tree.Environment) script.Error!void {
if (environment.statement) |statement| {
const last_statement = try self.compile_statement(environment, statement);
if (last_statement.kind != .@"return") {
try self.chunk.write(last_statement.line, .push_nil);
}
}
}
fn compile_statement(self: *const Compiler, environment: *const tree.Environment, initial_statement: *const tree.Stmt) script.Error!*const tree.Stmt {
var current_statement = initial_statement;
while (true) {
switch (current_statement.kind) {
.@"return" => |@"return"| {
if (@"return".returned_expression) |expression| {
try self.compile_expression(environment, expression, null);
} else {
try self.chunk.write(current_statement.line, .push_nil);
}
// TODO: Omit ret calls at ends of chunk.
try self.chunk.write(current_statement.line, .ret);
},
.@"while" => |@"while"| {
try self.compile_expression(environment, @"while".loop_expression, null);
try self.chunk.write(current_statement.line, .{.jf = 0});
const origin_index = @as(u16, @intCast(self.chunk.opcodes.values.len - 1));
_ = try self.compile_statement(environment, @"while".loop);
self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1);
try self.compile_expression(environment, @"while".loop_expression, null);
try self.chunk.write(current_statement.line, .{.jt = origin_index});
},
.@"if" => |@"if"| {
try self.compile_expression(environment, @"if".then_expression, null);
try self.chunk.write(current_statement.line, .{.jf = 0});
const origin_index = @as(u16, @intCast(self.chunk.opcodes.values.len - 1));
_ = try self.compile_statement(environment, @"if".@"then");
self.chunk.opcodes.values[origin_index].jf = @intCast(self.chunk.opcodes.values.len - 1);
if (@"if".@"else") |@"else"| {
_ = try self.compile_statement(environment, @"else");
}
},
.declare => |declare| {
try self.compile_expression(environment, declare.initial_expression, declare.declaration.identifier);
if (is_declaration_boxed(declare.declaration)) {
try self.chunk.write(current_statement.line, .push_boxed);
}
},
.top_expression => |top_expression| {
try self.compile_expression(environment, top_expression, null);
if (top_expression.kind == .invoke) {
try self.chunk.write(current_statement.line, .pop);
}
},
}
current_statement = current_statement.next orelse return current_statement;
}
}
const constants_max = @as(usize, std.math.maxInt(u16));
fn declare_chunk(self: *const Compiler, chunk: Self) script.Error!u16 {
if (self.chunk.constants.values.len == std.math.maxInt(u16)) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = @as(usize, std.math.maxInt(u16)),
});
}
const constant = (try self.env.new_dynamic(chunk)).pop().?;
errdefer self.env.release(constant);
try self.chunk.constants.push(constant);
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_fixed(self: *const Compiler, fixed: script.Fixed) script.Error!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = (try self.env.new_fixed(fixed)).pop().?;
errdefer self.env.release(constant);
try self.chunk.constants.push(constant);
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_float(self: *const Compiler, float: script.Float) script.Error!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = (try self.env.new_float(float)).pop().?;
errdefer self.env.release(constant);
try self.chunk.constants.push(constant);
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_string(self: *const Compiler, string: []const u8) script.Error!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = (try self.env.new_string(string)).pop().?;
errdefer self.env.release(constant);
try self.chunk.constants.push(constant);
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_vector2(self: *const Compiler, vector: script.Vector2) script.Error!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = (try self.env.new_vector2(vector)).pop().?;
errdefer self.env.release(constant);
try self.chunk.constants.push(constant);
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_vector3(self: *const Compiler, vector: script.Vector3) script.Error!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = (try self.env.new_vector3(vector)).pop().?;
errdefer self.env.release(constant);
try self.chunk.constants.push(constant);
return @intCast(self.chunk.constants.values.len - 1);
}
fn declare_symbol(self: *const Compiler, symbol: []const u8) script.Error!u16 {
if (self.chunk.constants.values.len == constants_max) {
return self.env.raise(error.BadSyntax, "chunks cannot contain more than {max} constants", .{
.max = constants_max,
});
}
const constant = (try self.env.new_symbol(symbol)).pop().?;
errdefer self.env.release(constant);
try self.chunk.constants.push(constant);
return @intCast(self.chunk.constants.values.len - 1);
}
fn get_binding_index(environment: *const tree.Environment, declaration: *const tree.Declaration) script.Error!?u8 {
var binding_index = @as(u8, 0);
while (binding_index < environment.capture_count) : (binding_index += 1) {
var capture = &environment.captures[binding_index];
var target_environment = environment.enclosing orelse return null;
while (capture.* == .capture_index) {
capture = &target_environment.captures[capture.capture_index];
target_environment = target_environment.enclosing orelse return null;
}
std.debug.assert(capture.* == .declaration_index);
if (&target_environment.declarations[capture.declaration_index] == declaration) {
return binding_index;
}
}
return null;
}
fn get_local_index(environment: *const tree.Environment, declaration: *const tree.Declaration) ?u8 {
var remaining = environment.declaration_count;
while (remaining != 0) {
remaining -= 1;
if (&environment.declarations[remaining] == declaration) {
return remaining;
}
}
return null;
}
fn is_declaration_boxed(declaration: *const tree.Declaration) bool {
return declaration.is.captured and !declaration.is.readonly;
}
};
pub const Opcode = union (enum) {
ret,
pop,
push_nil,
push_true,
push_false,
push_const: u16,
push_local: u8,
push_top,
push_vector2,
push_vector3,
push_table: u16,
push_binding: u8,
push_concat: u8,
push_boxed,
set_local: u8,
get_dynamic,
set_dynamic,
get_external,
get_box,
set_box,
call: u8,
bind: u8,
not,
neg,
add,
sub,
mul,
div,
eql,
cgt,
clt,
cge,
cle,
jt: u16,
jf: u16,
};
pub const External = struct {[]const u8, *script.Object};
const Self = @This();
pub fn deinit(self: *Self, env: *script.Runtime) void {
while (self.constants.pop()) |constant| {
env.release(constant.*);
}
self.constants.deinit();
self.opcodes.deinit();
self.lines.deinit();
env.release(self.name);
env.release(self.externals);
if (self.bindings.len != 0) {
for (self.bindings) |binding| {
if (binding) |value| {
env.release(value);
}
}
env.allocator.free(self.bindings);
}
self.bindings = &.{};
}
pub fn dump(chunk: Self, env: *script.Runtime) script.Error!*script.Object {
var opcode_cursor = @as(u32, 0);
var buffer = coral.list.ByteStack.init(env.allocator);
defer buffer.deinit();
const writer = coral.list.stack_as_writer(&buffer);
_ = coral.utf8.print_string(writer, "\n");
while (opcode_cursor < chunk.opcodes.values.len) : (opcode_cursor += 1) {
_ = coral.utf8.print_formatted(writer, "[{instruction}]: ", .{.instruction = opcode_cursor});
_ = switch (chunk.opcodes.values[opcode_cursor]) {
.ret => coral.utf8.print_string(writer, "ret\n"),
.pop => coral.utf8.print_string(writer, "pop\n"),
.push_nil => coral.utf8.print_string(writer, "push nil\n"),
.push_true => coral.utf8.print_string(writer, "push true\n"),
.push_false => coral.utf8.print_string(writer, "push false\n"),
.push_const => |push_const| print: {
const string_ref = (try (try env.push(try chunk.get_constant(env, push_const))).to_string()).pop().?;
defer env.release(string_ref);
const string = string_ref.is_string();
break: print coral.utf8.print_formatted(writer, "push const ({value})\n", .{.value = string.?});
},
.push_local => |push_local| coral.utf8.print_formatted(writer, "push local ({local})\n", .{
.local = push_local,
}),
.push_top => coral.utf8.print_string(writer, "push top\n"),
.push_table => |push_table| coral.utf8.print_formatted(writer, "push table ({count})\n", .{
.count = push_table,
}),
.push_boxed => coral.utf8.print_string(writer, "push boxed\n"),
.push_binding => |push_binding| coral.utf8.print_formatted(writer, "push binding ({binding})\n", .{
.binding = push_binding,
}),
.push_concat => |push_concat| coral.utf8.print_formatted(writer, "push concat ({count})\n", .{
.count = push_concat,
}),
.push_builtin => |push_builtin| coral.utf8.print_formatted(writer, "push builtin ({builtin})\n", .{
.builtin = switch (push_builtin) {
.import => "import",
.print => "print",
.vec2 => "vec2",
.vec3 => "vec3",
},
}),
.bind => |bind| coral.utf8.print_formatted(writer, "bind ({count})\n", .{
.count = bind,
}),
.set_local => |local_set| coral.utf8.print_formatted(writer, "set local ({local})\n", .{
.local = local_set,
}),
.get_box => coral.utf8.print_string(writer, "get box\n"),
.set_box => coral.utf8.print_string(writer, "set box\n"),
.get_dynamic => coral.utf8.print_string(writer, "get dynamic\n"),
.set_dynamic => coral.utf8.print_string(writer, "set dynamic\n"),
.call => |call| coral.utf8.print_formatted(writer, "call ({count})\n", .{.count = call}),
.not => coral.utf8.print_string(writer, "not\n"),
.neg => coral.utf8.print_string(writer, "neg\n"),
.add => coral.utf8.print_string(writer, "add\n"),
.sub => coral.utf8.print_string(writer, "sub\n"),
.mul => coral.utf8.print_string(writer, "mul\n"),
.div => coral.utf8.print_string(writer, "div\n"),
.eql => coral.utf8.print_string(writer, "eql\n"),
.cgt => coral.utf8.print_string(writer, "cgt\n"),
.clt => coral.utf8.print_string(writer, "clt\n"),
.cge => coral.utf8.print_string(writer, "cge\n"),
.cle => coral.utf8.print_string(writer, "cle\n"),
.jf => |jf| coral.utf8.print_formatted(writer, "jf ({instruction})\n", .{.instruction = jf}),
.jt => |jt| coral.utf8.print_formatted(writer, "jt ({instruction})\n", .{.instruction = jt}),
};
}
return (try env.new_string(buffer.values)).pop().?;
}
pub fn execute(self: *Self, env: *script.Runtime) script.Error!?*script.Object {
self.cursor = 0;
while (self.cursor < self.opcodes.values.len) : (self.cursor += 1) {
switch (self.opcodes.values[self.cursor]) {
.ret => break,
.pop => env.discard(),
.push_nil => _ = try env.push(null),
.push_true => _ = try env.new_boolean(true),
.push_false => _ = try env.new_boolean(false),
.push_const => |push_const| _ = try env.push(try self.get_constant(env, push_const)),
.push_local => |push_local| _ = try env.local_get(push_local),
.push_top => _ = try env.local_top(),
.push_vector2 => {
const y = try env.expect_float(try env.expect_object(env.pop()));
const x = try env.expect_float(try env.expect_object(env.pop()));
_ = try env.new_vector2(.{@floatCast(x), @floatCast(y)});
},
.push_vector3 => {
const z = try env.expect_float(try env.expect_object(env.pop()));
const y = try env.expect_float(try env.expect_object(env.pop()));
const x = try env.expect_float(try env.expect_object(env.pop()));
_ = try env.new_vector3(.{@floatCast(x), @floatCast(y), @floatCast(z)});
},
.push_table => |push_table| {
const table = (try env.new_table()).pop().?;
defer env.release(table);
var popped = @as(usize, 0);
while (popped < push_table) : (popped += 1) {
if (env.pop()) |object| {
defer env.release(object);
try env.index_set(table, object);
} else {
env.release(try env.expect_object(env.pop()));
}
}
_ = try env.push(table);
},
.push_boxed => {
const value = env.pop();
defer {
if (value) |object| {
env.release(object);
}
}
_ = try env.new_boxed(value);
},
.push_binding => |push_binding| _ = try env.push(try self.get_binding(env, push_binding)),
.push_concat => |push_concat| _ = try env.concat(push_concat),
.bind => |bind| {
const callable = try env.expect_object(env.pop());
defer env.release(callable);
const chunk = try env.expect_dynamic(callable, Self);
if (chunk.bindings.len != 0) {
return env.raise(error.IllegalState, "cannot bind values to an already-bound chunk", .{});
}
chunk.bindings = try env.allocator.alloc(?*script.Object, bind);
errdefer env.allocator.free(chunk.bindings);
for (0 .. bind) |index| {
const value = env.pop();
errdefer {
if (value) |object| {
env.release(object);
}
}
chunk.bindings[index] = value;
}
_ = try env.push(callable);
},
.set_local => |local_set| _ = try env.local_set(local_set, env.pop()),
.get_box => _ = try env.boxed_get(),
.set_box => {
const value = env.pop();
defer {
if (value) |object| {
env.release(object);
}
}
try env.boxed_set(value);
},
.get_dynamic => {
const indexable = try env.expect_object(env.pop());
defer env.release(indexable);
_ = try env.index_get(indexable);
},
.set_dynamic => {
const indexable = try env.expect_object(env.pop());
defer env.release(indexable);
const value = env.pop();
defer {
if (value) |object| {
env.release(object);
}
}
try env.index_set(indexable, value);
},
.get_external => _ = try env.index_get(self.externals),
.call => |call| _ = try env.call(call),
.not => {
const object = try env.expect_object(env.pop());
defer env.release(object);
_ = try env.new_boolean(object.is_false());
},
.neg => _ = try env.neg(),
.add => {
const addable = try env.expect_object(env.pop());
defer env.release(addable);
_ = switch (try env.expect_numeric(addable)) {
.fixed => |fixed| try env.fixed_add(fixed),
.float => |float| try env.float_add(float),
.vector2 => |vector2| try env.vector2_add(vector2),
.vector3 => |vector3| try env.vector3_add(vector3),
};
},
.sub => {
const subtractable = try env.expect_object(env.pop());
defer env.release(subtractable);
_ = switch (try env.expect_numeric(subtractable)) {
.fixed => |fixed| try env.fixed_subtract(fixed),
.float => |float| try env.float_subtract(float),
.vector2 => |vector2| try env.vector2_subtract(vector2),
.vector3 => |vector3| try env.vector3_subtract(vector3),
};
},
.mul => {
const multiplicable = try env.expect_object(env.pop());
defer env.release(multiplicable);
_ = switch (try env.expect_numeric(multiplicable)) {
.fixed => |fixed| try env.fixed_multiply(fixed),
.float => |float| try env.float_multiply(float),
.vector2 => |vector2| try env.vector2_multiply(vector2),
.vector3 => |vector3| try env.vector3_multiply(vector3),
};
},
.div => {
const divisible = try env.expect_object(env.pop());
defer env.release(divisible);
_ = switch (try env.expect_numeric(divisible)) {
.fixed => |fixed| try env.fixed_divide(fixed),
.float => |float| try env.float_divide(float),
.vector2 => |vector2| try env.vector2_divide(vector2),
.vector3 => |vector3| try env.vector3_divide(vector3),
};
},
.eql => {
if (env.pop()) |equatable| {
defer env.release(equatable);
_ = try env.equals_object(equatable);
} else {
_ = try env.equals_nil();
}
},
.cgt => {
const comparable = try env.expect_object(env.pop());
defer env.release(comparable);
_ = try env.compare_greater(comparable);
},
.clt => {
const comparable = try env.expect_object(env.pop());
defer env.release(comparable);
_ = try env.compare_less(comparable);
},
.cge => {
const comparable = try env.expect_object(env.pop());
defer env.release(comparable);
_ = try env.compare_greater_equals(comparable);
},
.cle => {
const comparable = try env.expect_object(env.pop());
defer env.release(comparable);
_ = try env.compare_less_equals(comparable);
},
.jf => |jf| {
if (env.pop()) |condition| {
defer env.release(condition);
if (condition.is_false()) {
self.cursor = jf;
}
} else {
self.cursor = jf;
}
},
.jt => |jt| {
if (env.pop()) |condition| {
defer env.release(condition);
if (condition.is_true()) {
self.cursor = jt;
}
}
},
}
}
return env.pop();
}
fn get_binding(self: *Self, env: *script.Runtime, index: usize) script.Error!?*script.Object {
if (index >= self.bindings.len) {
return env.raise(error.IllegalState, "invalid binding", .{});
}
return self.bindings[index];
}
fn get_constant(self: *const Self, env: *script.Runtime, index: usize) script.Error!*script.Object {
if (index >= self.constants.values.len) {
return env.raise(error.IllegalState, "invalid constant", .{});
}
return self.constants.values[index];
}
pub fn init(env: *script.Runtime, name: []const u8, environment: *const tree.Environment, externals: []const External) script.Error!Self {
const name_symbol = (try env.new_symbol(name)).pop().?;
errdefer env.release(name_symbol);
const externals_table = (try env.new_table()).pop().?;
errdefer env.release(externals_table);
for (0 .. externals.len) |i| {
const external_name, const external_object = externals[i];
try (try env.new_symbol(external_name)).index_set(externals_table, external_object);
}
var chunk = Self{
.externals = externals_table,
.name = name_symbol,
.opcodes = .{.allocator = env.allocator},
.constants = .{.allocator = env.allocator},
.lines = .{.allocator = env.allocator},
.bindings = &.{},
.arity = environment.argument_count,
.cursor = 0,
};
var compiler = Compiler{
.chunk = &chunk,
.env = env,
};
try compiler.compile_environment(environment);
return chunk;
}
pub const typeinfo = script.Typeinfo{
.name = "lambda",
.destruct = typeinfo_destruct,
.call = typeinfo_call,
.to_string = typeinfo_to_string,
};
fn typeinfo_call(context: script.Typeinfo.CallContext) script.Error!?*script.Object {
return @as(*Self, @ptrCast(@alignCast(context.userdata))).execute(context.env);
}
fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void {
@as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env);
}
fn typeinfo_to_string(context: script.Typeinfo.ToStringContext) script.Error!*script.Object {
return (try (try context.env.push(@as(*Self, @ptrCast(@alignCast(context.userdata))).name)).to_string()).pop().?;
}
pub fn write(self: *Self, line: tokens.Line, opcode: Opcode) std.mem.Allocator.Error!void {
try self.opcodes.push(opcode);
try self.lines.push(line);
}

View File

@ -1,135 +0,0 @@
const coral = @import("coral");
const script = @import("../script.zig");
associative: coral.map.Table(*script.Object, *script.Object, struct {
pub const hash = script.Object.hash;
pub const equals = script.Object.equals;
}),
contiguous: coral.Stack(?*script.Object),
const Self = @This();
pub fn deinit(self: *Self, env: *script.Runtime) void {
{
var entries = self.associative.entries();
while (entries.next()) |entry| {
env.release(entry.key);
env.release(entry.value);
}
}
self.associative.deinit();
while (self.contiguous.pop()) |value| {
if (value.*) |ref| {
env.release(ref);
}
}
self.contiguous.deinit();
}
pub fn init(env: *script.Runtime) Self {
return .{
.associative = .{
.allocator = env.allocator,
.traits = .{},
},
.contiguous = .{.allocator = env.allocator},
};
}
pub const typeinfo = script.Typeinfo{
.name = "table",
.destruct = typeinfo_destruct,
.get = typeinfo_get,
.set = typeinfo_set,
.count = typeinfo_count,
};
fn typeinfo_count(context: script.Typeinfo.CountContext) script.Error!script.Fixed {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata)));
return @intCast(table.associative.len + table.contiguous.values.len);
}
fn typeinfo_destruct(context: script.Typeinfo.DestructContext) void {
@as(*Self, @ptrCast(@alignCast(context.userdata))).deinit(context.env);
}
fn typeinfo_get(context: script.Typeinfo.GetContext) script.Error!?*script.Object {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata)));
const index = (try context.push_index()).pop().?;
defer context.env.release(index);
if (index.is_fixed()) |fixed| {
if (fixed < 0) {
// TODO: Negative indexing.
unreachable;
}
if (fixed < table.contiguous.values.len) {
return table.contiguous.values[@intCast(fixed)];
}
}
if (table.associative.lookup(index)) |value| {
return value;
}
return null;
}
fn typeinfo_set(context: script.Typeinfo.SetContext) script.Error!void {
const table = @as(*Self, @ptrCast(@alignCast(context.userdata)));
const index = (try context.push_index()).pop().?;
errdefer context.env.release(index);
if (index.is_fixed()) |fixed| {
if (fixed < 0) {
// TODO: Negative indexing.
unreachable;
}
if (fixed < table.contiguous.values.len) {
const maybe_replacing = &table.contiguous.values[@intCast(fixed)];
if (maybe_replacing.*) |replacing| {
context.env.release(replacing);
}
if ((try context.push_value()).pop()) |value| {
errdefer context.env.release(value);
maybe_replacing.* = value;
} else {
maybe_replacing.* = null;
}
return;
}
}
const value = (try context.push_value()).pop() orelse {
if (table.associative.remove(index)) |removed| {
context.env.release(removed.key);
context.env.release(removed.value);
}
return;
};
errdefer context.env.release(value);
if (try table.associative.replace(index, value)) |replaced| {
context.env.release(replaced.key);
context.env.release(replaced.value);
}
}

View File

@ -1,535 +0,0 @@
const coral = @import("coral");
const std = @import("std");
pub const Line = struct {
number: u32,
};
pub const Token = union(enum) {
end,
unknown: u8,
newline,
identifier: []const u8,
builtin: []const u8,
symbol_plus,
symbol_minus,
symbol_asterisk,
symbol_forward_slash,
symbol_paren_left,
symbol_paren_right,
symbol_bang,
symbol_comma,
symbol_at,
symbol_brace_left,
symbol_brace_right,
symbol_bracket_left,
symbol_bracket_right,
symbol_period,
symbol_colon,
symbol_less_than,
symbol_less_equals,
symbol_greater_than,
symbol_greater_equals,
symbol_equals,
symbol_double_equals,
number: []const u8,
string: []const u8,
template_string: []const u8,
keyword_nil,
keyword_false,
keyword_true,
keyword_return,
keyword_self,
keyword_const,
keyword_if,
keyword_do,
keyword_end,
keyword_while,
keyword_else,
keyword_elif,
keyword_var,
keyword_vec2,
keyword_vec3,
keyword_let,
keyword_lambda,
pub fn text(self: Token) []const u8 {
return switch (self) {
.end => "end",
.unknown => |unknown| @as([*]const u8, @ptrCast(&unknown))[0 .. 1],
.newline => "newline",
.identifier => |identifier| identifier,
.builtin => |identifier| identifier,
.symbol_plus => "+",
.symbol_minus => "-",
.symbol_asterisk => "*",
.symbol_forward_slash => "/",
.symbol_paren_left => "(",
.symbol_paren_right => ")",
.symbol_bang => "!",
.symbol_comma => ",",
.symbol_at => "@",
.symbol_brace_left => "{",
.symbol_brace_right => "}",
.symbol_bracket_left => "[",
.symbol_bracket_right => "]",
.symbol_period => ".",
.symbol_colon => ":",
.symbol_less_than => "<",
.symbol_less_equals => "<=",
.symbol_greater_than => ">",
.symbol_greater_equals => ">=",
.symbol_equals => "=",
.symbol_double_equals => "==",
.number => |literal| literal,
.string => |literal| literal,
.template_string => |literal| literal,
.keyword_const => "const",
.keyword_nil => "nil",
.keyword_false => "false",
.keyword_true => "true",
.keyword_return => "return",
.keyword_self => "self",
.keyword_if => "if",
.keyword_do => "do",
.keyword_end => "end",
.keyword_while => "while",
.keyword_elif => "elif",
.keyword_else => "else",
.keyword_var => "var",
.keyword_vec2 => "vec2",
.keyword_vec3 => "vec3",
.keyword_let => "let",
.keyword_lambda => "lambda",
};
}
};
pub const Stream = struct {
source: []const u8,
line: Line = .{.number = 1},
token: Token = .newline,
pub fn skip_newlines(self: *Stream) void {
self.step();
while (self.token == .newline) {
self.step();
}
}
pub fn step(self: *Stream) void {
var cursor = @as(usize, 0);
defer self.source = self.source[cursor ..];
while (cursor < self.source.len) {
switch (self.source[cursor]) {
'#' => {
cursor += 1;
while (cursor < self.source.len and self.source[cursor] != '\n') {
cursor += 1;
}
},
' ', '\t' => cursor += 1,
'\n' => {
cursor += 1;
self.token = .newline;
self.line.number += 1;
return;
},
'0' ... '9' => {
const begin = cursor;
cursor += 1;
while (cursor < self.source.len) switch (self.source[cursor]) {
'0' ... '9' => cursor += 1,
'.' => {
cursor += 1;
while (cursor < self.source.len) switch (self.source[cursor]) {
'0' ... '9' => cursor += 1,
else => break,
};
self.token = .{.number = self.source[begin .. cursor]};
return;
},
else => break,
};
self.token = .{.number = self.source[begin .. cursor]};
return;
},
'A' ... 'Z', 'a' ... 'z', '_' => {
const begin = cursor;
cursor += 1;
while (cursor < self.source.len) switch (self.source[cursor]) {
'0'...'9', 'A'...'Z', 'a'...'z', '_' => cursor += 1,
else => break,
};
const identifier = self.source[begin .. cursor];
std.debug.assert(identifier.len != 0);
switch (identifier[0]) {
'c' => {
if (coral.are_equal(identifier[1 ..], "onst")) {
self.token = .keyword_const;
return;
}
},
'd' => {
if (coral.are_equal(identifier[1 ..], "o")) {
self.token = .keyword_do;
return;
}
},
'e' => {
if (coral.are_equal(identifier[1 ..], "lse")) {
self.token = .keyword_else;
return;
}
if (coral.are_equal(identifier[1 ..], "lif")) {
self.token = .keyword_elif;
return;
}
if (coral.are_equal(identifier[1 ..], "nd")) {
self.token = .keyword_end;
return;
}
},
'f' => {
if (coral.are_equal(identifier[1 ..], "alse")) {
self.token = .keyword_false;
return;
}
},
'i' => {
if (coral.are_equal(identifier[1 ..], "f")) {
self.token = .keyword_if;
return;
}
},
'l' => {
if (coral.are_equal(identifier[1 ..], "ambda")) {
self.token = .keyword_lambda;
return;
}
if (coral.are_equal(identifier[1 ..], "et")) {
self.token = .keyword_let;
return;
}
},
'n' => {
if (coral.are_equal(identifier[1 ..], "il")) {
self.token = .keyword_nil;
return;
}
},
'r' => {
if (coral.are_equal(identifier[1 ..], "eturn")) {
self.token = .keyword_return;
return;
}
},
's' => {
if (coral.are_equal(identifier[1 ..], "elf")) {
self.token = .keyword_self;
return;
}
},
't' => {
if (coral.are_equal(identifier[1 ..], "rue")) {
self.token = .keyword_true;
return;
}
},
'v' => {
const rest = identifier[1 ..];
if (coral.are_equal(rest, "ar")) {
self.token = .keyword_var;
return;
}
if (coral.are_equal(rest, "vec2")) {
self.token = .keyword_vec2;
return;
}
if (coral.are_equal(rest, "vec3")) {
self.token = .keyword_vec3;
return;
}
},
'w' => {
if (coral.are_equal(identifier[1 ..], "hile")) {
self.token = .keyword_while;
return;
}
},
else => {},
}
self.token = .{.identifier = identifier};
return;
},
'`' => {
cursor += 1;
const begin = cursor;
while (cursor < self.source.len) switch (self.source[cursor]) {
'`' => break,
else => cursor += 1,
};
self.token = .{.template_string = self.source[begin .. cursor]};
cursor += 1;
return;
},
'"' => {
cursor += 1;
const begin = cursor;
while (cursor < self.source.len) switch (self.source[cursor]) {
'"' => break,
else => cursor += 1,
};
self.token = .{.string = self.source[begin .. cursor]};
cursor += 1;
return;
},
'{' => {
self.token = .symbol_brace_left;
cursor += 1;
return;
},
'}' => {
self.token = .symbol_brace_right;
cursor += 1;
return;
},
'[' => {
self.token = .symbol_bracket_left;
cursor += 1;
return;
},
']' => {
self.token = .symbol_bracket_right;
cursor += 1;
return;
},
',' => {
self.token = .symbol_comma;
cursor += 1;
return;
},
'!' => {
self.token = .symbol_bang;
cursor += 1;
return;
},
')' => {
self.token = .symbol_paren_right;
cursor += 1;
return;
},
'(' => {
self.token = .symbol_paren_left;
cursor += 1;
return;
},
'/' => {
self.token = .symbol_forward_slash;
cursor += 1;
return;
},
'*' => {
self.token = .symbol_asterisk;
cursor += 1;
return;
},
'-' => {
self.token = .symbol_minus;
cursor += 1;
return;
},
'+' => {
self.token = .symbol_plus;
cursor += 1;
return;
},
':' => {
self.token = .symbol_colon;
cursor += 1;
return;
},
'=' => {
cursor += 1;
if (cursor < self.source.len) {
switch (self.source[cursor]) {
'=' => {
cursor += 1;
self.token = .symbol_double_equals;
return;
},
else => {},
}
}
self.token = .symbol_equals;
return;
},
'<' => {
cursor += 1;
if (cursor < self.source.len and (self.source[cursor] == '=')) {
cursor += 1;
self.token = .symbol_less_equals;
return;
}
self.token = .symbol_less_than;
return;
},
'>' => {
cursor += 1;
if (cursor < self.source.len and (self.source[cursor] == '=')) {
cursor += 1;
self.token = .symbol_greater_equals;
return;
}
self.token = .symbol_greater_than;
return;
},
'.' => {
self.token = .symbol_period;
cursor += 1;
return;
},
'@' => {
self.token = .symbol_at;
cursor += 1;
return;
},
else => {
self.token = .{.unknown = self.source[cursor]};
cursor += 1;
return;
},
}
}
self.token = .end;
return;
}
};

View File

@ -1,268 +0,0 @@
pub const Expr = @import("./tree/Expr.zig");
pub const Stmt = @import("./tree/Stmt.zig");
const coral = @import("coral");
const std = @import("std");
const script = @import("../script.zig");
const tokens = @import("./tokens.zig");
pub const Declaration = struct {
identifier: []const coral.Byte,
is: packed struct {
readonly: bool = false,
captured: bool = false,
} = .{},
};
pub const Environment = struct {
captures: [capture_max]Capture = [_]Capture{.{.declaration_index = 0}} ** capture_max,
capture_count: u8 = 0,
declarations: [declaration_max]Declaration = [_]Declaration{.{.identifier = ""}} ** declaration_max,
declaration_count: u8 = 0,
argument_count: u8 = 0,
statement: ?*const Stmt = null,
enclosing: ?*Environment = null,
pub const Capture = union (enum) {
declaration_index: u8,
capture_index: u8,
};
pub const DeclareError = std.mem.Allocator.Error || error {
DeclarationExists,
};
const capture_max = std.math.maxInt(u8);
const declaration_max = std.math.maxInt(u8);
pub fn create_enclosed(self: *Environment, root: *Root) std.mem.Allocator.Error!*Environment {
const environment = try root.arena.allocator().create(Environment);
environment.* = .{.enclosing = self};
return environment;
}
fn declare(self: *Environment, declaration: Declaration) DeclareError!*const Declaration {
if (self.declaration_count == self.declarations.len) {
return error.OutOfMemory;
}
{
var environment = self;
while (true) {
var remaining_count = environment.declaration_count;
while (remaining_count != 0) {
remaining_count -= 1;
if (coral.are_equal(environment.declarations[remaining_count].identifier, declaration.identifier)) {
return error.DeclarationExists;
}
}
environment = environment.enclosing orelse break;
}
}
const declaration_slot = &self.declarations[self.declaration_count];
declaration_slot.* = declaration;
self.declaration_count += 1;
return declaration_slot;
}
pub fn declare_argument(self: *Environment, identifier: []const u8) DeclareError!*const Declaration {
std.debug.assert(self.declaration_count <= self.argument_count);
defer self.argument_count += 1;
return self.declare(.{
.identifier = identifier,
.is = .{.readonly = true},
});
}
pub fn declare_constant(self: *Environment, identifier: []const u8) DeclareError!*const Declaration {
return self.declare(.{
.identifier = identifier,
.is = .{.readonly = true},
});
}
pub fn declare_variable(self: *Environment, identifier: []const u8) DeclareError!*const Declaration {
return self.declare(.{.identifier = identifier});
}
pub fn resolve_declaration(self: *Environment, identifier: []const u8) std.mem.Allocator.Error!?*const Declaration {
var environment = self;
var ancestry = @as(u32, 0);
while (true) : (ancestry += 1) {
var remaining_count = environment.declaration_count;
while (remaining_count != 0) {
remaining_count -= 1;
const declaration = &environment.declarations[remaining_count];
if (coral.are_equal(declaration.identifier, identifier)) {
if (ancestry != 0) {
declaration.is.captured = true;
environment = self;
ancestry -= 1;
while (ancestry != 0) : (ancestry -= 1) {
if (environment.capture_count == environment.captures.len) {
return error.OutOfMemory;
}
environment.captures[environment.capture_count] = .{
.capture_index = environment.enclosing.?.capture_count
};
environment.capture_count += 1;
environment = environment.enclosing.?;
}
environment.captures[environment.capture_count] = .{.declaration_index = remaining_count};
environment.capture_count += 1;
}
return declaration;
}
}
environment = environment.enclosing orelse return null;
}
}
pub fn get_captures(self: *const Environment) []const Capture {
return self.captures[0 .. self.capture_count];
}
pub fn get_declarations(self: *const Environment) []const Declaration {
return self.declarations[0 .. self.declaration_count];
}
};
pub fn NodeChain(comptime Value: type) type {
return struct {
head: ?*Value = null,
tail: ?*Value = null,
pub const Nodes = struct {
current: ?*const Value,
pub fn next(self: *Nodes) ?*const Value {
const current = self.current orelse return null;
defer self.current = current.next;
return current;
}
};
const Self = @This();
pub fn append(self: *Self, value: *Value) void {
if (self.tail) |node| {
node.next = value;
self.tail = value;
} else {
self.tail = value;
self.head = value;
}
}
pub fn nodes(self: *const Self) Nodes {
return .{.current = self.head};
}
};
}
pub const ParseError = std.mem.Allocator.Error || error {
BadSyntax,
};
pub const Root = struct {
arena: std.heap.ArenaAllocator,
environment: Environment,
error_messages: MessageList,
const MessageList = coral.Stack([]coral.Byte);
pub fn report_error(self: *Root, line: tokens.Line, comptime format: []const u8, args: anytype) ParseError {
const allocator = self.arena.allocator();
const message = try coral.utf8.alloc_formatted(allocator, format, args);
defer allocator.free(message);
try self.error_messages.push(try coral.utf8.alloc_formatted(allocator, "{line_number}: {message}", .{
.message = message,
.line_number = line.number,
}));
return error.BadSyntax;
}
pub fn report_declare_error(self: *Root, line: tokens.Line, identifier: []const u8, @"error": Environment.DeclareError) ParseError {
return switch (@"error") {
error.OutOfMemory => error.OutOfMemory,
error.DeclarationExists => self.report_error(line, "declaration `{identifier}` already exists", .{
.identifier = identifier,
}),
};
}
pub fn create_node(self: *Root, node: anytype) std.mem.Allocator.Error!*@TypeOf(node) {
const copy = try self.arena.allocator().create(@TypeOf(node));
copy.* = node;
return copy;
}
pub fn create_string(self: *Root, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![]const u8 {
return coral.utf8.alloc_formatted(self.arena.allocator(), format, args);
}
pub fn deinit(self: *Root) void {
self.error_messages.deinit();
self.arena.deinit();
}
pub fn init(allocator: std.mem.Allocator) std.mem.Allocator.Error!Root {
return .{
.arena = std.heap.ArenaAllocator.init(allocator),
.error_messages = .{.allocator = allocator},
.environment = .{},
};
}
pub fn parse(self: *Root, stream: *tokens.Stream) ParseError!void {
stream.skip_newlines();
const first_statement = try Stmt.parse(self, stream, &self.environment);
var current_statement = first_statement;
while (stream.token != .end) {
const next_statement = try Stmt.parse(self, stream, &self.environment);
current_statement.next = next_statement;
current_statement = next_statement;
}
self.environment.statement = first_statement;
}
};

View File

@ -1,906 +0,0 @@
const Stmt = @import("./Stmt.zig");
const coral = @import("coral");
const tokens = @import("../tokens.zig");
const tree = @import("../tree.zig");
const std = @import("std");
next: ?*const Self = null,
line: tokens.Line,
kind: Kind,
pub const BinaryOp = struct {
rhs_operand: *Self,
lhs_operand: *Self,
operation: Operation,
pub const Operation = enum {
addition,
subtraction,
multiplication,
divsion,
equals_comparison,
greater_than_comparison,
greater_equals_comparison,
less_than_comparison,
less_equals_comparison,
};
fn parser(comptime parse_next: Parser, comptime operations: []const BinaryOp.Operation) Parser {
const BinaryOpParser = struct {
fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
var expression = try parse_next(root, stream, environment);
inline for (operations) |operation| {
const token = comptime @as(tokens.Token, switch (operation) {
.addition => .symbol_plus,
.subtraction => .symbol_minus,
.multiplication => .symbol_asterisk,
.divsion => .symbol_forward_slash,
.equals_comparison => .symbol_double_equals,
.greater_than_comparison => .symbol_greater_than,
.greater_equals_comparison => .symbol_greater_equals,
.less_than_comparison => .symbol_less_than,
.less_equals_comparison => .symbol_less_equals,
});
if (stream.token == std.meta.activeTag(token)) {
stream.step();
if (stream.token == .end) {
return root.report_error(stream.line, "expected other half of expression after `" ++ comptime token.text() ++ "`", .{});
}
// TODO: Remove once Zig has fixed struct self-reassignment.
const unnecessary_temp = expression;
expression = try root.create_node(Self{
.line = stream.line,
.kind = .{
.binary_op = .{
.rhs_operand = try parse_next(root, stream, environment),
.operation = operation,
.lhs_operand = unnecessary_temp,
},
},
});
}
}
return expression;
}
};
return BinaryOpParser.parse;
}
};
pub const DeclarationGet = struct {
declaration: *const tree.Declaration,
};
pub const DeclarationSet = struct {
declaration: *const tree.Declaration,
assign: *const Self,
};
pub const FieldGet = struct {
identifier: []const coral.Byte,
object: *const Self,
};
pub const FieldSet = struct {
identifier: []const coral.Byte,
object: *const Self,
assign: *const Self,
};
pub const Invoke = struct {
argument: ?*const Self,
object: *const Self,
};
const Kind = union (enum) {
nil_literal,
true_literal,
false_literal,
number_literal: []const u8,
string_literal: []const u8,
string_template,
symbol_literal: []const u8,
vector2: Vector2,
vector3: Vector3,
table: tree.NodeChain(TableEntry),
group: *Self,
lambda_construct: LambdaConstruct,
declaration_get: DeclarationGet,
declaration_set: DeclarationSet,
field_get: FieldGet,
field_set: FieldSet,
external_get: ExternalGet,
subscript_get: SubscriptGet,
subscript_set: SubscriptSet,
binary_op: BinaryOp,
unary_op: UnaryOp,
invoke: Invoke,
};
pub const LambdaConstruct = struct {
environment: *const tree.Environment,
};
const Parser = fn (root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self;
const ExternalGet = struct {
name: []const u8,
};
const Self = @This();
pub const SubscriptGet = struct {
index: *const Self,
object: *const Self,
};
pub const SubscriptSet = struct {
index: *const Self,
object: *const Self,
assign: *const Self,
};
pub const TableEntry = struct {
next: ?*const TableEntry = null,
key: *const Self,
value: *const Self,
};
const TemplateToken = union (enum) {
invalid: []const coral.Byte,
literal: []const coral.Byte,
expression: []const coral.Byte,
fn extract(source: *[]const coral.Byte) ?TemplateToken {
var cursor = @as(usize, 0);
defer source.* = source.*[cursor ..];
while (cursor < source.len) {
switch (source.*[cursor]) {
'{' => {
cursor += 1;
while (true) : (cursor += 1) {
if (cursor == source.len) {
return .{.invalid = source.*[0 .. cursor]};
}
if (source.*[cursor] == '}') {
const token = TemplateToken{.expression = source.*[1 .. cursor]};
cursor += 1;
return token;
}
}
},
else => {
cursor += 1;
while (true) : (cursor += 1) {
if (cursor == source.len) {
return .{.literal = source.*[0 .. cursor]};
}
if (source.*[cursor] == '{') {
const cursor_next = cursor + 1;
if (cursor_next == source.len) {
return .{.invalid = source.*[0 .. cursor]};
}
if (source.*[cursor_next] == '{') {
cursor = cursor_next;
return .{.literal = source.*[0 .. cursor]};
}
return .{.literal = source.*[0 .. cursor]};
}
}
}
}
}
return null;
}
};
pub const UnaryOp = struct {
operand: *Self,
operation: Operation,
pub const Operation = enum {
numeric_negation,
boolean_negation,
};
};
pub const Vector2 = struct {
x: *const Self,
y: *const Self,
};
pub const Vector3 = struct {
x: *const Self,
y: *const Self,
z: *const Self,
};
pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
const expression = try parse_additive(root, stream, environment);
if (stream.token == .symbol_equals) {
stream.skip_newlines();
if (stream.token == .end) {
return root.report_error(stream.line, "expected assignment after `=`", .{});
}
return root.create_node(Self{
.line = stream.line,
.kind = switch (expression.kind) {
.declaration_get => |declaration_get| convert: {
if (declaration_get.declaration.is.readonly) {
return root.report_error(stream.line, "readonly declarations cannot be re-assigned", .{});
}
break: convert .{
.declaration_set = .{
.assign = try parse(root, stream, environment),
.declaration = declaration_get.declaration,
},
};
},
.field_get => |field_get| .{
.field_set = .{
.assign = try parse(root, stream, environment),
.object = field_get.object,
.identifier = field_get.identifier,
},
},
.subscript_get => |subscript_get| .{
.subscript_set = .{
.assign = try parse(root, stream, environment),
.object = subscript_get.object,
.index = subscript_get.index,
},
},
else => return root.report_error(stream.line, "expected local or field on left-hand side of expression", .{}),
},
});
}
return expression;
}
const parse_additive = BinaryOp.parser(parse_equality, &.{
.addition,
.subtraction,
});
const parse_comparison = BinaryOp.parser(parse_term, &.{
.greater_than_comparison,
.greater_equals_comparison,
.less_than_comparison,
.less_equals_comparison
});
const parse_equality = BinaryOp.parser(parse_comparison, &.{
.equals_comparison,
});
fn parse_factor(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
var expression = try parse_operand(root, stream, environment);
while (true) {
switch (stream.token) {
.symbol_period => {
stream.skip_newlines();
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
const unnecessary_temp = expression;
expression = try root.create_node(Self{
.line = stream.line,
.kind = .{
.field_get = .{
.identifier = switch (stream.token) {
.identifier => |field_identifier| field_identifier,
else => return root.report_error(stream.line, "expected identifier after `.`", .{}),
},
.object = unnecessary_temp,
},
},
});
stream.skip_newlines();
},
.symbol_bracket_left => {
stream.skip_newlines();
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
const unnecessary_temp = expression;
expression = try root.create_node(Self{
.line = stream.line,
.kind = .{
.subscript_get = .{
.index = try parse(root, stream, environment),
.object = unnecessary_temp,
},
},
});
if (stream.token != .symbol_bracket_right) {
return root.report_error(stream.line, "expected closing `]` on subscript", .{});
}
stream.skip_newlines();
},
.symbol_paren_left => {
const lines_stepped = stream.line;
stream.skip_newlines();
var first_argument = @as(?*Self, null);
if (stream.token != .symbol_paren_right) {
var argument = try parse(root, stream, environment);
first_argument = argument;
while (true) {
switch (stream.token) {
.symbol_comma => stream.skip_newlines(),
.symbol_paren_right => break,
else => return root.report_error(stream.line, "expected `,` or `)` after lambda argument", .{}),
}
const next_argument = try parse(root, stream, environment);
argument.next = next_argument;
argument = next_argument;
}
}
stream.skip_newlines();
// TODO: Remove when Zig fixes miscompilation with in-place struct re-assignment.
const unnecessary_temp = expression;
expression = try root.create_node(Self{
.line = lines_stepped,
.kind = .{
.invoke = .{
.argument = first_argument,
.object = unnecessary_temp,
},
},
});
},
else => break,
}
}
return expression;
}
fn parse_operand(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
switch (stream.token) {
.symbol_paren_left => {
stream.skip_newlines();
const expression = try parse(root, stream, environment);
if (stream.token != .symbol_paren_right) {
return root.report_error(stream.line, "expected a closing `)` after expression", .{});
}
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{.group = expression},
});
},
.keyword_nil => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .nil_literal,
});
},
.keyword_true => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .true_literal,
});
},
.keyword_false => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .false_literal,
});
},
.keyword_vec2 => {
stream.skip_newlines();
if (stream.token != .symbol_paren_left) {
return root.report_error(stream.line, "expected an opening `(` after `vec2`", .{});
}
stream.skip_newlines();
const x_expression = try parse(root, stream, environment);
switch (stream.token) {
.symbol_paren_right => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{
.vector2 = .{
.x = x_expression,
.y = x_expression,
},
},
});
},
.symbol_comma => {
stream.skip_newlines();
const y_expression = try parse(root, stream, environment);
stream.skip_newlines();
if (stream.token != .symbol_paren_right) {
return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{});
}
return root.create_node(Self{
.line = stream.line,
.kind = .{
.vector2 = .{
.x = x_expression,
.y = y_expression,
},
},
});
},
else => return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}),
}
},
.keyword_vec3 => {
stream.skip_newlines();
if (stream.token != .symbol_paren_left) {
return root.report_error(stream.line, "expected an opening `(` after `vec2`", .{});
}
stream.skip_newlines();
const x_expression = try parse(root, stream, environment);
switch (stream.token) {
.symbol_paren_right => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{
.vector3 = .{
.x = x_expression,
.y = x_expression,
.z = x_expression,
},
},
});
},
.symbol_comma => {
stream.skip_newlines();
const y_expression = try parse(root, stream, environment);
stream.skip_newlines();
const z_expression = try parse(root, stream, environment);
stream.skip_newlines();
if (stream.token != .symbol_paren_right) {
return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{});
}
return root.create_node(Self{
.line = stream.line,
.kind = .{
.vector3 = .{
.x = x_expression,
.y = y_expression,
.z = z_expression,
},
},
});
},
else => return root.report_error(stream.line, "expected a closing `)` after `vec3`", .{}),
}
},
.number => |value| {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{.number_literal = value},
});
},
.string => |value| {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{.string_literal = value},
});
},
.template_string => |value| {
const line = stream.line;
stream.skip_newlines();
return parse_template(root, value, line, environment);
},
.symbol_at => {
stream.step();
const identifier = switch (stream.token) {
.identifier => |identifier| identifier,
else => return root.report_error(stream.line, "expected identifier after `@`", .{}),
};
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{.external_get = .{.name = identifier}},
});
},
.symbol_period => {
stream.step();
const identifier = switch (stream.token) {
.identifier => |identifier| identifier,
else => return root.report_error(stream.line, "expected identifier after `.`", .{}),
};
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{.symbol_literal = identifier},
});
},
.identifier => |identifier| {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{
.declaration_get = .{
.declaration = (try environment.resolve_declaration(identifier)) orelse {
return root.report_error(stream.line, "undefined identifier `{identifier}`", .{
.identifier = identifier,
});
}
},
},
});
},
.keyword_lambda => {
stream.skip_newlines();
if (stream.token != .symbol_paren_left) {
return root.report_error(stream.line, "expected `(` after opening lambda block", .{});
}
stream.skip_newlines();
var lambda_environment = try environment.create_enclosed(root);
while (stream.token != .symbol_paren_right) {
const identifier = switch (stream.token) {
.identifier => |identifier| identifier,
else => return root.report_error(stream.line, "expected identifier", .{}),
};
_ = lambda_environment.declare_argument(identifier) catch |declare_error| {
return root.report_declare_error(stream.line, identifier, declare_error);
};
stream.skip_newlines();
switch (stream.token) {
.symbol_comma => stream.skip_newlines(),
.symbol_paren_right => break,
else => return root.report_error(stream.line, "expected `,` or `)` after identifier", .{}),
}
}
stream.skip_newlines();
if (stream.token != .symbol_colon) {
return root.report_error(stream.line, "expected `:` after closing `)` of lambda identifiers", .{});
}
stream.skip_newlines();
if (stream.token != .keyword_end) {
const first_statement = try Stmt.parse(root, stream, lambda_environment);
var current_statement = first_statement;
while (stream.token != .keyword_end) {
const next_statement = try Stmt.parse(root, stream, lambda_environment);
current_statement.next = next_statement;
current_statement = next_statement;
}
lambda_environment.statement = first_statement;
}
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{.lambda_construct = .{.environment = lambda_environment}},
});
},
.symbol_brace_left => {
stream.skip_newlines();
return parse_table(root, stream, environment);
},
.symbol_minus => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{
.unary_op = .{
.operand = try parse_factor(root, stream, environment),
.operation = .numeric_negation,
},
},
});
},
.symbol_bang => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{
.unary_op = .{
.operand = try parse_factor(root, stream, environment),
.operation = .boolean_negation,
},
},
});
},
else => return root.report_error(stream.line, "unexpected token in expression", .{}),
}
}
fn parse_table(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
var entries = tree.NodeChain(TableEntry){};
var sequential_index = @as(usize, 0);
while (true) {
switch (stream.token) {
.symbol_brace_right => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{.table = entries},
});
},
.symbol_bracket_left => {
stream.skip_newlines();
const key = try parse(root, stream, environment);
if (stream.token != .symbol_bracket_right) {
return root.report_error(stream.line, "expected `]` after subscript index expression", .{});
}
stream.skip_newlines();
if (stream.token != .symbol_equals) {
return root.report_error(stream.line, "expected `=` after table expression key", .{});
}
stream.skip_newlines();
entries.append(try root.create_node(TableEntry{
.value = try parse(root, stream, environment),
.key = key,
}));
},
.symbol_period => {
stream.step();
const field = switch (stream.token) {
.identifier => |identifier| identifier,
else => return root.report_error(stream.line, "invalid symbol literal", .{}),
};
stream.skip_newlines();
switch (stream.token) {
.symbol_comma => {
stream.skip_newlines();
entries.append(try root.create_node(TableEntry{
.key = try root.create_node(Self{
.line = stream.line,
.kind = .{.number_literal = try root.create_string("{i}", .{.i = sequential_index})},
}),
.value = try root.create_node(Self{
.line = stream.line,
.kind = .{.symbol_literal = field},
}),
}));
sequential_index += 1;
},
.symbol_equals => {
stream.skip_newlines();
entries.append(try root.create_node(TableEntry{
.value = try parse(root, stream, environment),
.key = try root.create_node(Self{
.line = stream.line,
.kind = .{.symbol_literal = field},
}),
}));
},
else => return root.report_error(stream.line, "expected `,` or `=` after symbol", .{}),
}
},
else => {
entries.append(try root.create_node(TableEntry{
.value = try parse(root, stream, environment),
.key = try root.create_node(Self{
.line = stream.line,
.kind = .{.number_literal = try root.create_string("{i}", .{.i = sequential_index})},
}),
}));
sequential_index += 1;
},
}
switch (stream.token) {
.symbol_brace_right => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{.table = entries},
});
},
.symbol_comma => stream.skip_newlines(),
else => return root.report_error(stream.line, "expected `,` or '}' after table key value pair", .{}),
}
}
}
fn parse_template(root: *tree.Root, template: []const coral.Byte, line: tokens.Line, environment: *tree.Environment) tree.ParseError!*Self {
const expression_head = try root.create_node(Self{
.line = line,
.kind = .string_template,
});
var expression_tail = expression_head;
var source = template;
while (TemplateToken.extract(&source)) |token| {
const expression = try switch (token) {
.invalid => |invalid| root.report_error(line, "invalid template format: `{invalid}`", .{
.invalid = invalid,
}),
.literal => |literal| root.create_node(Self{
.line = line,
.kind = .{.string_literal = literal},
}),
.expression => |expression| create: {
var stream = tokens.Stream{
.source = expression,
.line = line,
};
stream.step();
break: create try parse(root, &stream, environment);
},
};
expression_tail.next = expression;
expression_tail = expression;
}
return expression_head;
}
const parse_term = BinaryOp.parser(parse_factor, &.{
.multiplication,
.divsion,
});

View File

@ -1,242 +0,0 @@
const Expr = @import("./Expr.zig");
const coral = @import("coral");
const tokens = @import("../tokens.zig");
const tree = @import("../tree.zig");
next: ?*const Self = null,
line: tokens.Line,
kind: union (enum) {
top_expression: *const Expr,
@"return": Return,
declare: Declare,
@"if": If,
@"while": While,
},
pub const Declare = struct {
declaration: *const tree.Declaration,
initial_expression: *const Expr,
};
pub const If = struct {
then_expression: *const Expr,
@"then": *const Self,
@"else": ?*const Self,
};
pub const Return = struct {
returned_expression: ?*const Expr,
};
const Self = @This();
pub const While = struct {
loop_expression: *const Expr,
loop: *const Self,
};
pub fn parse(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
switch (stream.token) {
.keyword_return => {
stream.step();
if (stream.token != .end and stream.token != .newline) {
return root.create_node(Self{
.line = stream.line,
.kind = .{.@"return" = .{.returned_expression = try Expr.parse(root, stream, environment)}},
});
}
if (stream.token != .end and stream.token != .newline) {
return root.report_error(stream.line, "expected end or newline after return statement", .{});
}
return root.create_node(Self{
.line = stream.line,
.kind = .{.@"return" = .{.returned_expression = null}},
});
},
.keyword_while => {
defer stream.skip_newlines();
stream.step();
const condition_expression = try Expr.parse(root, stream, environment);
if (stream.token != .symbol_colon) {
return root.report_error(stream.line, "expected `:` after `while` statement", .{});
}
stream.skip_newlines();
const first_statement = try parse(root, stream, environment);
{
var current_statement = first_statement;
while (stream.token != .keyword_end) {
const next_statement = try parse(root, stream, environment);
current_statement.next = next_statement;
current_statement = next_statement;
}
}
return root.create_node(Self{
.line = stream.line,
.kind = .{
.@"while" = .{
.loop = first_statement,
.loop_expression = condition_expression,
},
},
});
},
.keyword_var, .keyword_let => {
const is_constant = stream.token == .keyword_let;
stream.skip_newlines();
const identifier = switch (stream.token) {
.identifier => |identifier| identifier,
else => return root.report_error(stream.line, "expected identifier after declaration", .{}),
};
stream.skip_newlines();
if (stream.token != .symbol_equals) {
return root.report_error(stream.line, "expected `=` after declaration `{identifier}`", .{
.identifier = identifier,
});
}
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{
.declare = .{
.initial_expression = try Expr.parse(root, stream, environment),
.declaration = declare: {
if (is_constant) {
break: declare environment.declare_constant(identifier) catch |declaration_error| {
return root.report_declare_error(stream.line, identifier, declaration_error);
};
}
break: declare environment.declare_variable(identifier) catch |declaration_error| {
return root.report_declare_error(stream.line, identifier, declaration_error);
};
},
},
},
});
},
.keyword_if => return parse_branch(root, stream, environment),
else => return root.create_node(Self{
.line = stream.line,
.kind = .{.top_expression = try Expr.parse(root, stream, environment)},
}),
}
}
fn parse_branch(root: *tree.Root, stream: *tokens.Stream, environment: *tree.Environment) tree.ParseError!*Self {
stream.step();
const expression = try Expr.parse(root, stream, environment);
if (stream.token != .symbol_colon) {
return root.report_error(stream.line, "expected `:` after `{token}`", .{.token = stream.token.text()});
}
stream.skip_newlines();
const first_then_statement = try parse(root, stream, environment);
var current_then_statement = first_then_statement;
while (true) {
switch (stream.token) {
.keyword_end => {
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{
.@"if" = .{
.then_expression = expression,
.@"then" = first_then_statement,
.@"else" = null,
},
},
});
},
.keyword_else => {
stream.step();
if (stream.token != .symbol_colon) {
return root.report_error(stream.line, "expected `:` after `if` statement condition", .{});
}
stream.skip_newlines();
const first_else_statement = try parse(root, stream, environment);
var current_else_statement = first_else_statement;
while (stream.token != .keyword_end) {
const next_statement = try parse(root, stream, environment);
current_else_statement.next = next_statement;
current_else_statement = next_statement;
}
stream.skip_newlines();
return root.create_node(Self{
.line = stream.line,
.kind = .{
.@"if" = .{
.@"else" = first_else_statement,
.@"then" = first_then_statement,
.then_expression = expression,
},
}
});
},
.keyword_elif => {
return root.create_node(Self{
.line = stream.line,
.kind = .{
.@"if" = .{
.@"else" = try parse_branch(root, stream, environment),
.@"then" = first_then_statement,
.then_expression = expression,
},
},
});
},
else => {
const next_statement = try parse(root, stream, environment);
current_then_statement.next = next_statement;
current_then_statement = next_statement;
},
}
}
}

View File

@ -115,24 +115,6 @@ pub fn get_ptr(slice: anytype, index: usize) ?ElementPtr(@TypeOf(slice)) {
return &slice[index];
}
pub fn index_of(haystack: anytype, offset: usize, needle: std.meta.Child(@TypeOf(haystack))) ?usize {
for (offset .. haystack.len) |i| {
if (haystack[i] == needle) {
return i;
}
}
return null;
}
pub fn index_of_any(haystack: anytype, offset: usize, needle: []const std.meta.Child(@TypeOf(haystack))) ?usize {
return std.mem.indexOfAnyPos(std.meta.Child(@TypeOf(haystack)), haystack, offset, needle);
}
pub fn index_of_seq(haystack: anytype, offset: usize, needle: []const std.meta.Child(@TypeOf(haystack))) ?usize {
return std.mem.indexOfPos(std.meta.Child(@TypeOf(haystack)), haystack, offset, needle);
}
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);

View File

@ -1,23 +0,0 @@
const slices = @import("./slices.zig");
const std = @import("std");
pub fn Parallel(comptime Value: type) type {
const Slices = slices.Parallel(Value);
const alignment = @alignOf(Value);
return struct {
allocator: std.mem.Allocator,
slices: slices.Parallel(Value) = .{},
const Self = @This();
pub fn len(self: Self) usize {
return self.slices.len;
}
pub fn values(self: *Self, comptime field: Slices.Field) []align (alignment) Slices.Element(field) {
return self.slices.slice(field);
}
};
}

View File

@ -6,28 +6,64 @@ const io = @import("./io.zig");
const std = @import("std");
pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![]coral.Byte {
var buffer = coral.Stack(coral.Byte){.allocator = allocator};
pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8, args: anytype) std.mem.Allocator.Error![:0]u8 {
const formatted_len = count_formatted(format, args);
const buffer = try allocator.allocSentinel(u8, formatted_len, 0);
try buffer.grow(formatted_len);
errdefer allocator.free(buffer);
errdefer buffer.deinit();
print_formatted(buffer.writer(), format, args) catch unreachable;
return buffer.to_allocation(formatted_len, 0);
// TODO: This is dumb.
return @constCast(print_formatted(buffer, format, args) catch unreachable);
}
fn count_formatted(comptime format: []const u8, args: anytype) usize {
var count = io.defaultWritable{};
pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
var count = io.NullWritable{};
print_formatted(count.writer(), format, args) catch unreachable;
write_formatted(count.writer(), format, args) catch unreachable;
return count.written;
}
pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.PrintError!void {
pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]const u8 {
const Seekable = struct {
buffer: []coral.io.Byte,
cursor: usize,
const Self = @This();
fn write(self: *Self, input: []const coral.io.Byte) io.Error!usize {
const range = @min(input.len, self.buffer.len - self.cursor);
const tail = self.cursor + range;
@memcpy(self.buffer[self.cursor .. tail], input);
self.cursor = tail;
return range;
}
};
const len = count_formatted(format, args);
if (len > buffer.len) {
return error.UnavailableResource;
}
var seekable = Seekable{
.buffer = buffer,
.cursor = 0,
};
try write_formatted(coral.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args);
if (buffer.len < len) {
buffer[len] = 0;
}
return buffer[0 .. len:0];
}
pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.Error!void {
switch (@typeInfo(@TypeOf(args))) {
.Struct => |arguments_struct| {
comptime var arg_index = 0;
@ -67,7 +103,7 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: any
@compileError("format specifiers cannot be named when using a tuple struct");
}
try io.print(writer, format[head .. (tail - 1)]);
try io.write_all(writer, format[head .. (tail - 1)]);
head = tail;
tail += 1;
@ -93,25 +129,25 @@ pub fn print_formatted(writer: io.Writer, comptime format: []const u8, args: any
}
}
try io.print(writer, format[head .. ]);
try 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.PrintError!void {
noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void {
const Value = @TypeOf(value);
return switch (@typeInfo(Value)) {
.Int => ascii.DecimalFormat.default.print(writer, value),
.Float => ascii.DecimalFormat.default.print(writer, value),
.Int => ascii.DecimalFormat.default.format(writer, value),
.Float => ascii.DecimalFormat.default.format(writer, value),
.Enum => io.print(writer, @tagName(value)),
.Pointer => |pointer| switch (pointer.size) {
.Many, .C => ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)),
.One => if (pointer.child == []const u8) io.print(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)),
.Slice => if (pointer.child == u8) io.print(writer, value) else @compileError(unformattableMessage(Value)),
.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)),
},
else => @compileError(unformattableMessage(Value)),

View File

@ -1,34 +1,27 @@
const builtin = @import("builtin");
const heap = @import("./heap.zig");
const flow = @import("./flow.zig");
const map = @import("./map.zig");
const coral = @import("coral");
const resource = @import("./resource.zig");
const states = @import("./states.zig");
const std = @import("std");
const stack = @import("./stack.zig");
const system = @import("./system.zig");
thread_pool: ?*std.Thread.Pool = null,
thread_restricted_resources: [std.enums.values(ThreadRestriction).len]resource.Table,
event_systems: stack.Sequential(system.Schedule),
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 const ThreadRestriction = enum {
none,
main,
};
pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event {
var systems = try system.Schedule.init(label);
errdefer systems.deinit();
errdefer systems.deinit(self);
const index = self.event_systems.len();
@ -38,17 +31,17 @@ pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Even
}
pub fn deinit(self: *Self) void {
for (self.event_systems.values) |*schedule| {
schedule.deinit(self);
}
for (&self.thread_restricted_resources) |*resources| {
resources.deinit();
}
for (self.event_systems.values) |*schedule| {
schedule.deinit();
}
if (self.thread_pool) |thread_pool| {
thread_pool.deinit();
heap.allocator.destroy(thread_pool);
coral.heap.allocator.destroy(thread_pool);
}
self.event_systems.deinit();
@ -56,25 +49,33 @@ pub fn deinit(self: *Self) void {
self.* = undefined;
}
pub fn get_resource(self: Self, thread_restriction: ThreadRestriction, comptime Resource: type) ?*Resource {
return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(thread_restriction)].get(Resource)));
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 set_get_resource(self: *Self, thread_restriction: ThreadRestriction, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
return self.thread_restricted_resources[@intFromEnum(thread_restriction)].set_get(value);
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 = .{resource.Table.init(), resource.Table.init()},
.event_systems = .{.allocator = heap.allocator},
.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 heap.allocator.create(std.Thread.Pool);
const thread_pool = try coral.heap.allocator.create(std.Thread.Pool);
try thread_pool.init(.{
.allocator = heap.allocator,
.allocator = coral.heap.allocator,
.n_jobs = thread_count,
});
@ -92,6 +93,6 @@ pub fn run_event(self: *Self, event: Event) anyerror!void {
try self.event_systems.values[@intFromEnum(event)].run(self);
}
pub fn set_resource(self: *Self, thread_restriction: ThreadRestriction, value: anytype) std.mem.Allocator.Error!void {
try self.thread_restricted_resources[@intFromEnum(thread_restriction)].set(value);
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,6 +1,4 @@
const stack = @import("./stack.zig");
const slices = @import("./slices.zig");
const coral = @import("coral");
const std = @import("std");
@ -9,9 +7,9 @@ pub fn Graph(comptime Payload: type) type {
node_count: usize = 0,
table: NodeTables,
const NodeTables = stack.Parallel(struct {
const NodeTables = coral.stack.Parallel(struct {
payload: Payload,
edges: stack.Sequential(Node),
edges: coral.stack.Sequential(Node),
is_occupied: bool = true,
is_visited: bool = false,
});
@ -82,7 +80,7 @@ pub fn Graph(comptime Payload: type) type {
return false;
};
if (slices.index_of(edges.values, 0, edge_node) == null) {
if (std.mem.indexOfScalar(Node, edges.values, edge_node) == null) {
try edges.push_grow(edge_node);
}

329
src/flow/flow.zig Normal file
View File

@ -0,0 +1,329 @@
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,12 +1,10 @@
const coral = @import("coral");
const std = @import("std");
const heap = @import("./heap.zig");
const map = @import("./map.zig");
pub const Table = struct {
arena: std.heap.ArenaAllocator,
table: map.Hashed(TypeID, Entry, map.enum_traits(TypeID)),
table: coral.map.Hashed(TypeID, Entry, coral.map.enum_traits(TypeID)),
const Entry = struct {
ptr: *anyopaque,
@ -20,7 +18,7 @@ pub const Table = struct {
}
pub fn get(self: Table, comptime Resource: type) ?*Resource {
if (self.table.get_ptr(type_id(Resource))) |entry| {
if (self.table.get(type_id(Resource))) |entry| {
return @ptrCast(@alignCast(entry.ptr));
}
@ -29,8 +27,8 @@ pub const Table = struct {
pub fn init() Table {
return .{
.arena = std.heap.ArenaAllocator.init(heap.allocator),
.table = .{.allocator = heap.allocator},
.arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
.table = .{.allocator = coral.heap.allocator},
};
}
@ -44,7 +42,7 @@ pub const Table = struct {
const Value = @TypeOf(value);
const value_id = type_id(Value);
if (self.table.get_ptr(value_id)) |entry| {
if (self.table.get(value_id)) |entry| {
@as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value;
} else {
const resource_allocator = self.arena.allocator();
@ -61,6 +59,11 @@ pub const Table = struct {
}
};
pub const ThreadRestriction = enum {
none,
main,
};
pub const TypeID = enum (usize) { _ };
pub fn type_id(comptime T: type) TypeID {
@ -74,3 +77,11 @@ pub fn type_id(comptime T: type) TypeID {
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,14 +1,8 @@
const coral = @import("coral");
const dag = @import("./dag.zig");
const heap = @import("./heap.zig");
const map = @import("./map.zig");
const resource = @import("./resource.zig");
const slices = @import("./slices.zig");
const stack = @import("./stack.zig");
const states = @import("./states.zig");
const std = @import("std");
@ -21,7 +15,7 @@ pub const BindContext = struct {
pub const ResourceAccess = std.meta.Tag(Schedule.ResourceAccess);
pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: resource.TypeID) bool {
pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: states.TypeID) bool {
const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses;
for (resource_accesses.values) |resource_access| {
@ -43,56 +37,56 @@ pub const BindContext = struct {
return false;
}
pub fn register_read_write_resource_access(self: BindContext, thread_restriction: World.ThreadRestriction, comptime Resource: type) std.mem.Allocator.Error!?*Resource {
const value = self.world.get_resource(thread_restriction, Resource) orelse {
pub fn register_writable_state_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*Resource {
const value = self.world.get_state(Resource) orelse {
return null;
};
const id = resource.type_id(Resource);
const id = states.type_id(Resource);
if (!self.accesses_resource(.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_ptr(id) orelse insert: {
break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: {
std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
.allocator = heap.allocator,
.allocator = coral.heap.allocator,
}));
break: insert self.systems.read_write_resource_id_nodes.get_ptr(id).?;
break: insert self.systems.read_write_resource_id_nodes.get(id).?;
};
};
if (slices.index_of(read_write_resource_nodes.values, 0, self.node) == null) {
if (std.mem.indexOfScalar(dag.Node, read_write_resource_nodes.values, self.node) == null) {
try read_write_resource_nodes.push_grow(self.node);
}
return value;
}
pub fn register_read_only_resource_access(self: BindContext, thread_restriction: World.ThreadRestriction, comptime Resource: type) std.mem.Allocator.Error!?*const Resource {
const value = self.world.get_resource(thread_restriction, Resource) orelse {
pub fn register_readable_state_access(self: BindContext, comptime Resource: type) std.mem.Allocator.Error!?*const Resource {
const value = self.world.get_state(Resource) orelse {
return null;
};
const id = resource.type_id(Resource);
const id = states.type_id(Resource);
if (!self.accesses_resource(.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_ptr(id) orelse insert: {
break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: {
std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
.allocator = heap.allocator,
.allocator = coral.heap.allocator,
}));
break: insert self.systems.read_only_resource_id_nodes.get_ptr(id).?;
break: insert self.systems.read_only_resource_id_nodes.get(id).?;
};
};
if (slices.index_of(read_only_resource_nodes.values, 0, self.node) == null) {
if (std.mem.indexOfScalar(dag.Node, read_only_resource_nodes.values, self.node) == null) {
try read_only_resource_nodes.push_grow(self.node);
}
@ -101,16 +95,16 @@ pub const BindContext = struct {
};
pub const Info = struct {
execute: *const fn ([]const *const Parameter, *const [max_parameters]?*anyopaque) anyerror!void,
execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void,
parameters: [max_parameters]*const Parameter = undefined,
parameter_count: u4 = 0,
thread_restriction: World.ThreadRestriction = .none,
thread_restriction: states.ThreadRestriction = .none,
pub const Parameter = struct {
thread_restriction: World.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) void,
thread_restriction: states.ThreadRestriction,
init: *const fn (*anyopaque, *anyopaque) void,
bind: *const fn (std.mem.Allocator, BindContext) std.mem.Allocator.Error!*anyopaque,
unbind: *const fn (std.mem.Allocator, *anyopaque, UnbindContext) void,
};
pub fn used_parameters(self: *const Info) []const *const Parameter {
@ -128,7 +122,7 @@ pub const Schedule = struct {
label: [:0]const u8,
graph: Graph,
arena: std.heap.ArenaAllocator,
system_id_nodes: map.Hashed(usize, NodeBundle, map.usize_traits),
system_id_nodes: coral.map.Hashed(usize, NodeBundle, coral.map.usize_traits),
read_write_resource_id_nodes: ResourceNodeBundle,
read_only_resource_id_nodes: ResourceNodeBundle,
parallel_work_bundles: ParallelNodeBundles,
@ -148,22 +142,22 @@ pub const Schedule = struct {
info: *const Info,
label: [:0]u8,
dependencies: []Dependency,
parameter_states: [max_parameters]?*anyopaque = [_]?*anyopaque{null} ** max_parameters,
resource_accesses: stack.Sequential(ResourceAccess),
parameter_states: [max_parameters]*anyopaque = [_]*anyopaque{undefined} ** max_parameters,
resource_accesses: coral.stack.Sequential(ResourceAccess),
});
const NodeBundle = stack.Sequential(dag.Node);
const NodeBundle = coral.stack.Sequential(dag.Node);
const ParallelNodeBundles = stack.Sequential(NodeBundle);
const ParallelNodeBundles = coral.stack.Sequential(NodeBundle);
const ResourceAccess = union (enum) {
read_only: resource.TypeID,
read_write: resource.TypeID,
read_only: states.TypeID,
read_write: states.TypeID,
};
const ResourceNodeBundle = map.Hashed(resource.TypeID, NodeBundle, map.enum_traits(resource.TypeID));
const ResourceNodeBundle = coral.map.Hashed(states.TypeID, NodeBundle, coral.map.enum_traits(states.TypeID));
pub fn deinit(self: *Schedule) void {
pub fn deinit(self: *Schedule, world: *World) void {
{
var nodes = self.system_id_nodes.entries();
@ -194,12 +188,14 @@ pub const Schedule = struct {
const system = self.graph.get_ptr(node).?;
for (system.info.used_parameters(), system.parameter_states[0 .. system.info.parameter_count]) |parameter, state| {
parameter.unbind(self.arena.allocator(), state);
parameter.unbind(self.arena.allocator(), state, .{
.world = world,
});
}
system.resource_accesses.deinit();
heap.allocator.free(system.dependencies);
heap.allocator.free(system.label);
coral.heap.allocator.free(system.dependencies);
coral.heap.allocator.free(system.label);
}
for (self.parallel_work_bundles.values) |*bundle| {
@ -304,7 +300,7 @@ pub const Schedule = struct {
continue;
}
try schedule.parallel_work_bundles.push_grow(.{.allocator = heap.allocator});
try schedule.parallel_work_bundles.push_grow(.{.allocator = coral.heap.allocator});
const bundle = schedule.parallel_work_bundles.get_ptr().?;
@ -385,21 +381,21 @@ pub const Schedule = struct {
}
pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule {
var arena = std.heap.ArenaAllocator.init(heap.allocator);
var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
errdefer arena.deinit();
const duped_label = try arena.allocator().dupeZ(u8, label);
return .{
.graph = Graph.init(heap.allocator),
.graph = Graph.init(coral.heap.allocator),
.label = duped_label,
.arena = arena,
.system_id_nodes = .{.allocator = heap.allocator},
.read_write_resource_id_nodes = .{.allocator = heap.allocator},
.read_only_resource_id_nodes = .{.allocator = heap.allocator},
.parallel_work_bundles = .{.allocator = heap.allocator},
.blocking_work = .{.allocator = heap.allocator},
.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},
};
}
@ -421,18 +417,18 @@ pub const Schedule = struct {
const nodes = lazily_create: {
const system_id = @intFromPtr(info);
break: lazily_create self.system_id_nodes.get_ptr(system_id) orelse insert: {
break: lazily_create self.system_id_nodes.get(system_id) orelse insert: {
std.debug.assert(try self.system_id_nodes.emplace(system_id, .{
.allocator = self.system_id_nodes.allocator,
}));
break: insert self.system_id_nodes.get_ptr(system_id).?;
break: insert self.system_id_nodes.get(system_id).?;
};
};
const dependencies = init: {
const total_run_orders = order.run_after.len + order.run_before.len;
const dependencies = try heap.allocator.alloc(Dependency, total_run_orders);
const dependencies = try coral.heap.allocator.alloc(Dependency, total_run_orders);
var dependencies_written = @as(usize, 0);
for (order.run_after) |after_system| {
@ -456,26 +452,26 @@ pub const Schedule = struct {
break: init dependencies;
};
errdefer heap.allocator.free(dependencies);
errdefer coral.heap.allocator.free(dependencies);
const label = try heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
const label = try coral.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
errdefer heap.allocator.free(label);
errdefer coral.heap.allocator.free(label);
const node = try self.graph.append(.{
.info = info,
.label = label,
.dependencies = dependencies,
.resource_accesses = .{.allocator = heap.allocator},
.resource_accesses = .{.allocator = coral.heap.allocator},
});
const system = self.graph.get_ptr(node).?;
errdefer {
for (info.used_parameters(), system.parameter_states[0 .. info.parameter_count]) |parameter, state| {
if (state) |initialized_state| {
parameter.unbind(self.arena.allocator(), initialized_state);
}
parameter.unbind(self.arena.allocator(), state, .{
.world = world,
});
}
std.debug.assert(self.graph.remove_node(node) != null);
@ -495,4 +491,8 @@ pub const Schedule = struct {
}
};
pub const UnbindContext = struct {
world: *World,
};
pub const max_parameters = 16;

View File

@ -4,10 +4,25 @@ 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(ona.gfx.Point2D) = .{.allocator = coral.heap.allocator},
quad_mesh_2d: ona.gfx.Handle = .none,
body_texture: ona.gfx.Handle = .none,
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 {
@ -22,48 +37,90 @@ pub fn main() !void {
});
}
fn load(display: coral.Write(ona.gfx.Display), actors: coral.Write(Actors), assets: coral.Write(ona.gfx.Assets)) !void {
display.res.width, display.res.height = .{1280, 720};
actors.res.body_texture = try assets.res.open_file(coral.files.bundle, "actor.bmp");
actors.res.quad_mesh_2d = try assets.res.open_quad_mesh_2d(@splat(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: coral.Write(Actors)) void {
fn exit(actors: ona.Write(Actors)) void {
actors.res.instances.deinit();
}
fn render(queue: ona.gfx.Queue, actors: coral.Write(Actors)) !void {
for (actors.res.instances.values) |instance| {
try queue.commands.append(.{
.instance_2d = .{
.mesh_2d = actors.res.quad_mesh_2d,
.texture = actors.res.body_texture,
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,
});
.transform = .{
.origin = instance,
.xbasis = .{64, 0},
.ybasis = .{0, 64},
},
},
});
}
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: coral.Read(Player), actors: coral.Write(Actors), mapping: coral.Read(ona.act.Mapping)) !void {
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),
mapping.res.axis_strength(player.res.move_y),
mapping.res.axis_strength(player.res.move_x) * 10,
mapping.res.axis_strength(player.res.move_y) * 10,
};
}
fn setup(world: *coral.World, events: ona.App.Events) !void {
try world.set_resource(.none, Actors{});
try world.set_resource(.none, Player{});
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, coral.system_fn(load), .{.label = "load"});
try world.on_event(events.update, coral.system_fn(update), .{.label = "update"});
try world.on_event(events.exit, coral.system_fn(exit), .{.label = "exit"});
try world.on_event(events.render, coral.system_fn(render), .{.label = "render actors"});
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,17 +1,20 @@
const coral = @import("coral");
const flow = @import("flow");
events: *const Events,
target_frame_time: f64,
elapsed_time: f64,
is_running: bool,
pub const Events = struct {
load: coral.World.Event,
pre_update: coral.World.Event,
update: coral.World.Event,
post_update: coral.World.Event,
render: coral.World.Event,
finish: coral.World.Event,
exit: coral.World.Event,
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,
};
const Self = @This();

View File

@ -2,6 +2,8 @@ const App = @import("./App.zig");
const coral = @import("coral");
const flow = @import("flow");
const gfx = @import("./gfx.zig");
const msg = @import("./msg.zig");
@ -35,15 +37,15 @@ pub const Mapping = struct {
}
};
pub fn setup(world: *coral.World, events: App.Events) std.mem.Allocator.Error!void {
try world.set_resource(.none, Mapping{});
pub fn setup(world: *flow.World, events: App.Events) std.mem.Allocator.Error!void {
try world.set_state(Mapping{});
try world.on_event(events.pre_update, coral.system_fn(update), .{
try world.on_event(events.pre_update, flow.system_fn(update), .{
.label = "update act",
});
}
pub fn update(inputs: msg.Receive(gfx.Input), mapping: coral.Write(Mapping)) void {
pub fn update(inputs: msg.Receive(gfx.Input), mapping: flow.Write(Mapping)) void {
mapping.res.keys_pressed = Mapping.ScancodeSet.initEmpty();
for (inputs.messages()) |message| {

View File

@ -1,126 +1,298 @@
const App = @import("./App.zig");
pub const colors = @import("./gfx/colors.zig");
const coral = @import("coral");
const device = @import("./gfx/device.zig");
const ext = @import("./ext.zig");
const formats = @import("./gfx/formats.zig");
const flow = @import("flow");
const msg = @import("./msg.zig");
const rendering = @import("./gfx/rendering.zig");
const std = @import("std");
pub const Assets = struct {
context: device.Context,
formats: coral.stack.Sequential(Format),
staging_arena: std.heap.ArenaAllocator,
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 Format = struct {
extension: []const u8,
file_desc: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) Error!Desc,
pub const LoadError = std.mem.Allocator.Error;
pub const Error = std.mem.Allocator.Error || coral.files.Error || error {
FormatUnsupported,
};
pub const LoadFileError = LoadError || coral.files.ReadAllError || error {
FormatUnsupported,
};
pub fn open_file(self: *Assets, storage: coral.files.Storage, path: []const u8) (OpenError || Format.Error)!Handle {
defer {
const max_cache_size = 536870912;
pub const TextureFormat = struct {
extension: []const u8,
load_file: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) LoadFileError!Texture.Desc,
};
if (!self.staging_arena.reset(.{.retain_with_limit = max_cache_size})) {
std.log.warn("failed to retain staging arena size of {} bytes", .{max_cache_size});
}
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();
}
for (self.formats.values) |format| {
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.context.open(try format.file_desc(&self.staging_arena, storage, path));
return self.load_texture(try format.load_file(&arena, storage, path));
}
return .none;
return error.FormatUnsupported;
}
pub fn open_quad_mesh_2d(self: *Assets, extents: Point2D) OpenError!Handle {
const width, const height = extents / @as(Point2D, @splat(2));
return self.context.open(.{
.mesh_2d = .{
.indices = &.{0, 1, 2, 0, 2, 3},
.vertices = &.{
.{.xy = .{-width, height}, .uv = .{0, 1}},
.{.xy = .{width, height}, .uv = .{1, 1}},
.{.xy = .{width, -height}, .uv = .{1, 0}},
.{.xy = .{-width, -height}, .uv = .{0, 0}},
},
},
});
}
pub const thread_restriction = .main;
};
pub const Color = @Vector(4, f32);
pub const Desc = union (enum) {
texture: Texture,
mesh_2d: Mesh2D,
pub const Commands = struct {
pending: *List,
pub const Mesh2D = struct {
vertices: []const Vertex,
indices: []const u16,
pub const Vertex = struct {
xy: Point2D,
uv: Point2D,
};
const Command = union (enum) {
draw_texture: DrawTextureCommand,
set_effect: SetEffectCommand,
set_target: SetTargetCommand,
};
pub const Texture = struct {
data: []const coral.io.Byte,
width: u16,
format: Format,
access: Access,
pub const DrawTextureCommand = struct {
texture: Texture,
transform: Transform2D,
};
pub const Access = enum {
static,
};
pub const SetEffectCommand = struct {
effect: Effect,
properties: []const coral.io.Byte,
};
pub const Format = enum {
rgba8,
bgra8,
pub const SetTargetCommand = struct {
texture: Texture,
clear_color: ?Color,
clear_depth: ?f32,
clear_stencil: ?u8,
};
pub fn byte_size(self: Format) usize {
return switch (self) {
.rgba8, .bgra8 => 4,
};
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 Display = struct {
pub const Config = struct {
width: u16 = 1280,
height: u16 = 720,
clear_color: Color = colors.black,
};
pub const Handle = enum (usize) {
none,
_,
pub fn index(self: Handle) ?usize {
return switch (self) {
.none => null,
_ => @intFromEnum(self) - 1,
};
}
};
pub const Input = union (enum) {
key_up: Key,
key_down: Key,
@ -137,85 +309,135 @@ pub const Input = union (enum) {
};
};
pub const OpenError = std.mem.Allocator.Error || error {
pub const Effect = enum (u32) {
default,
_,
pub const Desc = struct {
fragment_spirv_ops: []const u32,
};
};
pub const Point2D = @Vector(2, f32);
pub const Texture = enum (u32) {
default,
backbuffer,
_,
pub const Queue = struct {
commands: *device.RenderList,
pub const Desc = struct {
format: Format,
access: Access,
pub const State = struct {
command_index: usize,
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 fn bind(_: coral.system.BindContext) std.mem.Allocator.Error!State {
// TODO: Review how good of an idea this global state is, even if bind is guaranteed to always be ran on main.
if (renders.is_empty()) {
renders = .{.allocator = coral.heap.allocator};
pub const Format = enum {
rgba8,
bgra8,
pub fn byte_size(self: Format) usize {
return switch (self) {
.rgba8, .bgra8 => 4,
};
}
const command_index = renders.len();
try renders.push_grow(device.RenderChain.init(coral.heap.allocator));
return .{
.command_index = command_index,
};
}
pub fn init(state: *State) Queue {
return .{
.commands = renders.values[state.command_index].pending(),
};
}
pub fn unbind(state: *State) void {
std.debug.assert(!renders.is_empty());
const render = &renders.values[state.command_index];
render.deinit();
std.mem.swap(device.RenderChain, render, renders.get_ptr().?);
std.debug.assert(renders.pop());
if (renders.is_empty()) {
Queue.renders.deinit();
}
}
var renders = coral.stack.Sequential(device.RenderChain){.allocator = coral.heap.allocator};
};
};
pub const Transform2D = extern struct {
xbasis: Point2D = .{1, 0},
ybasis: Point2D = .{0, 1},
origin: Point2D = @splat(0),
xbasis: Vector = .{1, 0},
ybasis: Vector = .{0, 1},
origin: Vector = @splat(0),
const Vector = @Vector(2, f32);
};
const builtin_formats = [_]Assets.Format{
.{
.extension = "bmp",
.file_desc = formats.bmp_file_desc,
},
};
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;
};
pub const colors = struct {
pub const black = greyscale(0);
pub const white = greyscale(1);
pub fn greyscale(v: f32) Color {
return .{v, v, v, 1};
if (!std.mem.eql(u8, &header.type, "BM")) {
return error.FormatUnsupported;
}
pub fn rgb(r: f32, g: f32, b: f32) Color {
return .{r, g, b, 1};
}
};
const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
return error.FormatUnsupported;
};
pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void {
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(App), inputs: msg.Send(Input)) !void {
var event = @as(ext.SDL_Event, undefined);
while (ext.SDL_PollEvent(&event) != 0) {
@ -228,45 +450,87 @@ pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void {
}
}
pub fn setup(world: *coral.World, events: App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
pub fn setup(world: *flow.World, events: App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
return error.Unsupported;
}
var context = try device.Context.init();
const assets = create: {
var assets = try Assets.init();
errdefer context.deinit();
errdefer {
assets.deinit();
}
var registered_formats = coral.stack.Sequential(Assets.Format){.allocator = coral.heap.allocator};
break: create try world.set_get_state(assets);
};
errdefer registered_formats.deinit();
assets.frame_rendered.set();
try registered_formats.grow(builtin_formats.len);
std.debug.assert(registered_formats.push_all(&builtin_formats));
errdefer {
assets.deinit();
}
try world.set_resource(.none, Assets{
.staging_arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
.formats = registered_formats,
.context = context,
assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{
&assets.pending_work,
assets.window,
});
try world.set_resource(.none, Display{});
try world.on_event(events.pre_update, coral.system_fn(poll), .{.label = "poll gfx"});
try world.on_event(events.exit, coral.system_fn(stop), .{.label = "stop gfx"});
try world.on_event(events.finish, coral.system_fn(synchronize), .{.label = "synchronize gfx"});
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: coral.Write(Assets)) void {
assets.res.staging_arena.deinit();
assets.res.formats.deinit();
assets.res.context.deinit();
pub fn stop(assets: flow.Write(Assets)) void {
assets.res.deinit();
}
pub fn synchronize(assets: coral.Write(Assets), display: coral.Read(Display)) !void {
assets.res.context.submit(.{
.width = display.res.width,
.height = display.res.height,
.clear_color = display.res.clear_color,
.renders = Queue.renders.values,
});
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();
}
}

518
src/ona/gfx/Resources.zig Normal file
View File

@ -0,0 +1,518 @@
const coral = @import("coral");
const gfx = @import("../gfx.zig");
const sokol = @import("sokol");
const spirv = @import("./spirv.zig");
const std = @import("std");
effects: EffectPool,
textures: TexturePool,
pub const Effect = struct {
shader: sokol.gfx.Shader,
pipeline: sokol.gfx.Pipeline,
properties: []coral.io.Byte,
pub fn deinit(self: *Effect) void {
coral.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);
defer {
spirv_arena.deinit();
}
const spirv_program = try spirv.analyze(&spirv_arena, .{
.target = try switch (sokol.gfx.queryBackend()) {
.GLCORE => spirv.Target.glsl,
else => error.InvalidSPIRV,
},
.vertex_source = .{
.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")),
},
.fragment_source = .{
.ops = desc.fragment_spirv_ops,
},
});
const shader = sokol.gfx.makeShader(shader_desc: {
const shader_desc = sokol.gfx.ShaderDesc{
.vs = stage_desc(spirv_program.vertex_stage),
.fs = stage_desc(spirv_program.fragment_stage),
};
// TODO: Vertex attributes, for some reason they aren't needed?
break: shader_desc shader_desc;
});
// TODO: Review blending rules.
const pipeline = sokol.gfx.makePipeline(pipeline_desc: {
var pipeline_desc = sokol.gfx.PipelineDesc{
.label = "Effect pipeline",
.layout = vertex_layout_state,
.shader = shader,
.index_type = .UINT16,
.blend_color = .{.r = 1.0, .g = 1.0, .b = 1.0, .a = 1.0},
};
pipeline_desc.colors[0] = .{
.write_mask = .RGBA,
.blend = .{
.enabled = true,
.src_factor_rgb = .SRC_ALPHA,
.dst_factor_rgb = .ONE_MINUS_SRC_ALPHA,
},
};
break: pipeline_desc pipeline_desc;
});
const properties = try coral.heap.allocator.alloc(
coral.io.Byte,
if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
);
errdefer {
coral.heap.allocator.free(properties);
sokol.gfx.destroyPipeline(pipeline);
sokol.gfx.destroyShader(shader);
}
return .{
.shader = shader,
.pipeline = pipeline,
.properties = properties,
};
}
fn stage_desc(spirv_stage: spirv.Stage) sokol.gfx.ShaderStageDesc {
var stage = sokol.gfx.ShaderStageDesc{
.entry = spirv_stage.entry_point,
.source = spirv_stage.source,
};
for (0 .. spirv.Stage.max_uniform_blocks) |slot| {
const uniform_block = &(spirv_stage.has_uniform_blocks[slot] orelse {
continue;
});
const stage_uniform_block = &stage.uniform_blocks[slot];
stage_uniform_block.layout = switch (uniform_block.layout) {
.std140 => .STD140,
};
stage_uniform_block.size = uniform_block.size();
for (stage_uniform_block.uniforms[0 .. uniform_block.uniforms.len], uniform_block.uniforms) |*stage_uniform, uniform| {
stage_uniform.* = .{
.type = switch (uniform.type) {
.float => .FLOAT,
.float2 => .FLOAT2,
.float3 => .FLOAT3,
.float4 => .FLOAT4,
.integer => .INT,
.integer2 => .INT2,
.integer3 => .INT3,
.integer4 => .INT4,
.matrix4 => .MAT4,
},
.name = uniform.name,
.array_count = uniform.len,
};
}
}
for (0 .. spirv.Stage.max_images) |slot| {
const image = &(spirv_stage.has_images[slot] orelse {
continue;
});
stage.images[slot] = .{
.multisampled = image.is_multisampled,
.image_type = ._2D,
.sample_type = .FLOAT,
.used = true,
};
}
for (0 .. spirv.Stage.max_samplers) |slot| {
const sampler = &(spirv_stage.has_samplers[slot] orelse {
continue;
});
stage.samplers[slot] = .{
.sampler_type = switch (sampler.*) {
.filtering => .FILTERING,
.non_filtering => .NONFILTERING,
.comparison => .COMPARISON,
},
.used = true,
};
}
for (0 .. spirv.Stage.max_image_sampler_pairs) |slot| {
const image_sampler_pair = &(spirv_stage.has_image_sampler_pairs[slot] orelse {
continue;
});
stage.image_sampler_pairs[slot] = .{
.glsl_name = image_sampler_pair.name,
.image_slot = @intCast(image_sampler_pair.image_slot),
.sampler_slot = @intCast(image_sampler_pair.sampler_slot),
.used = true,
};
}
return stage;
}
const vertex_layout_state = sokol.gfx.VertexLayoutState{
.attrs = get: {
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
attrs[0] = .{
.format = .FLOAT2,
.buffer_index = 0,
};
attrs[1] = .{
.format = .FLOAT2,
.buffer_index = 0,
};
attrs[2] = .{
.format = .FLOAT2,
.buffer_index = 1,
};
attrs[3] = .{
.format = .FLOAT2,
.buffer_index = 1,
};
attrs[4] = .{
.format = .FLOAT2,
.buffer_index = 1,
};
attrs[5] = .{
.format = .UBYTE4N,
.buffer_index = 1,
};
attrs[6] = .{
.format = .FLOAT,
.buffer_index = 1,
};
attrs[7] = .{
.format = .FLOAT4,
.buffer_index = 1,
};
break: get attrs;
},
.buffers = get: {
var vertex_buffer_layouts = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
vertex_buffer_layouts[1].step_func = .PER_INSTANCE;
break: get vertex_buffer_layouts;
},
};
};
const EffectPool = coral.Pool(Effect);
const Self = @This();
pub const Texture = struct {
width: u16,
height: u16,
access: Access,
pub const Access = union (enum) {
empty,
render: RenderAccess,
static: StaticAccess,
};
pub const RenderAccess = struct {
color_image: sokol.gfx.Image,
depth_image: sokol.gfx.Image,
attachments: sokol.gfx.Attachments,
};
pub const StaticAccess = struct {
image: sokol.gfx.Image,
};
pub fn deinit(self: *Texture) void {
switch (self.access) {
.render => |render| {
sokol.gfx.destroyImage(render.color_image);
sokol.gfx.destroyImage(render.depth_image);
sokol.gfx.destroyAttachments(render.attachments);
},
.static => |static| {
sokol.gfx.destroyImage(static.image);
},
.empty => {},
}
self.* = undefined;
}
pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture {
const pixel_format = switch (desc.format) {
.rgba8 => sokol.gfx.PixelFormat.RGBA8,
.bgra8 => sokol.gfx.PixelFormat.BGRA8,
};
switch (desc.access) {
.render => |render| {
if (render.width == 0 or render.height == 0) {
return .{
.width = render.width,
.height = render.height,
.access = .empty,
};
}
const color_image = sokol.gfx.makeImage(.{
.pixel_format = pixel_format,
.width = render.width,
.height = render.height,
.render_target = true,
});
const depth_image = sokol.gfx.makeImage(.{
.width = render.width,
.height = render.height,
.render_target = true,
.pixel_format = .DEPTH_STENCIL,
});
const attachments = sokol.gfx.makeAttachments(attachments_desc: {
var attachments_desc = sokol.gfx.AttachmentsDesc{
.depth_stencil = .{
.image = depth_image,
},
};
attachments_desc.colors[0] = .{
.image = color_image,
};
break: attachments_desc attachments_desc;
});
return .{
.width = render.width,
.height = render.height,
.access = .{
.render = .{
.attachments = attachments,
.color_image = color_image,
.depth_image = depth_image,
},
},
};
},
.static => |static| {
const height = std.math.cast(u16, static.data.len / (static.width * desc.format.byte_size())) orelse {
return error.OutOfMemory;
};
if (static.width == 0 or height == 0) {
return .{
.width = static.width,
.height = height,
.access = .empty,
};
}
const image = sokol.gfx.makeImage(image_desc: {
var image_desc = sokol.gfx.ImageDesc{
.height = height,
.pixel_format = pixel_format,
.width = static.width,
};
image_desc.data.subimage[0][0] = sokol.gfx.asRange(static.data);
break: image_desc image_desc;
});
errdefer {
sokol.gfx.destroyImage(image);
}
return .{
.width = static.width,
.height = height,
.access = .{
.static = .{
.image = image,
},
},
};
},
}
}
};
const TexturePool = coral.Pool(Texture);
pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect {
var effect = try Effect.init(desc);
errdefer effect.deinit();
return @enumFromInt(try self.effects.insert(effect));
}
pub fn create_texture(self: *Self, desc: gfx.Texture.Desc) !gfx.Texture {
var texture = try Texture.init(desc);
errdefer texture.deinit();
return @enumFromInt(try self.textures.insert(texture));
}
pub fn deinit(self: *Self) void {
var textures = self.textures.values();
while (textures.next()) |texture| {
texture.deinit();
}
self.textures.deinit();
var effects = self.effects.values();
while (effects.next()) |effect| {
effect.deinit();
}
self.effects.deinit();
self.* = undefined;
}
pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
switch (handle) {
.default => {},
else => {
var effect = self.effects.remove(@intFromEnum(handle)) orelse {
return false;
};
effect.deinit();
},
}
return true;
}
pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
switch (handle) {
.default => {},
else => {
var texture = self.textures.remove(@intFromEnum(handle)) orelse {
return false;
};
texture.deinit();
},
}
return true;
}
pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect {
return self.effects.get(@intFromEnum(handle));
}
pub fn get_texture(self: *Self, handle: gfx.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),
};
errdefer {
pools.deinit();
}
const assert = struct {
fn is_handle(expected: anytype, actual: @TypeOf(expected)) void {
std.debug.assert(actual == expected);
}
};
assert.is_handle(gfx.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(.{
.format = .rgba8,
.access = .{
.static = .{
.data = std.mem.asBytes(&[_]u32{
0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080,
0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000,
0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080,
0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000,
0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080,
0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000,
0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080,
0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000, 0xFF800080, 0xFF000000,
}),
.width = 8,
},
},
}));
assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{
.format = .rgba8,
.access = .{
.render = .{
.width = 0,
.height = 0,
},
}
}));
return pools;
}

13
src/ona/gfx/colors.zig Normal file
View File

@ -0,0 +1,13 @@
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,80 +0,0 @@
const coral = @import("coral");
const std = @import("std");
pub fn Chain(comptime Command: type, comptime clone_command: ?Clone(Command)) type {
return struct {
swap_lists: [2]CommandList,
swap_state: u1 = 0,
const CommandList = List(Command, clone_command);
const Self = @This();
pub fn deinit(self: *Self) void {
for (&self.swap_lists) |*list| {
list.deinit();
}
self.* = undefined;
}
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.swap_lists = .{CommandList.init(allocator), CommandList.init(allocator)},
};
}
pub fn pending(self: *Self) *CommandList {
return &self.swap_lists[self.swap_state];
}
pub fn submitted(self: *Self) *CommandList {
return &self.swap_lists[self.swap_state ^ 1];
}
pub fn swap(self: *Self) void {
self.swap_state ^= 1;
}
};
}
pub fn Clone(comptime Command: type) type {
return fn (Command, *std.heap.ArenaAllocator) std.mem.Allocator.Error!Command;
}
pub fn List(comptime Command: type, comptime clone_command: ?Clone(Command)) type {
return struct {
arena: std.heap.ArenaAllocator,
stack: coral.stack.Sequential(Command),
const Self = @This();
pub fn append(self: *Self, command: Command) std.mem.Allocator.Error!void {
return self.stack.push_grow(if (clone_command) |clone| try clone(command, &self.arena) else command);
}
pub fn clear(self: *Self) void {
self.stack.clear();
if (!self.arena.reset(.retain_capacity)) {
std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
}
}
pub fn deinit(self: *Self) void {
self.arena.deinit();
self.stack.deinit();
self.* = undefined;
}
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.arena = std.heap.ArenaAllocator.init(allocator),
.stack = .{.allocator = allocator},
};
}
};
}

View File

@ -1,691 +0,0 @@
const commands = @import("./commands.zig");
const coral = @import("coral");
const ext = @import("../ext.zig");
const gfx = @import("../gfx.zig");
const sokol = @import("sokol");
const std = @import("std");
pub const Context = struct {
window: *ext.SDL_Window,
thread: std.Thread,
loop: *Loop,
pub const Submission = struct {
width: u16,
height: u16,
clear_color: gfx.Color = gfx.colors.black,
renders: []RenderChain,
};
pub fn close(self: *Context, handle: gfx.Handle) void {
const handle_index = handle.index() orelse {
return;
};
const close_commands = self.loop.closes.pending();
std.debug.assert(close_commands.stack.cap > close_commands.stack.len());
close_commands.append(.{.index = handle_index}) catch unreachable;
}
pub fn deinit(self: *Context) void {
self.loop.is_running.store(false, .monotonic);
self.loop.ready.post();
self.thread.join();
self.loop.deinit();
coral.heap.allocator.destroy(self.loop);
ext.SDL_DestroyWindow(self.window);
self.* = undefined;
}
pub fn init() !Context {
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);
const loop = try coral.heap.allocator.create(Loop);
errdefer coral.heap.allocator.destroy(loop);
loop.* = .{};
return .{
.loop = loop,
.window = window,
.thread = spawn: {
const thread = try std.Thread.spawn(.{}, Loop.run, .{loop, window});
thread.setName("Ona Graphics") catch {
std.log.warn("failed to name the graphics thread", .{});
};
break: spawn thread;
},
};
}
pub fn open(self: *Context, desc: gfx.Desc) gfx.OpenError!gfx.Handle {
const open_commands = self.loop.opens.pending();
const index = self.loop.closed_indices.get() orelse open_commands.stack.len();
try open_commands.append(.{
.index = index,
.desc = desc,
});
const pending_closes = self.loop.closes.pending();
if (pending_closes.stack.len() == pending_closes.stack.cap) {
try pending_closes.stack.grow(1);
}
_ = self.loop.closed_indices.pop();
return @enumFromInt(index + 1);
}
pub fn submit(self: *Context, submission: Submission) void {
self.loop.finished.wait();
defer self.loop.ready.post();
for (submission.renders) |*render| {
render.swap();
}
self.loop.opens.swap();
self.loop.closes.swap();
var last_width, var last_height = [_]c_int{0, 0};
ext.SDL_GetWindowSize(self.window, &last_width, &last_height);
if (submission.width != last_width or submission.height != last_height) {
ext.SDL_SetWindowSize(self.window, submission.width, submission.height);
}
self.loop.clear_color = submission.clear_color;
self.loop.renders = submission.renders;
self.loop.ready.post();
}
};
const Loop = struct {
ready: std.Thread.Semaphore = .{},
finished: std.Thread.Semaphore = .{},
clear_color: gfx.Color = gfx.colors.black,
is_running: AtomicBool = AtomicBool.init(true),
renders: []RenderChain = &.{},
closes: CloseChain = CloseChain.init(coral.heap.allocator),
opens: OpenChain = OpenChain.init(coral.heap.allocator),
closed_indices: coral.stack.Sequential(usize) = .{.allocator = coral.heap.allocator},
const AtomicBool = std.atomic.Value(bool);
const CloseCommand = struct {
index: usize,
};
const OpenCommand = struct {
index: usize,
desc: gfx.Desc,
fn clone(command: OpenCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!OpenCommand {
const allocator = arena.allocator();
return .{
.desc = switch (command.desc) {
.texture => |texture| .{
.texture = .{
.data = try allocator.dupe(coral.io.Byte, texture.data),
.width = texture.width,
.format = texture.format,
.access = texture.access,
},
},
.mesh_2d => |mesh_2d| .{
.mesh_2d = .{
.vertices = try allocator.dupe(gfx.Desc.Mesh2D.Vertex, mesh_2d.vertices),
.indices = try allocator.dupe(u16, mesh_2d.indices),
},
},
},
.index = command.index,
};
}
};
const CloseChain = commands.Chain(CloseCommand, null);
const OpenChain = commands.Chain(OpenCommand, OpenCommand.clone);
fn deinit(self: *Loop) void {
self.closes.deinit();
self.opens.deinit();
self.closed_indices.deinit();
}
fn run(self: *Loop, window: *ext.SDL_Window) !void {
const context = configure_and_create: {
var result = @as(c_int, 0);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
if (result != 0) {
return error.Unsupported;
}
break: configure_and_create ext.SDL_GL_CreateContext(window);
};
sokol.gfx.setup(.{
.environment = .{
.defaults = .{
.color_format = .RGBA8,
.depth_format = .DEPTH_STENCIL,
.sample_count = 1,
},
},
.logger = .{
.func = sokol.log.func,
},
});
defer {
sokol.gfx.shutdown();
ext.SDL_GL_DeleteContext(context);
}
var rendering = Rendering.init();
defer rendering.deinit();
self.finished.post();
while (self.is_running.load(.monotonic)) {
self.ready.wait();
defer self.finished.post();
const open_commands = self.opens.submitted();
defer open_commands.clear();
for (open_commands.stack.values) |command| {
switch (command.desc) {
.texture => |texture| {
const stride = texture.width * texture.format.byte_size();
const image = sokol.gfx.makeImage(.{
.width = texture.width,
.height = @intCast(texture.data.len / stride),
.data = .{
.subimage = get: {
var subimage = [_][16]sokol.gfx.Range{.{.{}} ** 16} ** 6;
subimage[0][0] = sokol.gfx.asRange(texture.data);
break: get subimage;
},
},
.pixel_format = switch (texture.format) {
.rgba8 => .RGBA8,
.bgra8 => .BGRA8,
},
});
errdefer sokol.gfx.destroyImage(image);
const sampler = sokol.gfx.makeSampler(.{});
errdefer sokol.gfx.destroySampler(sampler);
try rendering.insert_object(command.index, .{
.texture = .{
.sampler = sampler,
.image = image,
},
});
},
.mesh_2d => |mesh_2d| {
const index_buffer = sokol.gfx.makeBuffer(.{
.data = sokol.gfx.asRange(mesh_2d.indices),
.type = .INDEXBUFFER,
});
const vertex_buffer = sokol.gfx.makeBuffer(.{
.data = sokol.gfx.asRange(mesh_2d.vertices),
.type = .VERTEXBUFFER,
});
errdefer {
sokol.gfx.destroyBuffer(index_buffer);
sokol.gfx.destroyBuffer(vertex_buffer);
}
if (mesh_2d.indices.len > std.math.maxInt(u32)) {
return error.OutOfMemory;
}
try rendering.insert_object(command.index, .{
.mesh_2d = .{
.index_buffer = index_buffer,
.vertex_buffer = vertex_buffer,
.index_count = @intCast(mesh_2d.indices.len),
},
});
},
}
}
var frame = init_frame: {
var width, var height = [_]c_int{0, 0};
ext.SDL_GL_GetDrawableSize(window, &width, &height);
std.debug.assert(width > 0 and height > 0);
break: init_frame Rendering.Frame{
.width = @intCast(width),
.height = @intCast(height),
};
};
sokol.gfx.beginPass(.{
.swapchain = .{
.width = frame.width,
.height = frame.height,
.sample_count = 1,
.color_format = .RGBA8,
.depth_format = .DEPTH_STENCIL,
.gl = .{.framebuffer = 0},
},
.action = .{
.colors = get: {
var actions = [_]sokol.gfx.ColorAttachmentAction{.{}} ** 4;
actions[0] = .{
.load_action = .CLEAR,
.clear_value = @as(sokol.gfx.Color, @bitCast(self.clear_color)),
};
break: get actions;
},
},
});
for (self.renders) |*render| {
const render_commands = render.submitted();
defer render_commands.clear();
for (render_commands.stack.values) |command| {
switch (command) {
.instance_2d => |instance_2d| {
try rendering.push_instance_2d(&frame, instance_2d);
},
.post_process => |post_process| {
rendering.flush_instance_2ds(&frame);
// sokol.gfx.applyPipeline(self.post_process_pipeline);
_ = post_process;
},
}
}
}
rendering.flush_instance_2ds(&frame);
sokol.gfx.endPass();
sokol.gfx.commit();
ext.SDL_GL_SwapWindow(window);
const close_commands = self.closes.submitted();
defer close_commands.clear();
for (close_commands.stack.values) |command| {
const object = &rendering.objects.values[command.index];
switch (object.*) {
.empty => {}, // TODO: Handle double-closes.
.mesh_2d => |mesh_2d| {
sokol.gfx.destroyBuffer(mesh_2d.vertex_buffer);
sokol.gfx.destroyBuffer(mesh_2d.index_buffer);
},
.texture => |texture| {
sokol.gfx.destroyImage(texture.image);
sokol.gfx.destroySampler(texture.sampler);
},
}
object.* = .empty;
}
}
}
};
const Rendering = struct {
objects: coral.stack.Sequential(Object),
instance_2d_pipeline: sokol.gfx.Pipeline,
instance_2d_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
const Instance2D = extern struct {
transform: gfx.Transform2D,
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
depth: f32 = 0,
texture_offset: gfx.Point2D = @splat(0),
texture_size: gfx.Point2D = @splat(1),
const buffer_indices = .{
.mesh = 0,
.instance = 1,
};
const instances_per_buffer = 512;
const shader = @import("./shaders/instance_2d.glsl.zig");
};
const Frame = struct {
width: u16,
height: u16,
flushed_instance_2d_count: usize = 0,
pushed_instance_2d_count: usize = 0,
mesh_2d: gfx.Handle = .none,
texture: gfx.Handle = .none,
fn unflushed_instance_2d_count(self: Frame) usize {
return self.pushed_instance_2d_count - self.flushed_instance_2d_count;
}
};
const Object = union (enum) {
empty,
mesh_2d: struct {
index_count: u32,
vertex_buffer: sokol.gfx.Buffer,
index_buffer: sokol.gfx.Buffer,
},
texture: struct {
image: sokol.gfx.Image,
sampler: sokol.gfx.Sampler,
},
};
fn deinit(self: *Rendering) void {
for (self.instance_2d_buffers.values) |buffer| {
sokol.gfx.destroyBuffer(buffer);
}
self.instance_2d_buffers.deinit();
sokol.gfx.destroyPipeline(self.instance_2d_pipeline);
self.objects.deinit();
}
fn init() Rendering {
sokol.gfx.setup(.{
.environment = .{
.defaults = .{
.color_format = .RGBA8,
.depth_format = .DEPTH_STENCIL,
.sample_count = 1,
},
},
.logger = .{
.func = sokol.log.func,
},
});
return .{
.instance_2d_pipeline = sokol.gfx.makePipeline(.{
.label = "2D drawing pipeline",
.layout = .{
.attrs = get: {
var attrs = [_]sokol.gfx.VertexAttrState{.{}} ** 16;
attrs[Instance2D.shader.ATTR_vs_mesh_xy] = .{
.format = .FLOAT2,
.buffer_index = Instance2D.buffer_indices.mesh,
};
attrs[Instance2D.shader.ATTR_vs_mesh_uv] = .{
.format = .FLOAT2,
.buffer_index = Instance2D.buffer_indices.mesh,
};
attrs[Instance2D.shader.ATTR_vs_instance_xbasis] = .{
.format = .FLOAT2,
.buffer_index = Instance2D.buffer_indices.instance,
};
attrs[Instance2D.shader.ATTR_vs_instance_ybasis] = .{
.format = .FLOAT2,
.buffer_index = Instance2D.buffer_indices.instance,
};
attrs[Instance2D.shader.ATTR_vs_instance_origin] = .{
.format = .FLOAT2,
.buffer_index = Instance2D.buffer_indices.instance,
};
attrs[Instance2D.shader.ATTR_vs_instance_tint] = .{
.format = .UBYTE4N,
.buffer_index = Instance2D.buffer_indices.instance,
};
attrs[Instance2D.shader.ATTR_vs_instance_depth] = .{
.format = .FLOAT,
.buffer_index = Instance2D.buffer_indices.instance,
};
attrs[Instance2D.shader.ATTR_vs_instance_rect] = .{
.format = .FLOAT4,
.buffer_index = Instance2D.buffer_indices.instance,
};
break: get attrs;
},
.buffers = get: {
var buffers = [_]sokol.gfx.VertexBufferLayoutState{.{}} ** 8;
buffers[Instance2D.buffer_indices.instance].step_func = .PER_INSTANCE;
break: get buffers;
},
},
.shader = sokol.gfx.makeShader(Instance2D.shader.draw2dShaderDesc(sokol.gfx.queryBackend())),
.index_type = .UINT16,
}),
.instance_2d_buffers = .{.allocator = coral.heap.allocator},
.objects = .{.allocator = coral.heap.allocator},
};
}
fn flush_instance_2ds(self: *Rendering, frame: *Frame) void {
const unflushed_count = frame.unflushed_instance_2d_count();
if (unflushed_count == 0) {
return;
}
sokol.gfx.applyPipeline(self.instance_2d_pipeline);
sokol.gfx.applyUniforms(.VS, Instance2D.shader.SLOT_Screen, sokol.gfx.asRange(&Instance2D.shader.Screen{
.screen_size = .{@floatFromInt(frame.width), @floatFromInt(frame.height)},
}));
const mesh_2d = self.objects.values[frame.mesh_2d.index().?].mesh_2d;
const texture = self.objects.values[frame.texture.index().?].texture;
var bindings = sokol.gfx.Bindings{
.vertex_buffers = get: {
var buffers = [_]sokol.gfx.Buffer{.{}} ** 8;
buffers[Instance2D.buffer_indices.mesh] = mesh_2d.vertex_buffer;
break: get buffers;
},
.index_buffer = mesh_2d.index_buffer,
.fs = .{
.images = get: {
var images = [_]sokol.gfx.Image{.{}} ** 12;
images[0] = texture.image;
break: get images;
},
.samplers = get: {
var samplers = [_]sokol.gfx.Sampler{.{}} ** 8;
samplers[0] = texture.sampler;
break: get samplers;
},
},
};
while (frame.flushed_instance_2d_count < frame.pushed_instance_2d_count) {
const buffer_index = frame.flushed_instance_2d_count / Instance2D.instances_per_buffer;
const buffer_offset = frame.flushed_instance_2d_count % Instance2D.instances_per_buffer;
const instances_to_flush = @min(Instance2D.instances_per_buffer - buffer_offset, unflushed_count);
bindings.vertex_buffers[Instance2D.buffer_indices.instance] = self.instance_2d_buffers.values[buffer_index];
bindings.vertex_buffer_offsets[Instance2D.buffer_indices.instance] = @intCast(buffer_offset);
sokol.gfx.applyBindings(bindings);
sokol.gfx.draw(0, mesh_2d.index_count, @intCast(instances_to_flush));
frame.flushed_instance_2d_count += instances_to_flush;
}
}
fn insert_object(self: *Rendering, index: usize, object: Object) !void {
const resource_count = self.objects.len();
if (index < resource_count) {
const empty_object = &self.objects.values[index];
if (empty_object.* != .empty) {
return error.InvalidHandle;
}
empty_object.* = object;
} else {
if (index != resource_count) {
return error.InvalidIndex;
}
try self.objects.push_grow(object);
}
}
fn push_instance_2d(self: *Rendering, frame: *Frame, command: RenderCommand.Instance) std.mem.Allocator.Error!void {
if (command.mesh_2d != frame.mesh_2d or command.texture != frame.texture) {
self.flush_instance_2ds(frame);
}
frame.mesh_2d = command.mesh_2d;
frame.texture = command.texture;
const has_filled_buffer = (frame.pushed_instance_2d_count % Instance2D.instances_per_buffer) == 0;
const pushed_buffer_count = frame.pushed_instance_2d_count / Instance2D.instances_per_buffer;
if (has_filled_buffer and pushed_buffer_count == self.instance_2d_buffers.len()) {
const instance_buffer = sokol.gfx.makeBuffer(.{
.size = @sizeOf(Instance2D) * Instance2D.instances_per_buffer,
.usage = .STREAM,
.label = "2D drawing instance buffer",
});
errdefer sokol.gfx.destroyBuffer(instance_buffer);
try self.instance_2d_buffers.push_grow(instance_buffer);
}
_ = sokol.gfx.appendBuffer(self.instance_2d_buffers.get().?, sokol.gfx.asRange(&Instance2D{
.transform = command.transform,
}));
frame.pushed_instance_2d_count += 1;
}
fn remove_object(self: *Rendering, index: usize) ?Object {
const object = self.objects.values[index];
if (object != .empty) {
self.objects.values[index] = .empty;
return object;
}
return null;
}
};
pub const RenderCommand = union (enum) {
instance_2d: Instance,
post_process: PostProcess,
pub const Instance = struct {
texture: gfx.Handle,
mesh_2d: gfx.Handle,
transform: gfx.Transform2D,
};
pub const PostProcess = struct {
};
fn clone(self: RenderCommand, arena: *std.heap.ArenaAllocator) std.mem.Allocator.Error!RenderCommand {
_ = arena;
return switch (self) {
.instance_2d => |instance_2d| .{.instance_2d = instance_2d},
.post_process => |post_process| .{.post_process = post_process},
};
}
};
pub const RenderChain = commands.Chain(RenderCommand, RenderCommand.clone);
pub const RenderList = commands.List(RenderCommand, RenderCommand.clone);

View File

@ -1,81 +0,0 @@
const coral = @import("coral");
const gfx = @import("../gfx.zig");
const std = @import("std");
pub fn bmp_file_desc(
arena: *std.heap.ArenaAllocator,
storage: coral.files.Storage,
path: []const u8,
) gfx.Assets.Format.Error!gfx.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_bytes(path, file_offset, line) != 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 .{
.texture = .{
.width = pixel_width,
.data = pixels,
.format = .rgba8,
.access = .static,
}
};
}

428
src/ona/gfx/rendering.zig Normal file
View File

@ -0,0 +1,428 @@
const Resources = @import("./Resources.zig");
const coral = @import("coral");
const ext = @import("../ext.zig");
const gfx = @import("../gfx.zig");
const spirv = @import("./spirv.zig");
const sokol = @import("sokol");
const std = @import("std");
const Frame = struct {
texture_batch_buffers: coral.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,
const DrawTexture = extern struct {
transform: gfx.Transform2D,
tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
depth: f32 = 0,
texture_offset: @Vector(2, f32) = @splat(0),
texture_size: @Vector(2, f32) = @splat(1),
};
const batches_per_buffer = 512;
pub fn deinit(self: *Frame) void {
for (self.texture_batch_buffers.values) |buffer| {
sokol.gfx.destroyBuffer(buffer);
}
self.texture_batch_buffers.deinit();
self.* = undefined;
}
pub fn init() !Frame {
const Vertex = struct {
xy: @Vector(2, f32),
uv: @Vector(2, f32),
};
const quad_index_buffer = sokol.gfx.makeBuffer(.{
.data = sokol.gfx.asRange(&[_]u16{0, 1, 2, 0, 2, 3}),
.type = .INDEXBUFFER,
});
const quad_vertex_buffer = sokol.gfx.makeBuffer(.{
.data = sokol.gfx.asRange(&[_]Vertex{
.{.xy = .{-0.5, -0.5}, .uv = .{0, 1}},
.{.xy = .{0.5, -0.5}, .uv = .{1, 1}},
.{.xy = .{0.5, 0.5}, .uv = .{1, 0}},
.{.xy = .{-0.5, 0.5}, .uv = .{0, 0}},
}),
.type = .VERTEXBUFFER,
});
return .{
.texture_batch_buffers = .{.allocator = coral.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 {
if (command.texture != self.current_source_texture) {
self.flush(pools);
}
self.current_source_texture = command.texture;
const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0;
const buffer_count = self.drawn_count / batches_per_buffer;
if (has_filled_current_buffer and buffer_count == self.texture_batch_buffers.len()) {
const instance_buffer = sokol.gfx.makeBuffer(.{
.size = @sizeOf(DrawTexture) * batches_per_buffer,
.usage = .STREAM,
});
errdefer sokol.gfx.destroyBuffer(instance_buffer);
try self.texture_batch_buffers.push_grow(instance_buffer);
}
_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?, sokol.gfx.asRange(&DrawTexture{
.transform = command.transform,
}));
self.drawn_count += 1;
}
pub fn finish(self: *Frame, pools: *Resources) void {
self.flush(pools);
self.drawn_count = 0;
self.flushed_count = 0;
self.current_source_texture = .default;
self.current_target_texture = .backbuffer;
self.current_effect = .default;
}
pub fn flush(self: *Frame, pools: *Resources) void {
if (self.flushed_count == self.drawn_count) {
return;
}
var bindings = sokol.gfx.Bindings{
.index_buffer = self.quad_index_buffer,
};
bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer;
switch (pools.get_texture(self.current_source_texture).?.access) {
.render => |render| {
bindings.fs.images[0] = render.color_image;
bindings.fs.samplers[0] = default_sampler;
},
.static => |static| {
bindings.fs.images[0] = static.image;
bindings.fs.samplers[0] = default_sampler;
},
.empty => {
@panic("Cannot render empty textures");
},
}
const effect = pools.get_effect(self.current_effect).?;
sokol.gfx.applyPipeline(effect.pipeline);
const texture = pools.get_texture(self.current_target_texture).?;
sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{
.left = 0,
.top = 0,
.right = @floatFromInt(texture.width),
.bottom = @floatFromInt(texture.height),
})));
if (effect.properties.len != 0) {
sokol.gfx.applyUniforms(.FS, 0, sokol.gfx.asRange(effect.properties));
}
while (true) {
const buffer_index = self.flushed_count / batches_per_buffer;
const buffer_offset = self.flushed_count % batches_per_buffer;
const instances_to_flush = @min(batches_per_buffer - buffer_offset, self.drawn_count - self.flushed_count);
self.flushed_count += instances_to_flush;
bindings.vertex_buffers[vertex_indices.instance] = self.texture_batch_buffers.values[buffer_index];
bindings.vertex_buffer_offsets[vertex_indices.instance] = @intCast(@sizeOf(DrawTexture) * buffer_offset);
sokol.gfx.applyBindings(bindings);
sokol.gfx.draw(0, 6, @intCast(instances_to_flush));
if (self.flushed_count == self.drawn_count) {
break;
}
}
}
pub fn set_effect(self: *Frame, pools: *Resources, command: gfx.Commands.SetEffectCommand) void {
if (command.effect != self.current_effect) {
self.flush(pools);
}
self.current_effect = command.effect;
if (pools.get_effect(self.current_effect)) |effect| {
@memcpy(effect.properties, command.properties);
}
}
pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void {
sokol.gfx.endPass();
var pass = sokol.gfx.Pass{
.action = .{
.stencil = .{
.load_action = .CLEAR,
},
},
};
if (command.clear_color) |color| {
pass.action.colors[0] = .{
.load_action = .CLEAR,
.clear_value = @bitCast(color),
};
} else {
pass.action.colors[0] = .{.load_action = .LOAD};
}
if (command.clear_depth) |depth| {
pass.action.depth = .{
.load_action = .CLEAR,
.clear_value = depth,
};
} else {
pass.action.depth = .{.load_action = .LOAD};
}
pass.attachments = switch (pools.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,
};
self.current_target_texture = command.texture;
sokol.gfx.beginPass(pass);
}
};
var default_sampler: sokol.gfx.Sampler = undefined;
const vertex_indices = .{
.mesh = 0,
.instance = 1,
};
pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void {
const context = configure_and_create: {
var result = @as(c_int, 0);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_FLAGS, ext.SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_PROFILE_MASK, ext.SDL_GL_CONTEXT_PROFILE_CORE);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MAJOR_VERSION, 3);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_CONTEXT_MINOR_VERSION, 3);
result |= ext.SDL_GL_SetAttribute(ext.SDL_GL_DOUBLEBUFFER, 1);
if (result != 0) {
return error.Unsupported;
}
break: configure_and_create ext.SDL_GL_CreateContext(window);
};
defer ext.SDL_GL_DeleteContext(context);
sokol.gfx.setup(.{
.environment = .{
.defaults = .{
.color_format = .RGBA8,
.depth_format = .DEPTH_STENCIL,
.sample_count = 1,
},
},
.logger = .{
.func = sokol.log.func,
},
});
defer {
sokol.gfx.shutdown();
}
var resources = try Resources.init();
defer {
resources.deinit();
}
var frame = try Frame.init();
defer {
frame.deinit();
}
default_sampler = sokol.gfx.makeSampler(.{});
while (true) {
switch (pending_work.dequeue()) {
.load_effect => |load| {
const effect = try resources.create_effect(load.desc);
if (!load.loaded.resolve(effect)) {
std.debug.assert(resources.destroy_effect(effect));
}
},
.load_texture => |load| {
const texture = try resources.create_texture(load.desc);
if (!load.loaded.resolve(texture)) {
std.debug.assert(resources.destroy_texture(texture));
}
},
.render_frame => |render_frame| {
const backbuffer = resources.get_texture(.backbuffer).?;
if (backbuffer.width != render_frame.width or backbuffer.height != render_frame.height) {
backbuffer.deinit();
backbuffer.* = try Resources.Texture.init(.{
.format = .rgba8,
.access = .{
.render = .{
.width = render_frame.width,
.height = render_frame.height,
},
},
});
}
sokol.gfx.beginPass(pass: {
var pass = sokol.gfx.Pass{
.action = .{
.stencil = .{
.load_action = .CLEAR,
},
.depth = .{
.load_action = .CLEAR,
.clear_value = 0,
}
},
};
pass.action.colors[0] = .{
.load_action = .CLEAR,
.clear_value = @bitCast(render_frame.clear_color),
};
pass.attachments = resources.get_texture(.backbuffer).?.access.render.attachments;
break: pass pass;
});
var has_command_params = render_frame.has_command_params;
while (has_command_params) |command_params| : (has_command_params = command_params.has_next) {
for (command_params.param.submitted_commands()) |command| {
try switch (command) {
.draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture),
.set_effect => |set_effect| frame.set_effect(&resources, set_effect),
.set_target => |set_target| frame.set_target(&resources, set_target),
};
}
frame.flush(&resources);
if (frame.current_target_texture != .backbuffer) {
frame.set_target(&resources, .{
.texture = .backbuffer,
.clear_color = null,
.clear_depth = null,
.clear_stencil = null,
});
}
}
sokol.gfx.endPass();
sokol.gfx.beginPass(swapchain_pass: {
var pass = sokol.gfx.Pass{
.swapchain = .{
.width = render_frame.width,
.height = render_frame.height,
.sample_count = 1,
.color_format = .RGBA8,
.depth_format = .DEPTH_STENCIL,
.gl = .{.framebuffer = 0},
},
.action = .{
.stencil = .{.load_action = .CLEAR},
.depth = .{.load_action = .CLEAR},
},
};
pass.action.colors[0] = .{.load_action = .CLEAR};
break: swapchain_pass pass;
});
try frame.draw_texture(&resources, .{
.texture = .backbuffer,
.transform = .{
.origin = .{@as(f32, @floatFromInt(render_frame.width)) / 2, @as(f32, @floatFromInt(render_frame.height)) / 2},
.xbasis = .{@floatFromInt(render_frame.width), 0},
.ybasis = .{0, @floatFromInt(render_frame.height)},
},
});
frame.finish(&resources);
sokol.gfx.endPass();
sokol.gfx.commit();
ext.SDL_GL_SwapWindow(window);
render_frame.finished.set();
},
.shutdown => {
break;
},
.unload_effect => |unload| {
if (!resources.destroy_effect(unload.handle)) {
@panic("Attempt to unload a non-existent effect");
}
},
.unload_texture => |unload| {
if (!resources.destroy_texture(unload.handle)) {
@panic("Attempt to unload a non-existent texture");
}
},
}
}
}
var work_thread: std.Thread = undefined;

View File

@ -0,0 +1,16 @@
#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;
void main() {
texel = texture(sprite, uv) * color;
if (texel.a == 0) {
discard;
}
}

View File

@ -0,0 +1,29 @@
#version 430
layout (location = 0) in vec2 mesh_xy;
layout (location = 1) in vec2 mesh_uv;
layout (location = 2) in vec2 instance_xbasis;
layout (location = 3) in vec2 instance_ybasis;
layout (location = 4) in vec2 instance_origin;
layout (location = 5) in vec4 instance_tint;
layout (location = 6) in float instance_depth;
layout (location = 7) in vec4 instance_rect;
layout (location = 0) out vec4 color;
layout (location = 1) out vec2 uv;
layout (binding = 0) uniform View {
mat4 projection_matrix;
};
void main() {
const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis;
const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy;
const vec2 rect_size = instance_rect.zw - instance_rect.xy;
const vec4 screen_position = vec4(projected_position, instance_depth, 1.0);
gl_Position = screen_position;
color = instance_tint;
uv = instance_rect.xy + (mesh_uv * rect_size);
}

View File

@ -1,61 +0,0 @@
@header const Vec2 = @Vector(2, f32)
@ctype vec2 Vec2
@vs vs
in vec2 mesh_xy;
in vec2 mesh_uv;
in vec2 instance_xbasis;
in vec2 instance_ybasis;
in vec2 instance_origin;
in vec4 instance_tint;
in float instance_depth;
in vec4 instance_rect;
uniform Screen {
vec2 screen_size;
};
out vec4 color;
out vec2 uv;
void main() {
// Calculate the world position of the vertex
const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis;
// Convert world position to normalized device coordinates (NDC)
// Assuming the screen coordinates range from (0, 0) to (screen_size.x, screen_size.y)
const vec2 ndc_position = (vec2(world_position.x, -world_position.y) / screen_size) * 2.0 - vec2(1.0, -1.0);
// Set the position of the vertex in clip space
gl_Position = vec4(ndc_position, instance_depth, 1.0);
color = instance_tint;
// Calculate the width and height from left, top, right, bottom configuration
const vec2 rect_pos = instance_rect.xy; // left, top
const vec2 rect_size = instance_rect.zw - instance_rect.xy; // right - left, bottom - top
// Calculate the adjusted UV coordinates using the instance_rect
uv = rect_pos + (mesh_uv * rect_size);
}
@end
@fs fs
uniform texture2D tex;
uniform sampler smp;
in vec4 color;
in vec2 uv;
out vec4 texel;
void main() {
texel = texture(sampler2D(tex, smp), uv) * color;
if (texel.a == 0) {
discard;
}
}
@end
@program draw_2d vs fs

407
src/ona/gfx/spirv.zig Normal file
View File

@ -0,0 +1,407 @@
const coral = @import("coral");
const ext = @cImport({
@cInclude("spirv-cross/spirv_cross_c.h");
});
const std = @import("std");
pub const Error = std.mem.Allocator.Error || error {
InvalidSPIRV,
UnsupportedSPIRV,
};
pub const Options = struct {
target: Target,
vertex_source: Source,
fragment_source: Source,
};
pub const Program = struct {
vertex_stage: Stage,
fragment_stage: Stage,
};
pub const Source = struct {
entry_point: []const u8 = "main",
ops: []const u32,
};
pub const Stage = struct {
entry_point: [:0]const u8,
source: [:0]const u8,
has_image_sampler_pairs: [max_image_sampler_pairs]?ImageSamplerPair = [_]?ImageSamplerPair{null} ** max_image_sampler_pairs,
has_images: [max_images]?Image = [_]?Image{null} ** max_images,
has_samplers: [max_samplers]?Sampler = [_]?Sampler{null} ** max_samplers,
has_uniform_blocks: [max_uniform_blocks]?UniformBlock = [_]?UniformBlock{null} ** max_uniform_blocks,
pub const Image = struct {
is_multisampled: bool,
};
pub const ImageSamplerPair = struct {
image_slot: usize,
sampler_slot: usize,
name: [:0]const u8,
};
pub const Layout = enum {
std140,
};
pub const Sampler = enum {
filtering,
non_filtering,
comparison,
};
pub const Uniform = struct {
name: [:0]const u8,
type: Type,
len: u16,
pub const Type = enum {
float,
float2,
float3,
float4,
integer,
integer2,
integer3,
integer4,
matrix4,
};
};
pub const UniformBlock = struct {
layout: Layout,
uniforms: []const Uniform,
pub const max_uniforms = 16;
pub fn size(self: UniformBlock) usize {
var accumulated_size: usize = 0;
for (self.uniforms) |uniform| {
accumulated_size += @max(1, uniform.len) * @as(usize, switch (uniform.type) {
.float => 4,
.float2 => 8,
.float3 => 12,
.float4 => 16,
.integer => 4,
.integer2 => 8,
.integer3 => 12,
.integer4 => 16,
.matrix4 => 64,
});
}
const alignment: usize = switch (self.layout) {
.std140 => 16,
};
return (accumulated_size + (alignment - 1)) & ~(alignment - 1);
}
};
pub const max_images = 12;
pub const max_image_sampler_pairs = 12;
pub const max_samplers = 8;
pub const max_storage_buffers = 8;
pub const max_uniform_blocks = 8;
fn reflect_images_samplers(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
const arena_allocator = arena.allocator();
var sampled_images: []const ext.spvc_reflected_resource = &.{};
try to_error(ext.spvc_resources_get_resource_list_for_type(
resources,
ext.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
@ptrCast(&sampled_images.ptr),
&sampled_images.len,
));
if (sampled_images.len > max_image_sampler_pairs) {
return error.UnsupportedSPIRV;
}
for (0 .. sampled_images.len, sampled_images) |i, sampled_image| {
const sampled_image_type = ext.spvc_compiler_get_type_handle(compiler, sampled_image.type_id);
if (ext.spvc_type_get_basetype(sampled_image_type) != ext.SPVC_BASETYPE_SAMPLED_IMAGE) {
return error.InvalidSPIRV;
}
try switch (ext.spvc_type_get_image_dimension(sampled_image_type)) {
ext.SpvDim2D => {},
else => error.InvalidSPIRV,
};
try switch (ext.spvc_type_get_basetype(ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_image_sampled_type(sampled_image_type)))) {
ext.SPVC_BASETYPE_FP32 => {},
else => error.InvalidSPIRV,
};
self.has_images[i] = .{
.is_multisampled = ext.spvc_type_get_image_multisampled(sampled_image_type) != 0,
};
self.has_samplers[i] = .filtering;
self.has_image_sampler_pairs[i] = .{
.name = try arena_allocator.dupeZ(u8, std.mem.span(ext.spvc_compiler_get_name(compiler, sampled_image.id))),
.image_slot = @intCast(i),
.sampler_slot = @intCast(i),
};
}
}
fn reflect_uniform_blocks(self: *Stage, arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler, resources: ext.spvc_resources) Error!void {
const arena_allocator = arena.allocator();
var reflected_resources: []const ext.spvc_reflected_resource = &.{};
try to_error(ext.spvc_resources_get_resource_list_for_type(
resources,
ext.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
@ptrCast(&reflected_resources.ptr),
&reflected_resources.len,
));
if (reflected_resources.len > max_uniform_blocks) {
return error.InvalidSPIRV;
}
for (0 .. reflected_resources.len, reflected_resources) |i, reflected_resource| {
const type_handle = ext.spvc_compiler_get_type_handle(compiler, reflected_resource.base_type_id);
const is_std430 = ext.spvc_compiler_has_decoration(compiler, reflected_resource.id, ext.SpvDecorationBufferBlock) == ext.SPVC_TRUE;
if (is_std430) {
return error.UnsupportedSPIRV;
}
const uniform_count = ext.spvc_type_get_num_member_types(type_handle);
if (uniform_count > UniformBlock.max_uniforms) {
return error.UnsupportedSPIRV;
}
const uniforms = try arena_allocator.alloc(Uniform, uniform_count);
for (uniforms, 0 .. uniform_count) |*uniform, j| {
const member_index: c_uint = @intCast(j);
const member_type_handle = ext.spvc_compiler_get_type_handle(compiler, ext.spvc_type_get_member_type(type_handle, member_index));
if (ext.spvc_type_get_num_array_dimensions(member_type_handle) > 1) {
return error.UnsupportedSPIRV;
}
uniform.* = .{
.name = try coral.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)),
}),
.type = try switch (ext.spvc_type_get_basetype(member_type_handle)) {
ext.SPVC_BASETYPE_FP32 => switch (ext.spvc_type_get_vector_size(member_type_handle)) {
4 => switch (ext.spvc_type_get_columns(member_type_handle)) {
4 => Uniform.Type.matrix4,
1 => Uniform.Type.float4,
else => error.UnsupportedSPIRV,
},
1 => Uniform.Type.float,
2 => Uniform.Type.float2,
3 => Uniform.Type.float3,
else => error.UnsupportedSPIRV,
},
ext.SPVC_BASETYPE_INT32 => try switch (ext.spvc_type_get_vector_size(member_type_handle)) {
1 => Uniform.Type.integer,
2 => Uniform.Type.integer2,
3 => Uniform.Type.integer3,
4 => Uniform.Type.integer4,
else => error.UnsupportedSPIRV,
},
else => error.UnsupportedSPIRV,
},
.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse {
return error.UnsupportedSPIRV;
},
};
}
self.has_uniform_blocks[i] = .{
.uniforms = uniforms,
.layout = .std140,
};
}
}
};
pub const Target = enum {
glsl,
};
pub fn analyze(arena: *std.heap.ArenaAllocator, options: Options) Error!Program {
var context: ext.spvc_context = null;
if (ext.spvc_context_create(&context) != ext.SPVC_SUCCESS) {
return error.OutOfMemory;
}
defer {
ext.spvc_context_destroy(context);
}
ext.spvc_context_set_error_callback(context, log_errors, null);
const arena_allocator = arena.allocator();
return .{
.vertex_stage = vertex_stage: {
const compiler = try parse(arena, context, options.target, options.vertex_source);
var stage = Stage{
.entry_point = try arena_allocator.dupeZ(u8, options.vertex_source.entry_point),
.source = try compile(arena, compiler),
};
const resources = create: {
var resources: ext.spvc_resources = null;
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
break: create resources;
};
try stage.reflect_images_samplers(arena, compiler, resources);
try stage.reflect_uniform_blocks(arena, compiler, resources);
std.log.info("{s}", .{stage.source});
break: vertex_stage stage;
},
.fragment_stage = fragment_stage: {
const compiler = try parse(arena, context, options.target, options.fragment_source);
var stage = Stage{
.entry_point = try arena_allocator.dupeZ(u8, options.fragment_source.entry_point),
.source = try compile(arena, compiler),
};
const resources = create: {
var resources: ext.spvc_resources = null;
try to_error(ext.spvc_compiler_create_shader_resources(compiler, &resources));
break: create resources;
};
try stage.reflect_images_samplers(arena, compiler, resources);
try stage.reflect_uniform_blocks(arena, compiler, resources);
std.log.info("{s}", .{stage.source});
break: fragment_stage stage;
},
};
}
fn compile(arena: *std.heap.ArenaAllocator, compiler: ext.spvc_compiler) Error![:0]const u8 {
const arena_allocator = arena.allocator();
var source: [*:0]const u8 = "";
try to_error(ext.spvc_compiler_compile(compiler, @ptrCast(&source)));
return arena_allocator.dupeZ(u8, std.mem.span(source));
}
fn log_errors(userdata: ?*anyopaque, error_message: [*c]const u8) callconv(.C) void {
std.debug.assert(userdata == null);
std.log.err("{s}", .{error_message});
}
fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Target, source: Source) Error!ext.spvc_compiler {
var parsed_ir: ext.spvc_parsed_ir = null;
try to_error(ext.spvc_context_parse_spirv(context, source.ops.ptr, source.ops.len, &parsed_ir));
var compiler: ext.spvc_compiler = null;
const spvc_target = switch (target) {
.glsl => ext.SPVC_BACKEND_GLSL,
};
try to_error(ext.spvc_context_create_compiler(context, spvc_target, parsed_ir, ext.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler));
try to_error(ext.spvc_compiler_build_combined_image_samplers(compiler));
var combined_image_samplers: []const ext.spvc_combined_image_sampler = &.{};
try to_error(ext.spvc_compiler_get_combined_image_samplers(compiler, @ptrCast(&combined_image_samplers.ptr), &combined_image_samplers.len));
const arena_allocator = arena.allocator();
var binding: u32 = 0;
for (combined_image_samplers) |combined_image_sampler| {
const name = try coral.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)),
});
ext.spvc_compiler_set_name(compiler, combined_image_sampler.combined_id, name);
ext.spvc_compiler_set_decoration(compiler, combined_image_sampler.combined_id, ext.SpvDecorationBinding, binding);
binding += 1;
}
const option_values: []const struct {ext.spvc_compiler_option, c_uint} = switch (target) {
.glsl => &.{
.{ext.SPVC_COMPILER_OPTION_GLSL_VERSION, 430},
.{ext.SPVC_COMPILER_OPTION_GLSL_ES, @intFromBool(false)},
.{ext.SPVC_COMPILER_OPTION_GLSL_VULKAN_SEMANTICS, @intFromBool(false)},
.{ext.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, @intFromBool(true)},
.{ext.SPVC_COMPILER_OPTION_FLIP_VERTEX_Y, @intFromBool(true)},
},
};
var compiler_options: ext.spvc_compiler_options = null;
try to_error(ext.spvc_compiler_create_compiler_options(compiler, &compiler_options));
for (option_values) |option_value| {
const entry, const value = option_value;
try to_error(ext.spvc_compiler_options_set_uint(compiler_options, entry, value));
}
try to_error(ext.spvc_compiler_install_compiler_options(compiler, compiler_options));
return compiler;
}
fn to_error(result: ext.spvc_result) Error!void {
return switch (result) {
ext.SPVC_SUCCESS => {},
ext.SPVC_ERROR_INVALID_SPIRV => error.InvalidSPIRV,
ext.SPVC_ERROR_UNSUPPORTED_SPIRV => error.UnsupportedSPIRV,
ext.SPVC_ERROR_OUT_OF_MEMORY => error.OutOfMemory,
ext.SPVC_ERROR_INVALID_ARGUMENT, ext.SPVC_ERROR_INT_MAX => unreachable,
else => unreachable,
};
}
pub fn to_ops(raw: anytype) [raw.len / @alignOf(u32)]u32 {
var ops: [raw.len / @alignOf(u32)]u32 = undefined;
@memcpy(std.mem.sliceAsBytes(&ops), raw);
return ops;
}

View File

@ -2,6 +2,8 @@ const App = @import("./App.zig");
const coral = @import("coral");
const flow = @import("flow");
const std = @import("std");
fn Channel(comptime Message: type) type {
@ -12,7 +14,7 @@ fn Channel(comptime Message: type) type {
const Self = @This();
fn cleanup(channel: coral.Write(Self)) void {
fn cleanup(channel: flow.Write(Self)) void {
channel.res.deinit();
}
@ -24,7 +26,7 @@ fn Channel(comptime Message: type) type {
self.* = undefined;
}
fn swap(channel: coral.Write(Self)) void {
fn swap(channel: flow.Write(Self)) void {
channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0;
if (channel.res.ticks == 0) {
@ -61,23 +63,23 @@ pub fn Receive(comptime Message: type) type {
const Self = @This();
pub const State = struct {
pub const Param = struct {
channel: *const TypedChannel,
};
pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State {
pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param {
return .{
.channel = (try context.register_read_only_resource_access(thread_restriction, TypedChannel)) orelse set: {
try context.world.set_resource(thread_restriction, TypedChannel.init(coral.heap.allocator));
.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_read_only_resource_access(thread_restriction, TypedChannel)).?;
break: set (try context.register_readable_state_access(TypedChannel)).?;
},
};
}
pub fn init(state: *State) Self {
pub fn init(param: *Param) Self {
return .{
.channel = state.channel,
.channel = param.channel,
};
}
@ -95,33 +97,33 @@ pub fn Send(comptime Message: type) type {
const Self = @This();
pub const State = struct {
pub const Param = struct {
channel: *TypedChannel,
};
pub fn bind(context: coral.system.BindContext) std.mem.Allocator.Error!State {
pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param {
return .{
.channel = (try context.register_read_write_resource_access(thread_restriction, TypedChannel)) orelse set: {
try context.world.set_resource(thread_restriction, TypedChannel.init(coral.heap.allocator));
.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_resource(.none, App).?;
const app = context.world.get_state(App).?;
try context.world.on_event(app.events.post_update, coral.system_fn(TypedChannel.swap), .{
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, coral.system_fn(TypedChannel.cleanup), .{
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_read_write_resource_access(thread_restriction, TypedChannel)).?;
break: set (try context.register_writable_state_access(TypedChannel)).?;
},
};
}
pub fn init(state: *State) Self {
pub fn init(param: *Param) Self {
return .{
.channel = state.channel,
.channel = param.channel,
};
}
@ -130,5 +132,3 @@ pub fn Send(comptime Message: type) type {
}
};
}
const thread_restriction = coral.World.ThreadRestriction.none;

View File

@ -6,6 +6,8 @@ const coral = @import("coral");
const ext = @import("./ext.zig");
const flow = @import("flow");
pub const gfx = @import("./gfx.zig");
pub const msg = @import("./msg.zig");
@ -23,7 +25,13 @@ pub const Options = struct {
};
};
pub const Setup = fn (*coral.World, App.Events) anyerror!void;
pub const Read = flow.Read;
pub const Setup = fn (*flow.World, App.Events) anyerror!void;
pub const World = flow.World;
pub const Write = flow.Write;
pub const default_middlewares = &.{
gfx.setup,
@ -37,7 +45,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void {
}
var world = try switch (options.execution) {
.single_threaded => coral.World.init(0),
.single_threaded => flow.World.init(0),
.thread_share => |thread_share| init: {
const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
@ -48,7 +56,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void {
});
}, 0, std.math.maxInt(u32))));
break: init coral.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0);
break: init flow.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0);
},
};
@ -64,9 +72,10 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void {
.exit = try world.create_event("exit"),
};
const app = try world.set_get_resource(.none, App{
const app = try world.set_get_state(App{
.events = &events,
.target_frame_time = 1.0 / @as(f64, @floatFromInt(options.tick_rate)),
.elapsed_time = 0,
.is_running = true,
});
@ -77,7 +86,8 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void {
try setup(&world, events);
try world.run_event(events.load);
var ticks_previous = std.time.milliTimestamp();
const ticks_initial = std.time.milliTimestamp();
var ticks_previous = ticks_initial;
var accumulated_time = @as(f64, 0);
while (app.is_running) {
@ -85,6 +95,7 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void {
const milliseconds_per_second = 1000.0;
const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second;
app.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second;
ticks_previous = ticks_current;
accumulated_time += delta_time;
@ -101,3 +112,5 @@ pub fn start_app(setup: Setup, options: Options) anyerror!void {
try world.run_event(events.exit);
}
pub const system_fn = flow.system_fn;

BIN
tools/sokol-shdc (Stored with Git LFS)

Binary file not shown.

BIN
tools/sokol-shdc.exe (Stored with Git LFS)

Binary file not shown.