Merge pull request 'renderer-mvp/post-processing' (#56) from renderer-mvp/post-processing into main
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
Reviewed-on: #56
This commit is contained in:
commit
2e70cf85a0
21
.drone.yml
21
.drone.yml
|
@ -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
|
||||
- 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Generated assets
|
||||
/.zig-cache
|
||||
.zig-cache
|
||||
/zig-out
|
||||
*.glsl.zig
|
||||
*.spv
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "ext/spirv-cross"]
|
||||
path = ext/spirv-cross
|
||||
url = https://github.com/KhronosGroup/SPIRV-Cross/
|
|
@ -7,7 +7,7 @@
|
|||
"request": "launch",
|
||||
"target": "${workspaceRoot}/zig-out/bin/main",
|
||||
"cwd": "${workspaceRoot}/debug/",
|
||||
"valuesFormatting": "parseText",
|
||||
"valuesFormatting": "prettyPrinters",
|
||||
"preLaunchTask": "Build All"
|
||||
},
|
||||
]
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
225
build.zig
225
build.zig
|
@ -2,21 +2,42 @@ 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 coral_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/coral/coral.zig"),
|
||||
});
|
||||
|
||||
const ona_module = add: {
|
||||
const sokol_dependency = b.dependency("sokol", .{
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
const module = b.addModule("ona", .{
|
||||
const coral_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/coral/coral.zig"),
|
||||
});
|
||||
|
||||
const flow_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/flow/flow.zig"),
|
||||
|
||||
.imports = &.{
|
||||
.{
|
||||
.name = "coral",
|
||||
.module = coral_module,
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
const ona_module = b.createModule(.{
|
||||
.root_source_file = b.path("src/ona/ona.zig"),
|
||||
|
||||
.imports = &.{
|
||||
|
@ -29,109 +50,175 @@ pub fn build(b: *std.Build) !void {
|
|||
.name = "coral",
|
||||
.module = coral_module,
|
||||
},
|
||||
|
||||
.{
|
||||
.name = "flow",
|
||||
.module = flow_module,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
break: add 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",
|
||||
};
|
||||
|
||||
b.step("test", "Run unit tests").dependOn(create: {
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "spirvcross",
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
switch (lib.rootModuleTarget().abi) {
|
||||
.msvc => lib.linkLibC(),
|
||||
else => lib.linkLibCpp(),
|
||||
}
|
||||
|
||||
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"},
|
||||
});
|
||||
}
|
||||
|
||||
break: spirv_cross lib;
|
||||
});
|
||||
|
||||
b.step("test", "Run unit tests").dependOn(tests: {
|
||||
const tests = b.addTest(.{
|
||||
.root_source_file = b.path("src/main.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
break: 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 Shader = struct {
|
||||
source_sub_path: SubPath = .{},
|
||||
binary_sub_path: SubPath = .{},
|
||||
};
|
||||
|
||||
const path_buffer_max = 255;
|
||||
var input_path_buffer = [_]u8{undefined} ** path_buffer_max;
|
||||
var output_path_buffer = [_]u8{undefined} ** path_buffer_max;
|
||||
var pending_shaders = std.ArrayList(Shader).init(b.allocator);
|
||||
|
||||
const glsl = if (target.result.isDarwin()) "glsl410" else "glsl430";
|
||||
const slang = glsl ++ ":metal_macos:hlsl5:glsl300es:wgsl";
|
||||
defer pending_shaders.deinit();
|
||||
|
||||
while (try walker.next()) |entry| {
|
||||
if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".glsl")) {
|
||||
continue;
|
||||
scan_shaders: while (try shaders_walker.next()) |entry| {
|
||||
if (entry.kind != .file) {
|
||||
continue: scan_shaders;
|
||||
}
|
||||
|
||||
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);
|
||||
const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
|
||||
|
||||
dir.access(output, .{.mode = .read_only}) catch {
|
||||
const cmd = b.addSystemCommand(&.{
|
||||
shdc_path,
|
||||
"-i",
|
||||
input_path,
|
||||
"-o",
|
||||
output_path,
|
||||
"-l",
|
||||
slang,
|
||||
"-f",
|
||||
"sokol_zig",
|
||||
});
|
||||
if (!is_shader_file) {
|
||||
continue: scan_shaders;
|
||||
}
|
||||
|
||||
step.dependOn(&cmd.step);
|
||||
const shader_name = std.fs.path.stem(entry.path);
|
||||
|
||||
continue;
|
||||
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 dir.statFile(entry.path)).mtime > (try dir.statFile(output)).mtime) {
|
||||
const cmd = b.addSystemCommand(&.{
|
||||
shdc_path,
|
||||
"-i",
|
||||
input_path,
|
||||
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",
|
||||
output_path,
|
||||
"-l",
|
||||
slang,
|
||||
"-f",
|
||||
"sokol_zig",
|
||||
vertex_binary_sub_path_utf8,
|
||||
});
|
||||
|
||||
step.dependOn(&cmd.step);
|
||||
}
|
||||
break: compile_vertex &compile_command.step;
|
||||
});
|
||||
|
||||
exe.step.dependOn(compile_fragment: {
|
||||
const compile_command = b.addSystemCommand(&.{
|
||||
"glslangValidator",
|
||||
"-V",
|
||||
fragment_binary_sub_path_utf8[0 .. fragment_binary_sub_path_utf8.len - 4],
|
||||
"-o",
|
||||
fragment_binary_sub_path_utf8,
|
||||
});
|
||||
|
||||
break: compile_fragment &compile_command.step;
|
||||
});
|
||||
}
|
||||
|
||||
break: add exe;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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)
BIN
debug/test.bmp (Stored with Git LFS)
Binary file not shown.
|
@ -0,0 +1 @@
|
|||
Subproject commit 6fd1f75636b1c424b809ad8a84804654cf5ae48b
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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)),
|
||||
pub fn get(self: *Self, key: usize) ?*Value {
|
||||
return switch (self.entries.values[key]) {
|
||||
.free_index => null,
|
||||
.occupied => |*occupied| occupied,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Self {
|
||||
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),
|
||||
}));
|
||||
},
|
||||
},
|
||||
.entries = .{.allocator = allocator},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(state: *State) Self {
|
||||
return .{
|
||||
.res = state.res,
|
||||
};
|
||||
pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
|
||||
const entries_count = self.entries.len();
|
||||
|
||||
if (self.first_free_index == entries_count) {
|
||||
try self.entries.push_grow(.{.occupied = value});
|
||||
|
||||
self.first_free_index += 1;
|
||||
|
||||
return entries_count;
|
||||
}
|
||||
|
||||
pub const thread_restriction = options.thread_restriction;
|
||||
};
|
||||
const insersion_index = self.first_free_index;
|
||||
|
||||
self.first_free_index = self.entries.values[self.first_free_index].free_index;
|
||||
self.entries.values[insersion_index] = .{.occupied = value};
|
||||
|
||||
return insersion_index;
|
||||
}
|
||||
|
||||
pub fn Write(comptime Value: type) type {
|
||||
return Resource(Value, .{
|
||||
.thread_restriction = .none,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn WriteBlocking(comptime Value: type) type {
|
||||
return Resource(Value, .{
|
||||
.thread_restriction = .main,
|
||||
});
|
||||
}
|
||||
|
||||
fn parameter_type(comptime Value: type) *const system.Info.Parameter {
|
||||
const has_state = @hasDecl(Value, "State");
|
||||
|
||||
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 {
|
||||
if (has_state) {
|
||||
const value_name = @typeName(Value);
|
||||
|
||||
if (!@hasDecl(Value, "bind")) {
|
||||
@compileError(
|
||||
"a `bind` declaration on " ++
|
||||
value_name ++
|
||||
" is requied for parameter types with a `State` 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 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);
|
||||
|
||||
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 (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();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
switch (self.entries.values[key]) {
|
||||
.free_index => {
|
||||
return null;
|
||||
},
|
||||
|
||||
else => @compileError("parameter `call` must be a function"),
|
||||
.occupied => |occupied| {
|
||||
self.entries.values[key] = .{.free_index = self.first_free_index};
|
||||
self.first_free_index = key;
|
||||
|
||||
return occupied;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn values(self: *const Self) Values {
|
||||
return .{
|
||||
.pool = self,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return system_info;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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},
|
||||
};
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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,
|
||||
});
|
|
@ -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;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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)),
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
113
src/main.zig
113
src/main.zig
|
@ -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,
|
||||
});
|
||||
|
||||
try commands.draw_texture(.{
|
||||
.texture = .default,
|
||||
|
||||
.transform = .{
|
||||
.origin = instance,
|
||||
.xbasis = .{64, 0},
|
||||
.ybasis = .{0, 64},
|
||||
.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"});
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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| {
|
||||
|
|
576
src/ona/gfx.zig
576
src/ona/gfx.zig
|
@ -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 {
|
||||
pub const LoadFileError = LoadError || coral.files.ReadAllError || error {
|
||||
FormatUnsupported,
|
||||
};
|
||||
|
||||
pub const TextureFormat = struct {
|
||||
extension: []const u8,
|
||||
load_file: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) LoadFileError!Texture.Desc,
|
||||
};
|
||||
|
||||
pub fn open_file(self: *Assets, storage: coral.files.Storage, path: []const u8) (OpenError || Format.Error)!Handle {
|
||||
pub const WorkQueue = coral.asyncio.BlockingQueue(1024, union (enum) {
|
||||
load_effect: LoadEffectWork,
|
||||
load_texture: LoadTextureWork,
|
||||
render_frame: RenderFrameWork,
|
||||
shutdown,
|
||||
unload_effect: UnloadEffectWork,
|
||||
unload_texture: UnloadTextureWork,
|
||||
|
||||
const LoadEffectWork = struct {
|
||||
desc: Effect.Desc,
|
||||
loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Effect),
|
||||
};
|
||||
|
||||
const LoadTextureWork = struct {
|
||||
desc: Texture.Desc,
|
||||
loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Texture),
|
||||
};
|
||||
|
||||
const RenderFrameWork = struct {
|
||||
clear_color: Color,
|
||||
width: u16,
|
||||
height: u16,
|
||||
finished: *std.Thread.ResetEvent,
|
||||
has_command_params: ?*flow.Params(Commands).Node,
|
||||
};
|
||||
|
||||
const UnloadEffectWork = struct {
|
||||
handle: Effect,
|
||||
};
|
||||
|
||||
const UnloadTextureWork = struct {
|
||||
handle: Texture,
|
||||
};
|
||||
});
|
||||
|
||||
fn deinit(self: *Assets) void {
|
||||
self.pending_work.enqueue(.shutdown);
|
||||
|
||||
if (self.has_worker_thread) |worker_thread| {
|
||||
worker_thread.join();
|
||||
}
|
||||
|
||||
self.texture_formats.deinit();
|
||||
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
fn init() !Assets {
|
||||
const window = create: {
|
||||
const position = ext.SDL_WINDOWPOS_CENTERED;
|
||||
const flags = ext.SDL_WINDOW_OPENGL;
|
||||
const width = 640;
|
||||
const height = 480;
|
||||
|
||||
break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
|
||||
return error.Unsupported;
|
||||
};
|
||||
};
|
||||
|
||||
errdefer {
|
||||
ext.SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
return .{
|
||||
.texture_formats = .{.allocator = coral.heap.allocator},
|
||||
.window = window,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn load_effect_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Effect {
|
||||
if (!std.mem.endsWith(u8, path, ".spv")) {
|
||||
return error.FormatUnsupported;
|
||||
}
|
||||
|
||||
const fragment_file_stat = try storage.stat(path);
|
||||
const fragment_spirv_ops = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
|
||||
|
||||
defer {
|
||||
const max_cache_size = 536870912;
|
||||
|
||||
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});
|
||||
}
|
||||
coral.heap.allocator.free(fragment_spirv_ops);
|
||||
}
|
||||
|
||||
for (self.formats.values) |format| {
|
||||
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) {
|
||||
pub const Commands = struct {
|
||||
pending: *List,
|
||||
|
||||
const Command = union (enum) {
|
||||
draw_texture: DrawTextureCommand,
|
||||
set_effect: SetEffectCommand,
|
||||
set_target: SetTargetCommand,
|
||||
};
|
||||
|
||||
pub const DrawTextureCommand = struct {
|
||||
texture: Texture,
|
||||
mesh_2d: Mesh2D,
|
||||
|
||||
pub const Mesh2D = struct {
|
||||
vertices: []const Vertex,
|
||||
indices: []const u16,
|
||||
|
||||
pub const Vertex = struct {
|
||||
xy: Point2D,
|
||||
uv: Point2D,
|
||||
};
|
||||
transform: Transform2D,
|
||||
};
|
||||
|
||||
pub const Texture = struct {
|
||||
data: []const coral.io.Byte,
|
||||
width: u16,
|
||||
format: Format,
|
||||
access: Access,
|
||||
|
||||
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 const Display = struct {
|
||||
pub fn bind(_: flow.system.BindContext) std.mem.Allocator.Error!Param {
|
||||
return .{
|
||||
.swap_lists = .{
|
||||
List.init(coral.heap.allocator),
|
||||
List.init(coral.heap.allocator),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
pub fn init(param: *Param) Commands {
|
||||
return .{
|
||||
.pending = param.pending_list(),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void {
|
||||
try self.pending.stack.push_grow(.{.draw_texture = command});
|
||||
}
|
||||
|
||||
pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void {
|
||||
try self.pending.stack.push_grow(.{
|
||||
.set_effect = .{
|
||||
.properties = try self.pending.arena.allocator().dupe(coral.io.Byte, command.properties),
|
||||
.effect = command.effect,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
pub fn unbind(param: *Param, _: flow.system.UnbindContext) void {
|
||||
param.deinit();
|
||||
}
|
||||
|
||||
pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void {
|
||||
try self.pending.stack.push_grow(.{.set_target = command});
|
||||
}
|
||||
};
|
||||
|
||||
pub const Config = struct {
|
||||
width: u16 = 1280,
|
||||
height: u16 = 720,
|
||||
clear_color: Color = colors.black,
|
||||
};
|
||||
|
||||
pub const 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 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 StaticAccess = struct {
|
||||
width: u16,
|
||||
data: []const coral.io.Byte,
|
||||
};
|
||||
|
||||
const command_index = renders.len();
|
||||
pub const RenderAccess = struct {
|
||||
width: u16,
|
||||
height: u16,
|
||||
};
|
||||
};
|
||||
|
||||
try renders.push_grow(device.RenderChain.init(coral.heap.allocator));
|
||||
pub const Format = enum {
|
||||
rgba8,
|
||||
bgra8,
|
||||
|
||||
return .{
|
||||
.command_index = command_index,
|
||||
pub fn byte_size(self: Format) usize {
|
||||
return switch (self) {
|
||||
.rgba8, .bgra8 => 4,
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
||||
if (!std.mem.eql(u8, &header.type, "BM")) {
|
||||
return error.FormatUnsupported;
|
||||
}
|
||||
|
||||
const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
|
||||
return error.FormatUnsupported;
|
||||
};
|
||||
|
||||
const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size);
|
||||
const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte;
|
||||
const alignment = 4;
|
||||
const byte_stride = pixel_width * bytes_per_pixel;
|
||||
const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
|
||||
const byte_padding = coral.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
|
||||
var buffer_offset: usize = 0;
|
||||
var file_offset = @as(usize, header.image_offset);
|
||||
|
||||
switch (header.bits_per_pixel) {
|
||||
32 => {
|
||||
while (buffer_offset < pixels.len) {
|
||||
const line = pixels[buffer_offset .. buffer_offset + byte_stride];
|
||||
|
||||
if (try storage.read(path, line, file_offset) != byte_stride) {
|
||||
return error.FormatUnsupported;
|
||||
}
|
||||
|
||||
for (0 .. pixel_width) |i| {
|
||||
const line_offset = i * 4;
|
||||
const pixel = line[line_offset .. line_offset + 4];
|
||||
|
||||
std.mem.swap(u8, &pixel[0], &pixel[2]);
|
||||
}
|
||||
|
||||
file_offset += line.len + byte_padding;
|
||||
buffer_offset += padded_byte_stride;
|
||||
}
|
||||
},
|
||||
|
||||
else => return error.FormatUnsupported,
|
||||
}
|
||||
|
||||
return .{
|
||||
.format = .rgba8,
|
||||
|
||||
.access = .{
|
||||
.static = .{
|
||||
.width = pixel_width,
|
||||
.data = pixels,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
pub const colors = struct {
|
||||
pub const black = greyscale(0);
|
||||
|
||||
pub const white = greyscale(1);
|
||||
|
||||
pub fn greyscale(v: f32) Color {
|
||||
return .{v, v, v, 1};
|
||||
}
|
||||
|
||||
pub fn rgb(r: f32, g: f32, b: f32) Color {
|
||||
return .{r, g, b, 1};
|
||||
}
|
||||
};
|
||||
|
||||
pub fn poll(app: coral.Write(App), inputs: msg.Send(Input)) !void {
|
||||
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);
|
||||
}
|
||||
|
||||
pub fn stop(assets: coral.Write(Assets)) void {
|
||||
assets.res.staging_arena.deinit();
|
||||
assets.res.formats.deinit();
|
||||
assets.res.context.deinit();
|
||||
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 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 stop(assets: flow.Write(Assets)) void {
|
||||
assets.res.deinit();
|
||||
}
|
||||
|
||||
pub fn synchronize(exclusive: flow.Exclusive) !void {
|
||||
const assets = exclusive.world.get_state(Assets).?;
|
||||
const config = exclusive.world.get_state(Config).?;
|
||||
|
||||
assets.frame_rendered.wait();
|
||||
assets.frame_rendered.reset();
|
||||
|
||||
{
|
||||
var has_command_param = exclusive.world.get_params(Commands).has_head;
|
||||
|
||||
while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
|
||||
command_param.param.rotate();
|
||||
}
|
||||
}
|
||||
|
||||
var display_width, var display_height = [_]c_int{0, 0};
|
||||
|
||||
ext.SDL_GL_GetDrawableSize(assets.window, &display_width, &display_height);
|
||||
|
||||
if (config.width != display_width or config.height != display_height) {
|
||||
ext.SDL_SetWindowSize(assets.window, config.width, config.height);
|
||||
}
|
||||
|
||||
if (exclusive.world.get_params(Commands).has_head) |command_param| {
|
||||
assets.pending_work.enqueue(.{
|
||||
.render_frame = .{
|
||||
.has_command_params = command_param,
|
||||
.width = config.width,
|
||||
.height = config.height,
|
||||
.clear_color = config.clear_color,
|
||||
.finished = &assets.frame_rendered,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
assets.frame_rendered.set();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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};
|
||||
}
|
|
@ -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},
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -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);
|
|
@ -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,
|
||||
}
|
||||
};
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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)
BIN
tools/sokol-shdc (Stored with Git LFS)
Binary file not shown.
BIN
tools/sokol-shdc.exe (Stored with Git LFS)
BIN
tools/sokol-shdc.exe (Stored with Git LFS)
Binary file not shown.
Loading…
Reference in New Issue