Convert project into Zig package and add demos (#55)
	
		
			
	
		
	
	
		
	
		
			Some checks failed
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is failing
				
			
		
		
	
	
				
					
				
			
		
			Some checks failed
		
		
	
	continuous-integration/drone/push Build is failing
				
			This commit is contained in:
		
							parent
							
								
									9323cad130
								
							
						
					
					
						commit
						6ec24c765c
					
				
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
								
							@ -1 +1,2 @@
 | 
				
			|||||||
*.bmp filter=lfs diff=lfs merge=lfs -text
 | 
					*.bmp filter=lfs diff=lfs merge=lfs -text
 | 
				
			||||||
 | 
					*.spv filter=lfs diff=lfs merge=lfs -text
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,4 +1,6 @@
 | 
				
			|||||||
# Generated assets
 | 
					# Generated assets
 | 
				
			||||||
.zig-cache
 | 
					.zig-cache
 | 
				
			||||||
/zig-out
 | 
					/zig-out
 | 
				
			||||||
*.spv
 | 
					/src/**/*.spv
 | 
				
			||||||
 | 
					/demos/**/*.out
 | 
				
			||||||
 | 
					/demos/**/*.exe
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							@ -5,8 +5,8 @@
 | 
				
			|||||||
			"name": "Runner",
 | 
								"name": "Runner",
 | 
				
			||||||
			"type": "gdb",
 | 
								"type": "gdb",
 | 
				
			||||||
			"request": "launch",
 | 
								"request": "launch",
 | 
				
			||||||
			"target": "${workspaceRoot}/zig-out/bin/main",
 | 
								"target": "${workspaceRoot}/demos/effects.out",
 | 
				
			||||||
			"cwd": "${workspaceRoot}/debug/",
 | 
								"cwd": "${workspaceRoot}/demos/",
 | 
				
			||||||
			"valuesFormatting": "prettyPrinters",
 | 
								"valuesFormatting": "prettyPrinters",
 | 
				
			||||||
			"preLaunchTask": "Build All"
 | 
								"preLaunchTask": "Build All"
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										384
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										384
									
								
								build.zig
									
									
									
									
									
								
							@ -2,44 +2,215 @@ const builtin = @import("builtin");
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ImportList = std.ArrayList(struct {
 | 
				
			||||||
 | 
						name: []const u8,
 | 
				
			||||||
 | 
						module: *std.Build.Module
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Project = struct {
 | 
				
			||||||
 | 
						target: std.Build.ResolvedTarget,
 | 
				
			||||||
 | 
						optimize: std.builtin.OptimizeMode,
 | 
				
			||||||
 | 
						imports: ImportList,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn find_demos(self: Project, b: *std.Build) !void {
 | 
				
			||||||
 | 
							const demos = b.step("demos", "Build demos");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var dir = try std.fs.cwd().openDir("demos/", .{
 | 
				
			||||||
 | 
								.iterate = true,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							defer {
 | 
				
			||||||
 | 
								dir.close();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var entries = try dir.walk(b.allocator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							defer {
 | 
				
			||||||
 | 
								entries.deinit();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							while (try entries.next()) |entry| {
 | 
				
			||||||
 | 
								if (entry.kind != .file or !std.mem.endsWith(u8, entry.path, ".zig")) {
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const source_path = try sub_path(.{"demos", entry.basename});
 | 
				
			||||||
 | 
								var path_buffer = [_:0]u8{0} ** 255;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const demo = b.addExecutable(.{
 | 
				
			||||||
 | 
									.name = try std.fmt.bufPrint(&path_buffer, "{s}.out", .{std.fs.path.stem(entry.basename)}),
 | 
				
			||||||
 | 
									.root_source_file = b.path(source_path.bytes()),
 | 
				
			||||||
 | 
									.target = self.target,
 | 
				
			||||||
 | 
									.optimize = self.optimize,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								for (self.imports.items) |import| {
 | 
				
			||||||
 | 
									demo.root_module.addImport(import.name, import.module);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								demos.dependOn(&b.addInstallArtifact(demo, .{
 | 
				
			||||||
 | 
									.dest_dir = .{
 | 
				
			||||||
 | 
										.override = .{
 | 
				
			||||||
 | 
											.custom = "../demos/",
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}).step);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn find_tests(self: Project, b: *std.Build) !void {
 | 
				
			||||||
 | 
							const tests = b.step("test", "Build and run tests");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (self.imports.items) |import| {
 | 
				
			||||||
 | 
								tests.dependOn(&b.addRunArtifact(b.addTest(.{
 | 
				
			||||||
 | 
									.root_source_file = import.module.root_source_file.?,
 | 
				
			||||||
 | 
									.target = self.target,
 | 
				
			||||||
 | 
									.optimize = self.optimize,
 | 
				
			||||||
 | 
								})).step);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn add_module(self: *Project, b: *std.Build, comptime name: []const u8, options: std.Build.Module.CreateOptions) !*std.Build.Module {
 | 
				
			||||||
 | 
							const cwd = std.fs.cwd();
 | 
				
			||||||
 | 
							var corrected_options = options;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (corrected_options.root_source_file == null) {
 | 
				
			||||||
 | 
								corrected_options.root_source_file = b.path("src/" ++ name ++ "/" ++ name ++ ".zig");
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (corrected_options.target == null) {
 | 
				
			||||||
 | 
								corrected_options.target = self.target;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (corrected_options.optimize == null) {
 | 
				
			||||||
 | 
								corrected_options.optimize = self.optimize;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const module = b.addModule(name, corrected_options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try self.imports.append(.{
 | 
				
			||||||
 | 
								.name = name,
 | 
				
			||||||
 | 
								.module = module
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO: Probably want to make it search the same path as any explicit root_source_path override for shaders.
 | 
				
			||||||
 | 
							const shaders_path = "src/" ++ name ++ "/shaders/";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var shaders_dir = cwd.openDir(shaders_path, .{.iterate = true}) catch |open_error| {
 | 
				
			||||||
 | 
								return switch (open_error) {
 | 
				
			||||||
 | 
									error.FileNotFound, error.NotDir => module,
 | 
				
			||||||
 | 
									else => open_error,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							defer {
 | 
				
			||||||
 | 
								shaders_dir.close();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var shaders_entries = try shaders_dir.walk(b.allocator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							defer {
 | 
				
			||||||
 | 
								shaders_entries.deinit();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							while (try shaders_entries.next()) |entry| {
 | 
				
			||||||
 | 
								if (entry.kind != .file) {
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!is_shader_file) {
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								var binary_buffer = [_:0]u8{0} ** 255;
 | 
				
			||||||
 | 
								const binary_name = try std.fmt.bufPrint(&binary_buffer, "{s}.spv", .{entry.path});
 | 
				
			||||||
 | 
								const full_source_path = try sub_path(.{shaders_path, entry.path});
 | 
				
			||||||
 | 
								const full_binary_path = try sub_path(.{shaders_path, binary_name});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const glslang_validator_args = [_][]const u8{
 | 
				
			||||||
 | 
									"glslangValidator",
 | 
				
			||||||
 | 
									"-V",
 | 
				
			||||||
 | 
									full_source_path.bytes(),
 | 
				
			||||||
 | 
									"-o",
 | 
				
			||||||
 | 
									full_binary_path.bytes(),
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								shaders_dir.access(binary_name, .{.mode = .read_only}) catch {
 | 
				
			||||||
 | 
									const output = b.run(&glslang_validator_args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									std.log.info("{s}", .{output[0 .. output.len - 1]});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if ((try shaders_dir.statFile(entry.path)).mtime > (try shaders_dir.statFile(binary_name)).mtime) {
 | 
				
			||||||
 | 
									const output = b.run(&glslang_validator_args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									std.log.info("{s}", .{output[0 .. output.len - 1]});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return module;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const SubPath = struct {
 | 
					const SubPath = struct {
 | 
				
			||||||
	buffer: [max]u8 = [_]u8{0} ** max,
 | 
						buffer: [max]u8 = [_]u8{0} ** max,
 | 
				
			||||||
	unused: u8 = max,
 | 
						unused: u8 = max,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const max = 255;
 | 
						pub const max = 255;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn utf8(self: *const SubPath) [:0]const u8 {
 | 
						pub fn append(self: *SubPath, component: []const u8) !void {
 | 
				
			||||||
		return self.buffer[0 .. (max - self.unused):0];
 | 
							const used = max - self.unused;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (used != 0 and self.buffer[used - 1] != '/') {
 | 
				
			||||||
 | 
								if (component.len > self.unused) {
 | 
				
			||||||
 | 
									return error.PathTooBig;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@memcpy(self.buffer[used .. (used + component.len)], component);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.unused -= @intCast(component.len);
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								const required_len = component.len + 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (required_len > self.unused) {
 | 
				
			||||||
 | 
									return error.PathTooBig;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@memcpy(self.buffer[used .. (used + component.len)], component);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.buffer[component.len] = '/';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.unused -= @intCast(required_len);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn bytes(self: *const SubPath) [:0]const u8 {
 | 
				
			||||||
 | 
							return @ptrCast(self.buffer[0 .. max - self.unused]);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn build(b: *std.Build) !void {
 | 
					pub fn build(b: *std.Build) !void {
 | 
				
			||||||
	const target = b.standardTargetOptions(.{});
 | 
						var project = Project{
 | 
				
			||||||
	const optimize = b.standardOptimizeOption(.{});
 | 
							.imports = ImportList.init(b.allocator),
 | 
				
			||||||
 | 
							.target = b.standardTargetOptions(.{}),
 | 
				
			||||||
 | 
							.optimize = b.standardOptimizeOption(.{}),
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const sokol_dependency = b.dependency("sokol", .{
 | 
						const sokol_dependency = b.dependency("sokol", .{
 | 
				
			||||||
		.target = target,
 | 
							.target = project.target,
 | 
				
			||||||
		.optimize = optimize,
 | 
							.optimize = project.optimize,
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const coral_module = b.createModule(.{
 | 
						const ona_module = try project.add_module(b, "ona", .{});
 | 
				
			||||||
		.root_source_file = b.path("src/coral/coral.zig"),
 | 
						const input_module = try project.add_module(b, "input", .{});
 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const flow_module = b.createModule(.{
 | 
					 | 
				
			||||||
		.root_source_file = b.path("src/flow/flow.zig"),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		.imports = &.{
 | 
					 | 
				
			||||||
			.{
 | 
					 | 
				
			||||||
				.name = "coral",
 | 
					 | 
				
			||||||
				.module = coral_module,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const ona_module = b.createModule(.{
 | 
					 | 
				
			||||||
		.root_source_file = b.path("src/ona/ona.zig"),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const coral_module = try project.add_module(b, "coral", .{
 | 
				
			||||||
		.imports = &.{
 | 
							.imports = &.{
 | 
				
			||||||
			.{
 | 
								.{
 | 
				
			||||||
				.name = "sokol",
 | 
									.name = "sokol",
 | 
				
			||||||
@ -47,20 +218,20 @@ pub fn build(b: *std.Build) !void {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			.{
 | 
								.{
 | 
				
			||||||
				.name = "coral",
 | 
									.name = "ona",
 | 
				
			||||||
				.module = coral_module,
 | 
									.module = ona_module,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			.{
 | 
								.{
 | 
				
			||||||
				.name = "flow",
 | 
									.name = "input",
 | 
				
			||||||
				.module = flow_module,
 | 
									.module = input_module,
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ona_module.addIncludePath(b.path("ext/"));
 | 
						coral_module.addIncludePath(b.path("ext/"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ona_module.linkLibrary(spirv_cross: {
 | 
						coral_module.linkLibrary(spirv_cross: {
 | 
				
			||||||
		const dir = "ext/spirv-cross/";
 | 
							const dir = "ext/spirv-cross/";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const sources = [_][]const u8{
 | 
							const sources = [_][]const u8{
 | 
				
			||||||
@ -78,8 +249,8 @@ pub fn build(b: *std.Build) !void {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		const lib = b.addStaticLibrary(.{
 | 
							const lib = b.addStaticLibrary(.{
 | 
				
			||||||
			.name = "spirvcross",
 | 
								.name = "spirvcross",
 | 
				
			||||||
			.target = target,
 | 
								.target = project.target,
 | 
				
			||||||
			.optimize = optimize,
 | 
								.optimize = project.optimize,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch (lib.rootModuleTarget().abi) {
 | 
							switch (lib.rootModuleTarget().abi) {
 | 
				
			||||||
@ -90,135 +261,62 @@ pub fn build(b: *std.Build) !void {
 | 
				
			|||||||
		inline for (sources) |src| {
 | 
							inline for (sources) |src| {
 | 
				
			||||||
			lib.addCSourceFile(.{
 | 
								lib.addCSourceFile(.{
 | 
				
			||||||
				.file = b.path(dir ++ src),
 | 
									.file = b.path(dir ++ src),
 | 
				
			||||||
				.flags = &.{"-fstrict-aliasing", "-DSPIRV_CROSS_C_API_GLSL", "-DSPIRV_CROSS_C_API_HLSL", "-DSPIRV_CROSS_C_API_MSL"},
 | 
					
 | 
				
			||||||
 | 
									.flags = &.{
 | 
				
			||||||
 | 
										"-fstrict-aliasing",
 | 
				
			||||||
 | 
										"-DSPIRV_CROSS_C_API_GLSL",
 | 
				
			||||||
 | 
										"-DSPIRV_CROSS_C_API_HLSL",
 | 
				
			||||||
 | 
										"-DSPIRV_CROSS_C_API_MSL",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		break: spirv_cross lib;
 | 
							break: spirv_cross lib;
 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	b.step("test", "Run unit tests").dependOn(tests: {
 | 
						coral_module.linkSystemLibrary("SDL2", .{
 | 
				
			||||||
		const tests = b.addTest(.{
 | 
							.needed = true,
 | 
				
			||||||
			.root_source_file = b.path("src/main.zig"),
 | 
							.preferred_link_mode = .dynamic,
 | 
				
			||||||
			.target = target,
 | 
					 | 
				
			||||||
			.optimize = optimize,
 | 
					 | 
				
			||||||
	});
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		break: tests &tests.step;
 | 
						coral_module.link_libc = true;
 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	b.installArtifact(add: {
 | 
						try project.find_tests(b);
 | 
				
			||||||
		const exe = b.addExecutable(.{
 | 
						try project.find_demos(b);
 | 
				
			||||||
			.name = "main",
 | 
					}
 | 
				
			||||||
			.root_source_file = b.path("src/main.zig"),
 | 
					 | 
				
			||||||
			.target = target,
 | 
					 | 
				
			||||||
			.optimize = optimize,
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		exe.root_module.addImport("ona", ona_module);
 | 
					fn sub_path(components: anytype) !SubPath {
 | 
				
			||||||
		exe.root_module.addImport("coral", coral_module);
 | 
						var path = comptime try std.BoundedArray(u8, SubPath.max).init(0);
 | 
				
			||||||
		exe.linkLibC();
 | 
						const Components = @TypeOf(components);
 | 
				
			||||||
		exe.linkSystemLibrary("SDL2");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const shaders_sub_path = "src/ona/gfx/shaders/";
 | 
						switch (@typeInfo(Components)) {
 | 
				
			||||||
 | 
							.Struct => |@"struct"| {
 | 
				
			||||||
 | 
								if (!@"struct".is_tuple) {
 | 
				
			||||||
 | 
									@compileError("`components` must be a tuple");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var shaders_dir = try std.fs.cwd().openDir(shaders_sub_path, .{
 | 
								const last_component_index = components.len - 1;
 | 
				
			||||||
			.iterate = true,
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		defer shaders_dir.close();
 | 
								inline for (components, 0 .. components.len) |component, i| {
 | 
				
			||||||
 | 
									path.appendSlice(component) catch {
 | 
				
			||||||
		var shaders_walker = try shaders_dir.walk(b.allocator);
 | 
										return error.PathTooBig;
 | 
				
			||||||
 | 
					 | 
				
			||||||
		defer shaders_walker.deinit();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const Shader = struct {
 | 
					 | 
				
			||||||
			source_sub_path: SubPath = .{},
 | 
					 | 
				
			||||||
			binary_sub_path: SubPath = .{},
 | 
					 | 
				
			||||||
				};
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var pending_shaders = std.ArrayList(Shader).init(b.allocator);
 | 
									if (i < last_component_index and !std.mem.endsWith(u8, component, "/")) {
 | 
				
			||||||
 | 
										path.append('/') catch {
 | 
				
			||||||
		defer pending_shaders.deinit();
 | 
											return error.PathTooBig;
 | 
				
			||||||
 | 
					 | 
				
			||||||
		scan_shaders: while (try shaders_walker.next()) |entry| {
 | 
					 | 
				
			||||||
			if (entry.kind != .file) {
 | 
					 | 
				
			||||||
				continue: scan_shaders;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const is_shader_file = std.mem.endsWith(u8, entry.path, ".frag") or std.mem.endsWith(u8, entry.path, ".vert");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (!is_shader_file) {
 | 
					 | 
				
			||||||
				continue: scan_shaders;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const shader_name = std.fs.path.stem(entry.path);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			for (pending_shaders.items) |pending_shader| {
 | 
					 | 
				
			||||||
				if (std.mem.endsWith(u8, pending_shader.source_sub_path.utf8(), shader_name)) {
 | 
					 | 
				
			||||||
					continue: scan_shaders;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			var shader = Shader{};
 | 
					 | 
				
			||||||
			const source_sub_path = try std.fmt.bufPrint(&shader.source_sub_path.buffer, "{s}{s}", .{shaders_sub_path, shader_name});
 | 
					 | 
				
			||||||
			const binary_sub_path = try std.fmt.bufPrint(&shader.binary_sub_path.buffer, "{s}.spv", .{source_sub_path});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			shaders_dir.access(std.fs.path.basename(binary_sub_path), .{.mode = .read_only}) catch {
 | 
					 | 
				
			||||||
				shader.source_sub_path.unused -= @intCast(source_sub_path.len);
 | 
					 | 
				
			||||||
				shader.binary_sub_path.unused -= @intCast(binary_sub_path.len);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try pending_shaders.append(shader);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				continue: scan_shaders;
 | 
					 | 
				
			||||||
					};
 | 
										};
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if ((try shaders_dir.statFile(entry.basename)).mtime > (try shaders_dir.statFile(std.fs.path.basename(binary_sub_path))).mtime) {
 | 
							else => {
 | 
				
			||||||
				shader.source_sub_path.unused -= @intCast(source_sub_path.len);
 | 
								@compileError("`components` cannot be a " ++ @typeName(Components));
 | 
				
			||||||
				shader.binary_sub_path.unused -= @intCast(binary_sub_path.len);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				try pending_shaders.append(shader);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				continue: scan_shaders;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (pending_shaders.items) |pending_shader| {
 | 
						return .{
 | 
				
			||||||
			var vertex_binary_sub_path = SubPath{};
 | 
							.unused = SubPath.max - path.len,
 | 
				
			||||||
			var fragment_binary_sub_path = SubPath{};
 | 
							.buffer = path.buffer,
 | 
				
			||||||
			const source_sub_path_utf8 = pending_shader.source_sub_path.utf8();
 | 
						};
 | 
				
			||||||
 | 
					 | 
				
			||||||
			vertex_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&vertex_binary_sub_path.buffer, "{s}.vert.spv", .{source_sub_path_utf8})).len);
 | 
					 | 
				
			||||||
			fragment_binary_sub_path.unused -= @intCast((try std.fmt.bufPrint(&fragment_binary_sub_path.buffer, "{s}.frag.spv", .{source_sub_path_utf8})).len);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const vertex_binary_sub_path_utf8 = vertex_binary_sub_path.utf8();
 | 
					 | 
				
			||||||
			const fragment_binary_sub_path_utf8 = fragment_binary_sub_path.utf8();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			exe.step.dependOn(compile_vertex: {
 | 
					 | 
				
			||||||
				const compile_command = b.addSystemCommand(&.{
 | 
					 | 
				
			||||||
					"glslangValidator",
 | 
					 | 
				
			||||||
					"-V",
 | 
					 | 
				
			||||||
					vertex_binary_sub_path_utf8[0 .. vertex_binary_sub_path_utf8.len - 4],
 | 
					 | 
				
			||||||
					"-o",
 | 
					 | 
				
			||||||
					vertex_binary_sub_path_utf8,
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				break: compile_vertex &compile_command.step;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			exe.step.dependOn(compile_fragment: {
 | 
					 | 
				
			||||||
				const compile_command = b.addSystemCommand(&.{
 | 
					 | 
				
			||||||
					"glslangValidator",
 | 
					 | 
				
			||||||
					"-V",
 | 
					 | 
				
			||||||
					fragment_binary_sub_path_utf8[0 .. fragment_binary_sub_path_utf8.len - 4],
 | 
					 | 
				
			||||||
					"-o",
 | 
					 | 
				
			||||||
					fragment_binary_sub_path_utf8,
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				break: compile_fragment &compile_command.step;
 | 
					 | 
				
			||||||
			});
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		break: add exe;
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,6 +1,8 @@
 | 
				
			|||||||
.{
 | 
					.{
 | 
				
			||||||
	.name = "Ona",
 | 
						.name = "ona",
 | 
				
			||||||
	.version = "0.0.1",
 | 
						.version = "0.0.1",
 | 
				
			||||||
 | 
						.minimum_zig_version = "0.13.0",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	.paths = .{
 | 
						.paths = .{
 | 
				
			||||||
		"src",
 | 
							"src",
 | 
				
			||||||
		"build.zig",
 | 
							"build.zig",
 | 
				
			||||||
@ -8,10 +10,15 @@
 | 
				
			|||||||
		"LICENSE",
 | 
							"LICENSE",
 | 
				
			||||||
		"README.md",
 | 
							"README.md",
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	.dependencies = .{
 | 
						.dependencies = .{
 | 
				
			||||||
		.sokol = .{
 | 
							.sokol = .{
 | 
				
			||||||
			.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
 | 
								.url = "git+https://github.com/floooh/sokol-zig.git#7c25767e51aa06dd5fb0684e4a8f2200d182ef27",
 | 
				
			||||||
			.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
 | 
								.hash = "1220fa7f47fbaf2f1ed8c17fab2d23b6a85bcbbc4aa0b3802c90a3e8bf6fca1f8569",
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							.@"spirv-cross" = . {
 | 
				
			||||||
 | 
								.path = "./ext/spirv-cross",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										
											BIN
										
									
								
								debug/actor.bmp
									 (Stored with Git LFS)
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								debug/actor.bmp
									 (Stored with Git LFS)
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							@ -1,25 +0,0 @@
 | 
				
			|||||||
#version 430
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
layout (binding = 0) uniform sampler2D sprite;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
layout (location = 0) in vec4 color;
 | 
					 | 
				
			||||||
layout (location = 1) in vec2 uv;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
layout (location = 0) out vec4 texel;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
layout (binding = 0) uniform Effect {
 | 
					 | 
				
			||||||
	float effect_magnitude;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void main() {
 | 
					 | 
				
			||||||
	vec4 color1 = texture(sprite, uv) / 3.0;
 | 
					 | 
				
			||||||
    vec4 color2 = texture(sprite, uv + 0.002 * effect_magnitude) / 3.0;
 | 
					 | 
				
			||||||
    vec4 color3 = texture(sprite, uv - 0.002 * effect_magnitude) / 3.0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    color1 *= 2.0;
 | 
					 | 
				
			||||||
    color2.g = 0.0;
 | 
					 | 
				
			||||||
    color2.b = 0.0;
 | 
					 | 
				
			||||||
    color3.r = 0.0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    texel = color * (color1 + color2 + color3);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,5 +1,7 @@
 | 
				
			|||||||
#version 430
 | 
					#version 430
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Adapted from: https://www.shadertoy.com/view/4sf3Dr
 | 
				
			||||||
 | 
					
 | 
				
			||||||
layout (binding = 0) uniform sampler2D sprite;
 | 
					layout (binding = 0) uniform sampler2D sprite;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
layout (location = 0) in vec4 color;
 | 
					layout (location = 0) in vec4 color;
 | 
				
			||||||
@ -13,14 +15,12 @@ layout (binding = 0) uniform Effect {
 | 
				
			|||||||
	float time;
 | 
						float time;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
vec3 scanline(vec2 coord, vec3 screen)
 | 
					vec3 scanline(vec2 coord, vec3 screen) {
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02;
 | 
						screen.rgb -= sin((coord.y + (time * 29.0))) * 0.02;
 | 
				
			||||||
	return screen;
 | 
						return screen;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
vec2 crt(vec2 coord, float bend)
 | 
					vec2 crt(vec2 coord, float bend) {
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// put in symmetrical coords
 | 
						// put in symmetrical coords
 | 
				
			||||||
	coord = (coord - 0.5) * 2.0;
 | 
						coord = (coord - 0.5) * 2.0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -36,17 +36,13 @@ vec2 crt(vec2 coord, float bend)
 | 
				
			|||||||
	return coord;
 | 
						return coord;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main()
 | 
					void main() {
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	vec2 crtCoords = crt(uv, 4.8);
 | 
						vec2 crtCoords = crt(uv, 4.8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Split the color channels
 | 
						// Split the color channels
 | 
				
			||||||
	texel.rgb = texture(sprite, crtCoords).rgb;
 | 
						texel.rgb = texture(sprite, crtCoords).rgb;
 | 
				
			||||||
	texel.a = 1;
 | 
						texel.a = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// HACK: this bend produces a shitty moire pattern.
 | 
					 | 
				
			||||||
	// Up the bend for the scanline
 | 
					 | 
				
			||||||
	vec2 screenSpace = crtCoords * vec2(screen_width, screen_height);
 | 
						vec2 screenSpace = crtCoords * vec2(screen_width, screen_height);
 | 
				
			||||||
	texel.rgb = scanline(screenSpace, texel.rgb);
 | 
						texel.rgb = scanline(screenSpace, texel.rgb);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
							
								
								
									
										
											BIN
										
									
								
								demos/crt.frag.spv
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								demos/crt.frag.spv
									 (Stored with Git LFS)
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										92
									
								
								demos/effects.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								demos/effects.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					const coral = @import("coral");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ona = @import("ona");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const CRT = extern struct {
 | 
				
			||||||
 | 
						width: f32,
 | 
				
			||||||
 | 
						height: f32,
 | 
				
			||||||
 | 
						time: f32,
 | 
				
			||||||
 | 
						padding: [4]u8 = undefined,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Effects = struct {
 | 
				
			||||||
 | 
						render_texture: coral.Texture = .default,
 | 
				
			||||||
 | 
						crt_effect: coral.Effect = .default,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const main = ona.start(setup, .{
 | 
				
			||||||
 | 
						.tick_rate = 60,
 | 
				
			||||||
 | 
						.execution = .{.thread_share = 0.1},
 | 
				
			||||||
 | 
						.middleware = &.{coral.setup},
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn load(display: ona.Write(coral.Display), actors: ona.Write(Effects), assets: ona.Write(coral.Assets)) !void {
 | 
				
			||||||
 | 
						display.state.width, display.state.height = .{1280, 720};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						actors.state.render_texture = try assets.state.load_texture(.{
 | 
				
			||||||
 | 
							.format = .rgba8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							.access = .{
 | 
				
			||||||
 | 
								.render = .{
 | 
				
			||||||
 | 
									.width = display.state.width,
 | 
				
			||||||
 | 
									.height = display.state.height,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						actors.state.crt_effect = try assets.state.load_effect_file(ona.files.bundle, "./crt.frag.spv");
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn render(commands: coral.Commands, effects: ona.Write(Effects), app: ona.Read(ona.App), display: ona.Write(coral.Display)) !void {
 | 
				
			||||||
 | 
						try commands.set_target(.{
 | 
				
			||||||
 | 
							.texture = effects.state.render_texture,
 | 
				
			||||||
 | 
							.clear_color = coral.colors.black,
 | 
				
			||||||
 | 
							.clear_depth = 0,
 | 
				
			||||||
 | 
							.clear_stencil = 0,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const display_width: f32 = @floatFromInt(display.state.width);
 | 
				
			||||||
 | 
						const display_height: f32 = @floatFromInt(display.state.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const display_transform = coral.Transform2D{
 | 
				
			||||||
 | 
							.origin = .{display_width / 2, display_height / 2},
 | 
				
			||||||
 | 
							.xbasis = .{display_width, 0},
 | 
				
			||||||
 | 
							.ybasis = .{0, display_height},
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try commands.draw_texture(.{
 | 
				
			||||||
 | 
							.texture = .default,
 | 
				
			||||||
 | 
							.transform = display_transform,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try commands.set_effect(.{
 | 
				
			||||||
 | 
							.effect = effects.state.crt_effect,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							.properties = std.mem.asBytes(&CRT{
 | 
				
			||||||
 | 
								.width = display_width,
 | 
				
			||||||
 | 
								.height = display_height,
 | 
				
			||||||
 | 
								.time = @floatCast(app.state.elapsed_time),
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try commands.set_target(.{
 | 
				
			||||||
 | 
							.texture = .backbuffer,
 | 
				
			||||||
 | 
							.clear_color = null,
 | 
				
			||||||
 | 
							.clear_depth = null,
 | 
				
			||||||
 | 
							.clear_stencil = null,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try commands.draw_texture(.{
 | 
				
			||||||
 | 
							.texture = effects.state.render_texture,
 | 
				
			||||||
 | 
							.transform = display_transform,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn setup(world: *ona.World, events: ona.App.Events) !void {
 | 
				
			||||||
 | 
						try world.set_state(Effects{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try world.on_event(events.load, ona.system_fn(load), .{.label = "load"});
 | 
				
			||||||
 | 
						try world.on_event(events.render, ona.system_fn(render), .{.label = "render actors"});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
const coral = @import("coral");
 | 
					const coral = @import("./coral.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const gfx = @import("../gfx.zig");
 | 
					const ona = @import("ona");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sokol = @import("sokol");
 | 
					const sokol = @import("sokol");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -14,18 +14,18 @@ textures: TexturePool,
 | 
				
			|||||||
pub const Effect = struct {
 | 
					pub const Effect = struct {
 | 
				
			||||||
	shader: sokol.gfx.Shader,
 | 
						shader: sokol.gfx.Shader,
 | 
				
			||||||
	pipeline: sokol.gfx.Pipeline,
 | 
						pipeline: sokol.gfx.Pipeline,
 | 
				
			||||||
	properties: []coral.io.Byte,
 | 
						properties: []u8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn deinit(self: *Effect) void {
 | 
						pub fn deinit(self: *Effect) void {
 | 
				
			||||||
		coral.heap.allocator.free(self.properties);
 | 
							ona.heap.allocator.free(self.properties);
 | 
				
			||||||
		sokol.gfx.destroyPipeline(self.pipeline);
 | 
							sokol.gfx.destroyPipeline(self.pipeline);
 | 
				
			||||||
		sokol.gfx.destroyShader(self.shader);
 | 
							sokol.gfx.destroyShader(self.shader);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.* = undefined;
 | 
							self.* = undefined;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn init(desc: gfx.Effect.Desc) spirv.Error!Effect {
 | 
						pub fn init(desc: coral.Effect.Desc) spirv.Error!Effect {
 | 
				
			||||||
		var spirv_arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
 | 
							var spirv_arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		defer {
 | 
							defer {
 | 
				
			||||||
			spirv_arena.deinit();
 | 
								spirv_arena.deinit();
 | 
				
			||||||
@ -80,13 +80,13 @@ pub const Effect = struct {
 | 
				
			|||||||
			break: pipeline_desc pipeline_desc;
 | 
								break: pipeline_desc pipeline_desc;
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const properties = try coral.heap.allocator.alloc(
 | 
							const properties = try ona.heap.allocator.alloc(
 | 
				
			||||||
			coral.io.Byte,
 | 
								u8,
 | 
				
			||||||
			if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
 | 
								if (spirv_program.fragment_stage.has_uniform_blocks[0]) |uniform_block| uniform_block.size() else 0,
 | 
				
			||||||
		);
 | 
							);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		errdefer {
 | 
							errdefer {
 | 
				
			||||||
			coral.heap.allocator.free(properties);
 | 
								ona.heap.allocator.free(properties);
 | 
				
			||||||
			sokol.gfx.destroyPipeline(pipeline);
 | 
								sokol.gfx.destroyPipeline(pipeline);
 | 
				
			||||||
			sokol.gfx.destroyShader(shader);
 | 
								sokol.gfx.destroyShader(shader);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -239,7 +239,7 @@ pub const Effect = struct {
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const EffectPool = coral.Pool(Effect);
 | 
					const EffectPool = ona.Pool(Effect);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Self = @This();
 | 
					const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -282,7 +282,7 @@ pub const Texture = struct {
 | 
				
			|||||||
		self.* = undefined;
 | 
							self.* = undefined;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn init(desc: gfx.Texture.Desc) std.mem.Allocator.Error!Texture {
 | 
						pub fn init(desc: coral.Texture.Desc) std.mem.Allocator.Error!Texture {
 | 
				
			||||||
		const pixel_format = switch (desc.format) {
 | 
							const pixel_format = switch (desc.format) {
 | 
				
			||||||
			.rgba8 => sokol.gfx.PixelFormat.RGBA8,
 | 
								.rgba8 => sokol.gfx.PixelFormat.RGBA8,
 | 
				
			||||||
			.bgra8 => sokol.gfx.PixelFormat.BGRA8,
 | 
								.bgra8 => sokol.gfx.PixelFormat.BGRA8,
 | 
				
			||||||
@ -384,9 +384,9 @@ pub const Texture = struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TexturePool = coral.Pool(Texture);
 | 
					const TexturePool = ona.Pool(Texture);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect {
 | 
					pub fn create_effect(self: *Self, desc: coral.Effect.Desc) !coral.Effect {
 | 
				
			||||||
	var effect = try Effect.init(desc);
 | 
						var effect = try Effect.init(desc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	errdefer effect.deinit();
 | 
						errdefer effect.deinit();
 | 
				
			||||||
@ -394,7 +394,7 @@ pub fn create_effect(self: *Self, desc: gfx.Effect.Desc) !gfx.Effect {
 | 
				
			|||||||
	return @enumFromInt(try self.effects.insert(effect));
 | 
						return @enumFromInt(try self.effects.insert(effect));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn create_texture(self: *Self, desc: gfx.Texture.Desc) !gfx.Texture {
 | 
					pub fn create_texture(self: *Self, desc: coral.Texture.Desc) !coral.Texture {
 | 
				
			||||||
	var texture = try Texture.init(desc);
 | 
						var texture = try Texture.init(desc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	errdefer texture.deinit();
 | 
						errdefer texture.deinit();
 | 
				
			||||||
@ -422,7 +422,7 @@ pub fn deinit(self: *Self) void {
 | 
				
			|||||||
	self.* = undefined;
 | 
						self.* = undefined;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
 | 
					pub fn destroy_effect(self: *Self, handle: coral.Effect) bool {
 | 
				
			||||||
	switch (handle) {
 | 
						switch (handle) {
 | 
				
			||||||
		.default => {},
 | 
							.default => {},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -438,7 +438,7 @@ pub fn destroy_effect(self: *Self, handle: gfx.Effect) bool {
 | 
				
			|||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
 | 
					pub fn destroy_texture(self: *Self, handle: coral.Texture) bool {
 | 
				
			||||||
	switch (handle) {
 | 
						switch (handle) {
 | 
				
			||||||
		.default => {},
 | 
							.default => {},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -454,18 +454,18 @@ pub fn destroy_texture(self: *Self, handle: gfx.Texture) bool {
 | 
				
			|||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn get_effect(self: *Self, handle: gfx.Effect) ?*Effect {
 | 
					pub fn get_effect(self: *Self, handle: coral.Effect) ?*Effect {
 | 
				
			||||||
	return self.effects.get(@intFromEnum(handle));
 | 
						return self.effects.get(@intFromEnum(handle));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn get_texture(self: *Self, handle: gfx.Texture) ?*Texture {
 | 
					pub fn get_texture(self: *Self, handle: coral.Texture) ?*Texture {
 | 
				
			||||||
	return self.textures.get(@intFromEnum(handle));
 | 
						return self.textures.get(@intFromEnum(handle));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn init() !Self {
 | 
					pub fn init() !Self {
 | 
				
			||||||
	var pools = Self{
 | 
						var pools = Self{
 | 
				
			||||||
		.effects = EffectPool.init(coral.heap.allocator),
 | 
							.effects = EffectPool.init(ona.heap.allocator),
 | 
				
			||||||
		.textures = TexturePool.init(coral.heap.allocator),
 | 
							.textures = TexturePool.init(ona.heap.allocator),
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	errdefer {
 | 
						errdefer {
 | 
				
			||||||
@ -478,11 +478,11 @@ pub fn init() !Self {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.is_handle(gfx.Effect.default, try pools.create_effect(.{
 | 
						assert.is_handle(coral.Effect.default, try pools.create_effect(.{
 | 
				
			||||||
		.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
 | 
							.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")),
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.is_handle(gfx.Texture.default, try pools.create_texture(.{
 | 
						assert.is_handle(coral.Texture.default, try pools.create_texture(.{
 | 
				
			||||||
		.format = .rgba8,
 | 
							.format = .rgba8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		.access = .{
 | 
							.access = .{
 | 
				
			||||||
@ -503,7 +503,7 @@ pub fn init() !Self {
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	}));
 | 
						}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	assert.is_handle(gfx.Texture.backbuffer, try pools.create_texture(.{
 | 
						assert.is_handle(coral.Texture.backbuffer, try pools.create_texture(.{
 | 
				
			||||||
		.format = .rgba8,
 | 
							.format = .rgba8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		.access = .{
 | 
							.access = .{
 | 
				
			||||||
							
								
								
									
										13
									
								
								src/coral/colors.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/coral/colors.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					const coral = @import("./coral.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const black = greyscale(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const white = greyscale(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn greyscale(v: f32) coral.Color {
 | 
				
			||||||
 | 
						return .{v, v, v, 1};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn rgb(r: f32, g: f32, b: f32) coral.Color {
 | 
				
			||||||
 | 
						return .{r, g, b, 1};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,125 +1,524 @@
 | 
				
			|||||||
pub const ascii = @import("./ascii.zig");
 | 
					pub const colors = @import("./colors.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const asyncio = @import("./asyncio.zig");
 | 
					const input = @import("input");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const files = @import("./files.zig");
 | 
					const ona = @import("ona");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const hashes = @import("./hashes.zig");
 | 
					const ext = @cImport({
 | 
				
			||||||
 | 
						@cInclude("SDL2/SDL.h");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const heap = @import("./heap.zig");
 | 
					const rendering = @import("./rendering.zig");
 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const io = @import("./io.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const lina = @import("./lina.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const map = @import("./map.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const scalars = @import("./scalars.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const slices = @import("./slices.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const stack = @import("./stack.zig");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const utf8 = @import("./utf8.zig");
 | 
					pub const Assets = struct {
 | 
				
			||||||
 | 
						window: *ext.SDL_Window,
 | 
				
			||||||
 | 
						texture_formats: ona.stack.Sequential(TextureFormat),
 | 
				
			||||||
 | 
						frame_rendered: std.Thread.ResetEvent = .{},
 | 
				
			||||||
 | 
						pending_work: WorkQueue = .{},
 | 
				
			||||||
 | 
						has_worker_thread: ?std.Thread = null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn Pool(comptime Value: type) type {
 | 
						pub const LoadError = std.mem.Allocator.Error;
 | 
				
			||||||
	return struct {
 | 
					 | 
				
			||||||
		entries: stack.Sequential(Entry),
 | 
					 | 
				
			||||||
		first_free_index: usize = 0,
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const Entry = union (enum) {
 | 
						pub const LoadFileError = LoadError || ona.files.ReadAllError || error {
 | 
				
			||||||
			free_index: usize,
 | 
							FormatUnsupported,
 | 
				
			||||||
			occupied: Value,
 | 
					 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub const Values = struct {
 | 
						pub const TextureFormat = struct {
 | 
				
			||||||
			cursor: usize = 0,
 | 
							extension: []const u8,
 | 
				
			||||||
			pool: *const Self,
 | 
							load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc,
 | 
				
			||||||
 | 
					 | 
				
			||||||
			pub fn next(self: *Values) ?*Value {
 | 
					 | 
				
			||||||
				while (self.cursor < self.pool.entries.len()) {
 | 
					 | 
				
			||||||
					defer self.cursor += 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					switch (self.pool.entries.values[self.cursor]) {
 | 
					 | 
				
			||||||
						.free_index => {
 | 
					 | 
				
			||||||
							continue;
 | 
					 | 
				
			||||||
						},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						.occupied => |*occupied| {
 | 
					 | 
				
			||||||
							return occupied;
 | 
					 | 
				
			||||||
						},
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				return null;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const Self = @This();
 | 
						pub const WorkQueue = ona.asyncio.BlockingQueue(1024, union (enum) {
 | 
				
			||||||
 | 
							load_effect: LoadEffectWork,
 | 
				
			||||||
 | 
							load_texture: LoadTextureWork,
 | 
				
			||||||
 | 
							render_frame: RenderFrameWork,
 | 
				
			||||||
 | 
							shutdown,
 | 
				
			||||||
 | 
							unload_effect: UnloadEffectWork,
 | 
				
			||||||
 | 
							unload_texture: UnloadTextureWork,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn deinit(self: *Self) void {
 | 
							const LoadEffectWork = struct {
 | 
				
			||||||
			self.entries.deinit();
 | 
								desc: Effect.Desc,
 | 
				
			||||||
 | 
								loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const LoadTextureWork = struct {
 | 
				
			||||||
 | 
								desc: Texture.Desc,
 | 
				
			||||||
 | 
								loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const RenderFrameWork = struct {
 | 
				
			||||||
 | 
								clear_color: Color,
 | 
				
			||||||
 | 
								width: u16,
 | 
				
			||||||
 | 
								height: u16,
 | 
				
			||||||
 | 
								finished: *std.Thread.ResetEvent,
 | 
				
			||||||
 | 
								has_command_params: ?*ona.Params(Commands).Node,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const UnloadEffectWork = struct {
 | 
				
			||||||
 | 
								handle: Effect,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const UnloadTextureWork = struct {
 | 
				
			||||||
 | 
								handle: Texture,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fn deinit(self: *Assets) void {
 | 
				
			||||||
 | 
							self.pending_work.enqueue(.shutdown);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (self.has_worker_thread) |worker_thread| {
 | 
				
			||||||
 | 
								worker_thread.join();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.texture_formats.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.* = undefined;
 | 
							self.* = undefined;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn get(self: *Self, key: usize) ?*Value {
 | 
						fn init() !Assets {
 | 
				
			||||||
			return switch (self.entries.values[key]) {
 | 
							const window = create: {
 | 
				
			||||||
				.free_index => null,
 | 
								const position = ext.SDL_WINDOWPOS_CENTERED;
 | 
				
			||||||
				.occupied => |*occupied| occupied,
 | 
								const flags = ext.SDL_WINDOW_OPENGL;
 | 
				
			||||||
 | 
								const width = 640;
 | 
				
			||||||
 | 
								const height = 480;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
 | 
				
			||||||
 | 
									return error.Unsupported;
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							errdefer {
 | 
				
			||||||
 | 
								ext.SDL_DestroyWindow(window);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn init(allocator: std.mem.Allocator) Self {
 | 
					 | 
				
			||||||
		return .{
 | 
							return .{
 | 
				
			||||||
				.entries = .{.allocator = allocator},
 | 
								.texture_formats = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
 | 
								.window = window,
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
 | 
						pub fn load_effect_file(self: *Assets, storage: ona.files.Storage, path: []const u8) LoadFileError!Effect {
 | 
				
			||||||
			const entries_count = self.entries.len();
 | 
							if (!std.mem.endsWith(u8, path, ".spv")) {
 | 
				
			||||||
 | 
								return error.FormatUnsupported;
 | 
				
			||||||
			if (self.first_free_index == entries_count) {
 | 
					 | 
				
			||||||
				try self.entries.push_grow(.{.occupied = value});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				self.first_free_index += 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				return entries_count;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			const insersion_index = self.first_free_index;
 | 
							const fragment_file_stat = try storage.stat(path);
 | 
				
			||||||
 | 
							const fragment_spirv_ops = try ona.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			self.first_free_index = self.entries.values[self.first_free_index].free_index;
 | 
							defer {
 | 
				
			||||||
			self.entries.values[insersion_index] = .{.occupied = value};
 | 
								ona.heap.allocator.free(fragment_spirv_ops);
 | 
				
			||||||
 | 
					 | 
				
			||||||
			return insersion_index;
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn remove(self: *Self, key: usize) ?Value {
 | 
							const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{});
 | 
				
			||||||
			if (key >= self.entries.len()) {
 | 
					 | 
				
			||||||
				return null;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			switch (self.entries.values[key]) {
 | 
							std.debug.assert(bytes_read.len == fragment_file_stat.size);
 | 
				
			||||||
				.free_index => {
 | 
					
 | 
				
			||||||
					return null;
 | 
							var loaded = ona.asyncio.Future(std.mem.Allocator.Error!Effect){};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.pending_work.enqueue(.{
 | 
				
			||||||
 | 
								.load_effect = .{
 | 
				
			||||||
 | 
									.desc = .{
 | 
				
			||||||
 | 
										.fragment_spirv_ops = fragment_spirv_ops,
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				.occupied => |occupied| {
 | 
									.loaded = &loaded,
 | 
				
			||||||
					self.entries.values[key] = .{.free_index = self.first_free_index};
 | 
					 | 
				
			||||||
					self.first_free_index = key;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					return occupied;
 | 
					 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return loaded.get().*;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn load_texture(self: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture {
 | 
				
			||||||
 | 
							var loaded = ona.asyncio.Future(std.mem.Allocator.Error!Texture){};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.pending_work.enqueue(.{
 | 
				
			||||||
 | 
								.load_texture = .{
 | 
				
			||||||
 | 
									.desc = desc,
 | 
				
			||||||
 | 
									.loaded = &loaded,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return loaded.get().*;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn load_texture_file(self: *Assets, storage: ona.files.Storage, path: []const u8) LoadFileError!Texture {
 | 
				
			||||||
 | 
							var arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							defer {
 | 
				
			||||||
 | 
								arena.deinit();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (self.texture_formats.values) |format| {
 | 
				
			||||||
 | 
								if (!std.mem.endsWith(u8, path, format.extension)) {
 | 
				
			||||||
 | 
									continue;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return self.load_texture(try format.load_file(&arena, storage, path));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return error.FormatUnsupported;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const thread_restriction = .main;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Color = @Vector(4, f32);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Commands = struct {
 | 
				
			||||||
 | 
						pending: *List,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const Command = union (enum) {
 | 
				
			||||||
 | 
							draw_texture: DrawTextureCommand,
 | 
				
			||||||
 | 
							set_effect: SetEffectCommand,
 | 
				
			||||||
 | 
							set_target: SetTargetCommand,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const DrawTextureCommand = struct {
 | 
				
			||||||
 | 
							texture: Texture,
 | 
				
			||||||
 | 
							transform: Transform2D,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const SetEffectCommand = struct {
 | 
				
			||||||
 | 
							effect: Effect,
 | 
				
			||||||
 | 
							properties: []const u8,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const SetTargetCommand = struct {
 | 
				
			||||||
 | 
							texture: Texture,
 | 
				
			||||||
 | 
							clear_color: ?Color,
 | 
				
			||||||
 | 
							clear_depth: ?f32,
 | 
				
			||||||
 | 
							clear_stencil: ?u8,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const List = struct {
 | 
				
			||||||
 | 
							arena: std.heap.ArenaAllocator,
 | 
				
			||||||
 | 
							stack: ona.stack.Sequential(Command),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn clear(self: *List) void {
 | 
				
			||||||
 | 
								self.stack.clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!self.arena.reset(.retain_capacity)) {
 | 
				
			||||||
 | 
									std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn values(self: *const Self) Values {
 | 
							fn deinit(self: *List) void {
 | 
				
			||||||
 | 
								self.arena.deinit();
 | 
				
			||||||
 | 
								self.stack.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.* = undefined;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn init(allocator: std.mem.Allocator) List {
 | 
				
			||||||
			return .{
 | 
								return .{
 | 
				
			||||||
				.pool = self,
 | 
									.arena = std.heap.ArenaAllocator.init(allocator),
 | 
				
			||||||
 | 
									.stack = .{.allocator = allocator},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const Param = struct {
 | 
				
			||||||
 | 
							swap_lists: [2]List,
 | 
				
			||||||
 | 
							swap_state: u1 = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn deinit(self: *Param) void {
 | 
				
			||||||
 | 
								for (&self.swap_lists) |*list| {
 | 
				
			||||||
 | 
									list.deinit();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.* = undefined;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn pending_list(self: *Param) *List {
 | 
				
			||||||
 | 
								return &self.swap_lists[self.swap_state];
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn rotate(self: *Param) void {
 | 
				
			||||||
 | 
								const swapped_state = self.swap_state ^ 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.swap_lists[swapped_state].clear();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.swap_state = swapped_state;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn submitted_commands(self: Param) []const Command {
 | 
				
			||||||
 | 
								return self.swap_lists[self.swap_state ^ 1].stack.values;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param {
 | 
				
			||||||
 | 
							return .{
 | 
				
			||||||
 | 
								.swap_lists = .{
 | 
				
			||||||
 | 
									List.init(ona.heap.allocator),
 | 
				
			||||||
 | 
									List.init(ona.heap.allocator),
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn init(param: *Param) Commands {
 | 
				
			||||||
 | 
							return .{
 | 
				
			||||||
 | 
								.pending = param.pending_list(),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
							try self.pending.stack.push_grow(.{.draw_texture = command});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
							try self.pending.stack.push_grow(.{
 | 
				
			||||||
 | 
								.set_effect = .{
 | 
				
			||||||
 | 
									.properties = try self.pending.arena.allocator().dupe(u8, command.properties),
 | 
				
			||||||
 | 
									.effect = command.effect,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn unbind(param: *Param, _: ona.World.UnbindContext) void {
 | 
				
			||||||
 | 
							param.deinit();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
							try self.pending.stack.push_grow(.{.set_target = command});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Display = struct {
 | 
				
			||||||
 | 
						width: u16 = 1280,
 | 
				
			||||||
 | 
						height: u16 = 720,
 | 
				
			||||||
 | 
						clear_color: Color = colors.black,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Effect = enum (u32) {
 | 
				
			||||||
 | 
						default,
 | 
				
			||||||
 | 
						_,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const Desc = struct {
 | 
				
			||||||
 | 
							fragment_spirv_ops: []const u32,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Rect = struct {
 | 
				
			||||||
 | 
						left: f32,
 | 
				
			||||||
 | 
						top: f32,
 | 
				
			||||||
 | 
						right: f32,
 | 
				
			||||||
 | 
						bottom: f32,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Texture = enum (u32) {
 | 
				
			||||||
 | 
						default,
 | 
				
			||||||
 | 
						backbuffer,
 | 
				
			||||||
 | 
						_,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const Desc = struct {
 | 
				
			||||||
 | 
							format: Format,
 | 
				
			||||||
 | 
							access: Access,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const Access = union (enum) {
 | 
				
			||||||
 | 
								static: StaticAccess,
 | 
				
			||||||
 | 
								render: RenderAccess,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const StaticAccess = struct {
 | 
				
			||||||
 | 
								width: u16,
 | 
				
			||||||
 | 
								data: []const u8,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const RenderAccess = struct {
 | 
				
			||||||
 | 
								width: u16,
 | 
				
			||||||
 | 
								height: u16,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const Format = enum {
 | 
				
			||||||
 | 
							rgba8,
 | 
				
			||||||
 | 
							bgra8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn byte_size(self: Format) usize {
 | 
				
			||||||
 | 
								return switch (self) {
 | 
				
			||||||
 | 
									.rgba8, .bgra8 => 4,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Transform2D = extern struct {
 | 
				
			||||||
 | 
						xbasis: Vector = .{1, 0},
 | 
				
			||||||
 | 
						ybasis: Vector = .{0, 1},
 | 
				
			||||||
 | 
						origin: Vector = @splat(0),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const Vector = @Vector(2, f32);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn load_bmp_texture(arena: *std.heap.ArenaAllocator, storage: ona.files.Storage, path: []const u8) !Texture.Desc {
 | 
				
			||||||
 | 
						const header = try storage.read_little(path, 0, extern struct {
 | 
				
			||||||
 | 
							type: [2]u8 align (1),
 | 
				
			||||||
 | 
							file_size: u32 align (1),
 | 
				
			||||||
 | 
							reserved: [2]u16 align (1),
 | 
				
			||||||
 | 
							image_offset: u32 align (1),
 | 
				
			||||||
 | 
							header_size: u32 align (1),
 | 
				
			||||||
 | 
							pixel_width: i32 align (1),
 | 
				
			||||||
 | 
							pixel_height: i32 align (1),
 | 
				
			||||||
 | 
							color_planes: u16 align (1),
 | 
				
			||||||
 | 
							bits_per_pixel: u16 align (1),
 | 
				
			||||||
 | 
							compression_method: u32 align (1),
 | 
				
			||||||
 | 
							image_size: u32 align(1),
 | 
				
			||||||
 | 
							pixels_per_meter_x: i32 align (1),
 | 
				
			||||||
 | 
							pixels_per_meter_y: i32 align (1),
 | 
				
			||||||
 | 
							palette_colors_used: u32 align (1),
 | 
				
			||||||
 | 
							important_colors_used: u32 align (1),
 | 
				
			||||||
 | 
						}) orelse {
 | 
				
			||||||
 | 
							return error.FormatUnsupported;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!std.mem.eql(u8, &header.type, "BM")) {
 | 
				
			||||||
 | 
							return error.FormatUnsupported;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
 | 
				
			||||||
 | 
							return error.FormatUnsupported;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const pixels = try arena.allocator().alloc(u8, header.image_size);
 | 
				
			||||||
 | 
						const bytes_per_pixel = header.bits_per_pixel / @bitSizeOf(u8);
 | 
				
			||||||
 | 
						const alignment = 4;
 | 
				
			||||||
 | 
						const byte_stride = pixel_width * bytes_per_pixel;
 | 
				
			||||||
 | 
						const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
 | 
				
			||||||
 | 
						const byte_padding = ona.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
 | 
				
			||||||
 | 
						var buffer_offset: usize = 0;
 | 
				
			||||||
 | 
						var file_offset = @as(usize, header.image_offset);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch (header.bits_per_pixel) {
 | 
				
			||||||
 | 
							32 => {
 | 
				
			||||||
 | 
								while (buffer_offset < pixels.len) {
 | 
				
			||||||
 | 
									const line = pixels[buffer_offset .. buffer_offset + byte_stride];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (try storage.read(path, line, file_offset) != byte_stride) {
 | 
				
			||||||
 | 
										return error.FormatUnsupported;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									for (0 .. pixel_width) |i| {
 | 
				
			||||||
 | 
										const line_offset = i * 4;
 | 
				
			||||||
 | 
										const pixel = line[line_offset .. line_offset + 4];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										std.mem.swap(u8, &pixel[0], &pixel[2]);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									file_offset += line.len + byte_padding;
 | 
				
			||||||
 | 
									buffer_offset += padded_byte_stride;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							else => return error.FormatUnsupported,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return .{
 | 
				
			||||||
 | 
							.format = .rgba8,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							.access = .{
 | 
				
			||||||
 | 
								.static = .{
 | 
				
			||||||
 | 
									.width = pixel_width,
 | 
				
			||||||
 | 
									.data = pixels,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn poll(app: ona.Write(ona.App), events: ona.Send(input.Event)) !void {
 | 
				
			||||||
 | 
						var event = @as(ext.SDL_Event, undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						while (ext.SDL_PollEvent(&event) != 0) {
 | 
				
			||||||
 | 
							switch (event.type) {
 | 
				
			||||||
 | 
								ext.SDL_QUIT => app.state.quit(),
 | 
				
			||||||
 | 
								ext.SDL_KEYUP => try events.push(.{.key_up = @enumFromInt(event.key.keysym.scancode)}),
 | 
				
			||||||
 | 
								ext.SDL_KEYDOWN => try events.push(.{.key_down = @enumFromInt(event.key.keysym.scancode)}),
 | 
				
			||||||
 | 
								else => {},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
 | 
				
			||||||
 | 
						if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) {
 | 
				
			||||||
 | 
							return error.Unsupported;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const assets = create: {
 | 
				
			||||||
 | 
							var assets = try Assets.init();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							errdefer {
 | 
				
			||||||
 | 
								assets.deinit();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							break: create try world.set_get_state(assets);
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assets.frame_rendered.set();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errdefer {
 | 
				
			||||||
 | 
							assets.deinit();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{
 | 
				
			||||||
 | 
							&assets.pending_work,
 | 
				
			||||||
 | 
							assets.window,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const builtin_texture_formats = [_]Assets.TextureFormat{
 | 
				
			||||||
 | 
							.{
 | 
				
			||||||
 | 
								.extension = "bmp",
 | 
				
			||||||
 | 
								.load_file = load_bmp_texture,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (builtin_texture_formats) |format| {
 | 
				
			||||||
 | 
							try assets.texture_formats.push_grow(format);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try world.set_state(Display{});
 | 
				
			||||||
 | 
						try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll coral"});
 | 
				
			||||||
 | 
						try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop coral"});
 | 
				
			||||||
 | 
						try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize coral"});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn stop(assets: ona.Write(Assets)) void {
 | 
				
			||||||
 | 
						assets.state.deinit();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void {
 | 
				
			||||||
 | 
						const assets, const display = exclusive.states;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						assets.frame_rendered.wait();
 | 
				
			||||||
 | 
						assets.frame_rendered.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							var has_command_param = exclusive.world.get_params(Commands).has_head;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
 | 
				
			||||||
 | 
								command_param.param.rotate();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var display_width, var display_height = [_]c_int{0, 0};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ext.SDL_GL_GetDrawableSize(assets.window, &display_width, &display_height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (display.width != display_width or display.height != display_height) {
 | 
				
			||||||
 | 
							ext.SDL_SetWindowSize(assets.window, display.width, display.height);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (exclusive.world.get_params(Commands).has_head) |command_param| {
 | 
				
			||||||
 | 
							assets.pending_work.enqueue(.{
 | 
				
			||||||
 | 
								.render_frame = .{
 | 
				
			||||||
 | 
									.has_command_params = command_param,
 | 
				
			||||||
 | 
									.width = display.width,
 | 
				
			||||||
 | 
									.height = display.height,
 | 
				
			||||||
 | 
									.clear_color = display.clear_color,
 | 
				
			||||||
 | 
									.finished = &assets.frame_rendered,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							assets.frame_rendered.set();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,29 +1,31 @@
 | 
				
			|||||||
const Resources = @import("./Resources.zig");
 | 
					const Resources = @import("./Resources.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const coral = @import("coral");
 | 
					const coral = @import("./coral.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ext = @import("../ext.zig");
 | 
					const ext = @cImport({
 | 
				
			||||||
 | 
						@cInclude("SDL2/SDL.h");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const gfx = @import("../gfx.zig");
 | 
					const ona = @import("ona");
 | 
				
			||||||
 | 
					 | 
				
			||||||
const spirv = @import("./spirv.zig");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sokol = @import("sokol");
 | 
					const sokol = @import("sokol");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const spirv = @import("./spirv.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Frame = struct {
 | 
					const Frame = struct {
 | 
				
			||||||
	texture_batch_buffers: coral.stack.Sequential(sokol.gfx.Buffer),
 | 
						texture_batch_buffers: ona.stack.Sequential(sokol.gfx.Buffer),
 | 
				
			||||||
	quad_index_buffer: sokol.gfx.Buffer,
 | 
						quad_index_buffer: sokol.gfx.Buffer,
 | 
				
			||||||
	quad_vertex_buffer: sokol.gfx.Buffer,
 | 
						quad_vertex_buffer: sokol.gfx.Buffer,
 | 
				
			||||||
	drawn_count: usize = 0,
 | 
						drawn_count: usize = 0,
 | 
				
			||||||
	flushed_count: usize = 0,
 | 
						flushed_count: usize = 0,
 | 
				
			||||||
	current_source_texture: gfx.Texture = .default,
 | 
						current_source_texture: coral.Texture = .default,
 | 
				
			||||||
	current_target_texture: gfx.Texture = .backbuffer,
 | 
						current_target_texture: coral.Texture = .backbuffer,
 | 
				
			||||||
	current_effect: gfx.Effect = .default,
 | 
						current_effect: coral.Effect = .default,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const DrawTexture = extern struct {
 | 
						const DrawTexture = extern struct {
 | 
				
			||||||
		transform: gfx.Transform2D,
 | 
							transform: coral.Transform2D,
 | 
				
			||||||
		tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
 | 
							tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)),
 | 
				
			||||||
		depth: f32 = 0,
 | 
							depth: f32 = 0,
 | 
				
			||||||
		texture_offset: @Vector(2, f32) = @splat(0),
 | 
							texture_offset: @Vector(2, f32) = @splat(0),
 | 
				
			||||||
@ -65,15 +67,15 @@ const Frame = struct {
 | 
				
			|||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return .{
 | 
							return .{
 | 
				
			||||||
			.texture_batch_buffers = .{.allocator = coral.heap.allocator},
 | 
								.texture_batch_buffers = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
			.quad_index_buffer = quad_index_buffer,
 | 
								.quad_index_buffer = quad_index_buffer,
 | 
				
			||||||
			.quad_vertex_buffer = quad_vertex_buffer,
 | 
								.quad_vertex_buffer = quad_vertex_buffer,
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn draw_texture(self: *Frame, pools: *Resources, command: gfx.Commands.DrawTextureCommand) !void {
 | 
						pub fn draw_texture(self: *Frame, resources: *Resources, command: coral.Commands.DrawTextureCommand) !void {
 | 
				
			||||||
		if (command.texture != self.current_source_texture) {
 | 
							if (command.texture != self.current_source_texture) {
 | 
				
			||||||
			self.flush(pools);
 | 
								self.flush(resources);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.current_source_texture = command.texture;
 | 
							self.current_source_texture = command.texture;
 | 
				
			||||||
@ -99,8 +101,8 @@ const Frame = struct {
 | 
				
			|||||||
		self.drawn_count += 1;
 | 
							self.drawn_count += 1;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn finish(self: *Frame, pools: *Resources) void {
 | 
						pub fn finish(self: *Frame, resources: *Resources) void {
 | 
				
			||||||
		self.flush(pools);
 | 
							self.flush(resources);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.drawn_count = 0;
 | 
							self.drawn_count = 0;
 | 
				
			||||||
		self.flushed_count = 0;
 | 
							self.flushed_count = 0;
 | 
				
			||||||
@ -109,7 +111,7 @@ const Frame = struct {
 | 
				
			|||||||
		self.current_effect = .default;
 | 
							self.current_effect = .default;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn flush(self: *Frame, pools: *Resources) void {
 | 
						pub fn flush(self: *Frame, resources: *Resources) void {
 | 
				
			||||||
		if (self.flushed_count == self.drawn_count) {
 | 
							if (self.flushed_count == self.drawn_count) {
 | 
				
			||||||
			return;
 | 
								return;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -120,7 +122,7 @@ const Frame = struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer;
 | 
							bindings.vertex_buffers[vertex_indices.mesh] = self.quad_vertex_buffer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch (pools.get_texture(self.current_source_texture).?.access) {
 | 
							switch (resources.get_texture(self.current_source_texture).?.access) {
 | 
				
			||||||
			.render => |render| {
 | 
								.render => |render| {
 | 
				
			||||||
				bindings.fs.images[0] = render.color_image;
 | 
									bindings.fs.images[0] = render.color_image;
 | 
				
			||||||
				bindings.fs.samplers[0] = default_sampler;
 | 
									bindings.fs.samplers[0] = default_sampler;
 | 
				
			||||||
@ -136,13 +138,13 @@ const Frame = struct {
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const effect = pools.get_effect(self.current_effect).?;
 | 
							const effect = resources.get_effect(self.current_effect).?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		sokol.gfx.applyPipeline(effect.pipeline);
 | 
							sokol.gfx.applyPipeline(effect.pipeline);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const texture = pools.get_texture(self.current_target_texture).?;
 | 
							const texture = resources.get_texture(self.current_target_texture).?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&coral.lina.orthographic_projection(-1.0, 1.0, .{
 | 
							sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{
 | 
				
			||||||
			.left = 0,
 | 
								.left = 0,
 | 
				
			||||||
			.top = 0,
 | 
								.top = 0,
 | 
				
			||||||
			.right = @floatFromInt(texture.width),
 | 
								.right = @floatFromInt(texture.width),
 | 
				
			||||||
@ -171,19 +173,19 @@ const Frame = struct {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn set_effect(self: *Frame, pools: *Resources, command: gfx.Commands.SetEffectCommand) void {
 | 
						pub fn set_effect(self: *Frame, resources: *Resources, command: coral.Commands.SetEffectCommand) void {
 | 
				
			||||||
		if (command.effect != self.current_effect) {
 | 
							if (command.effect != self.current_effect) {
 | 
				
			||||||
			self.flush(pools);
 | 
								self.flush(resources);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		self.current_effect = command.effect;
 | 
							self.current_effect = command.effect;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (pools.get_effect(self.current_effect)) |effect| {
 | 
							if (resources.get_effect(self.current_effect)) |effect| {
 | 
				
			||||||
			@memcpy(effect.properties, command.properties);
 | 
								@memcpy(effect.properties, command.properties);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn set_target(self: *Frame, pools: *Resources, command: gfx.Commands.SetTargetCommand) void {
 | 
						pub fn set_target(self: *Frame, resources: *Resources, command: coral.Commands.SetTargetCommand) void {
 | 
				
			||||||
		sokol.gfx.endPass();
 | 
							sokol.gfx.endPass();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		var pass = sokol.gfx.Pass{
 | 
							var pass = sokol.gfx.Pass{
 | 
				
			||||||
@ -212,7 +214,7 @@ const Frame = struct {
 | 
				
			|||||||
			pass.action.depth = .{.load_action = .LOAD};
 | 
								pass.action.depth = .{.load_action = .LOAD};
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pass.attachments = switch (pools.get_texture(self.current_target_texture).?.access) {
 | 
							pass.attachments = switch (resources.get_texture(self.current_target_texture).?.access) {
 | 
				
			||||||
			.static => @panic("Cannot render to static textures"),
 | 
								.static => @panic("Cannot render to static textures"),
 | 
				
			||||||
			.empty => @panic("Cannot render to empty textures"),
 | 
								.empty => @panic("Cannot render to empty textures"),
 | 
				
			||||||
			.render => |render| render.attachments,
 | 
								.render => |render| render.attachments,
 | 
				
			||||||
@ -224,6 +226,10 @@ const Frame = struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn Matrix(comptime n: usize, comptime Element: type) type {
 | 
				
			||||||
 | 
						return [n]@Vector(n, Element);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var default_sampler: sokol.gfx.Sampler = undefined;
 | 
					var default_sampler: sokol.gfx.Sampler = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const vertex_indices = .{
 | 
					const vertex_indices = .{
 | 
				
			||||||
@ -231,7 +237,19 @@ const vertex_indices = .{
 | 
				
			|||||||
	.instance = 1,
 | 
						.instance = 1,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window) !void {
 | 
					fn orthographic_projection(near: f32, far: f32, viewport: coral.Rect) Matrix(4, f32) {
 | 
				
			||||||
 | 
						const width = viewport.right - viewport.left;
 | 
				
			||||||
 | 
						const height = viewport.bottom - viewport.top;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return .{
 | 
				
			||||||
 | 
							.{2 / width, 0, 0, 0},
 | 
				
			||||||
 | 
							.{0, 2 / height, 0, 0},
 | 
				
			||||||
 | 
							.{0, 0, 1 / (far - near), 0},
 | 
				
			||||||
 | 
							.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn process_work(pending_work: *coral.Assets.WorkQueue, window: *ext.SDL_Window) !void {
 | 
				
			||||||
	const context = configure_and_create: {
 | 
						const context = configure_and_create: {
 | 
				
			||||||
		var result = @as(c_int, 0);
 | 
							var result = @as(c_int, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							
								
								
									
										2218
									
								
								src/coral/script.zig
									
									
									
									
									
								
							
							
						
						
									
										2218
									
								
								src/coral/script.zig
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,10 +1,10 @@
 | 
				
			|||||||
const coral = @import("coral");
 | 
					const coral = @import("./coral.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ext = @cImport({
 | 
					const ext = @cImport({
 | 
				
			||||||
	@cInclude("spirv-cross/spirv_cross_c.h");
 | 
						@cInclude("spirv-cross/spirv_cross_c.h");
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ona = @import("../ona.zig");
 | 
					const ona = @import("ona");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -202,7 +202,7 @@ pub const Stage = struct {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				uniform.* = .{
 | 
									uniform.* = .{
 | 
				
			||||||
					.name = try coral.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{
 | 
										.name = try ona.utf8.alloc_formatted(arena_allocator, "_{id}.{member_name}", .{
 | 
				
			||||||
						.id = reflected_resource.id,
 | 
											.id = reflected_resource.id,
 | 
				
			||||||
						.member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)),
 | 
											.member_name = std.mem.span(ext.spvc_compiler_get_member_name(compiler, reflected_resource.base_type_id, member_index)),
 | 
				
			||||||
					}),
 | 
										}),
 | 
				
			||||||
@ -357,7 +357,7 @@ fn parse(arena: *std.heap.ArenaAllocator, context: ext.spvc_context, target: Tar
 | 
				
			|||||||
	var binding: u32 = 0;
 | 
						var binding: u32 = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for (combined_image_samplers) |combined_image_sampler| {
 | 
						for (combined_image_samplers) |combined_image_sampler| {
 | 
				
			||||||
		const name = try coral.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
 | 
							const name = try ona.utf8.alloc_formatted(arena_allocator, "{image_name}_{sampler_name}", .{
 | 
				
			||||||
			.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
 | 
								.image_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.image_id)),
 | 
				
			||||||
			.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
 | 
								.sampler_name = std.mem.span(ext.spvc_compiler_get_name(compiler, combined_image_sampler.sampler_id)),
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
@ -1,98 +0,0 @@
 | 
				
			|||||||
const builtin = @import("builtin");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const flow = @import("./flow.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const coral = @import("coral");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const states = @import("./states.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const system = @import("./system.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
thread_pool: ?*std.Thread.Pool = null,
 | 
					 | 
				
			||||||
thread_restricted_resources: [std.enums.values(states.ThreadRestriction).len]states.Table,
 | 
					 | 
				
			||||||
event_systems: coral.stack.Sequential(system.Schedule),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Event = enum (usize) { _ };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Self = @This();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event {
 | 
					 | 
				
			||||||
	var systems = try system.Schedule.init(label);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	errdefer systems.deinit(self);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const index = self.event_systems.len();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try self.event_systems.push_grow(systems);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return @enumFromInt(index);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn deinit(self: *Self) void {
 | 
					 | 
				
			||||||
	for (self.event_systems.values) |*schedule| {
 | 
					 | 
				
			||||||
		schedule.deinit(self);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for (&self.thread_restricted_resources) |*resources| {
 | 
					 | 
				
			||||||
		resources.deinit();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (self.thread_pool) |thread_pool| {
 | 
					 | 
				
			||||||
		thread_pool.deinit();
 | 
					 | 
				
			||||||
		coral.heap.allocator.destroy(thread_pool);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	self.event_systems.deinit();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	self.* = undefined;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn get_params(self: Self, comptime Value: type) flow.Params(Value) {
 | 
					 | 
				
			||||||
	const params = self.get_state(flow.Params(Value)) orelse {
 | 
					 | 
				
			||||||
		return .{};
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return params.*;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn get_state(self: Self, comptime Value: type) ?*Value {
 | 
					 | 
				
			||||||
	return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(states.thread_restriction(Value))].get(Value)));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
 | 
					 | 
				
			||||||
	return self.thread_restricted_resources[@intFromEnum(states.thread_restriction(@TypeOf(value)))].set_get(value);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
 | 
					 | 
				
			||||||
	var world = Self{
 | 
					 | 
				
			||||||
		.thread_restricted_resources = .{states.Table.init(), states.Table.init()},
 | 
					 | 
				
			||||||
		.event_systems = .{.allocator = coral.heap.allocator},
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (thread_count != 0 and !builtin.single_threaded) {
 | 
					 | 
				
			||||||
		const thread_pool = try coral.heap.allocator.create(std.Thread.Pool);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		try thread_pool.init(.{
 | 
					 | 
				
			||||||
			.allocator = coral.heap.allocator,
 | 
					 | 
				
			||||||
			.n_jobs = thread_count,
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		world.thread_pool = thread_pool;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return world;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn on_event(self: *Self, event: Event, action: *const system.Info, order: system.Order) std.mem.Allocator.Error!void {
 | 
					 | 
				
			||||||
	try self.event_systems.values[@intFromEnum(event)].then(self, action, order);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn run_event(self: *Self, event: Event) anyerror!void {
 | 
					 | 
				
			||||||
	try self.event_systems.values[@intFromEnum(event)].run(self);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void {
 | 
					 | 
				
			||||||
	try self.thread_restricted_resources[@intFromEnum(states.thread_restriction(@TypeOf(value)))].set(value);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,329 +0,0 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const states = @import("./states.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const system = @import("./system.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const World = @import("./World.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Exclusive = struct {
 | 
					 | 
				
			||||||
	world: *World,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const Param = struct {
 | 
					 | 
				
			||||||
		world: *World,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn bind(context: system.BindContext) std.mem.Allocator.Error!Param {
 | 
					 | 
				
			||||||
		return .{
 | 
					 | 
				
			||||||
			.world = context.world,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn init(param: *Param) Exclusive {
 | 
					 | 
				
			||||||
		return .{
 | 
					 | 
				
			||||||
			.world = param.world,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const thread_restriction = .main;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn Params(comptime Value: type) type {
 | 
					 | 
				
			||||||
	if (!@hasDecl(Value, "Param")) {
 | 
					 | 
				
			||||||
		@compileError("System parameters must have a Params type declaration");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return struct {
 | 
					 | 
				
			||||||
		has_head: ?*Node = null,
 | 
					 | 
				
			||||||
		has_tail: ?*Node = null,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub const Node = struct {
 | 
					 | 
				
			||||||
			param: Value.Param,
 | 
					 | 
				
			||||||
			has_prev: ?*Node = null,
 | 
					 | 
				
			||||||
			has_next: ?*Node = null,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn Read(comptime Value: type) type {
 | 
					 | 
				
			||||||
	return Shared(Value, .{
 | 
					 | 
				
			||||||
		.thread_restriction = states.thread_restriction(Value),
 | 
					 | 
				
			||||||
		.read_only = true,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const ShareInfo = struct {
 | 
					 | 
				
			||||||
	thread_restriction: states.ThreadRestriction,
 | 
					 | 
				
			||||||
	read_only: bool,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn Shared(comptime Value: type, comptime info: ShareInfo) type {
 | 
					 | 
				
			||||||
	const value_info = @typeInfo(Value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const Qualified = switch (value_info) {
 | 
					 | 
				
			||||||
		.Optional => @Type(.{
 | 
					 | 
				
			||||||
			.Optional = .{
 | 
					 | 
				
			||||||
				.child = .{
 | 
					 | 
				
			||||||
					.Pointer = .{
 | 
					 | 
				
			||||||
						.is_allowzero = false,
 | 
					 | 
				
			||||||
						.sentinel = null,
 | 
					 | 
				
			||||||
						.address_space = .generic,
 | 
					 | 
				
			||||||
						.is_volatile = false,
 | 
					 | 
				
			||||||
						.alignment = @alignOf(Value),
 | 
					 | 
				
			||||||
						.size = .One,
 | 
					 | 
				
			||||||
						.child = Value,
 | 
					 | 
				
			||||||
						.is_const = info.read_only,
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		else => @Type(.{
 | 
					 | 
				
			||||||
			.Pointer = .{
 | 
					 | 
				
			||||||
				.is_allowzero = false,
 | 
					 | 
				
			||||||
				.sentinel = null,
 | 
					 | 
				
			||||||
				.address_space = .generic,
 | 
					 | 
				
			||||||
				.is_volatile = false,
 | 
					 | 
				
			||||||
				.alignment = @alignOf(Value),
 | 
					 | 
				
			||||||
				.size = .One,
 | 
					 | 
				
			||||||
				.child = Value,
 | 
					 | 
				
			||||||
				.is_const = info.read_only,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}),
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return struct {
 | 
					 | 
				
			||||||
		res: Qualified,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const Self = @This();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub const Param = struct {
 | 
					 | 
				
			||||||
			res: Qualified,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn bind(context: system.BindContext) std.mem.Allocator.Error!Param {
 | 
					 | 
				
			||||||
			const thread_restriction_name = switch (info.thread_restriction) {
 | 
					 | 
				
			||||||
				.main => "main thread-restricted ",
 | 
					 | 
				
			||||||
				.none => ""
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const res = switch (info.read_only) {
 | 
					 | 
				
			||||||
				true => (try context.register_readable_state_access(Value)),
 | 
					 | 
				
			||||||
				false => (try context.register_writable_state_access(Value)),
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return .{
 | 
					 | 
				
			||||||
				.res = switch (value_info) {
 | 
					 | 
				
			||||||
					.Optional => res,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					else => res orelse {
 | 
					 | 
				
			||||||
						@panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{
 | 
					 | 
				
			||||||
							thread_restriction_name,
 | 
					 | 
				
			||||||
							if (info.read_only) "read-only" else "read-write",
 | 
					 | 
				
			||||||
							@typeName(Value),
 | 
					 | 
				
			||||||
						}));
 | 
					 | 
				
			||||||
					},
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn init(param: *Param) Self {
 | 
					 | 
				
			||||||
			return .{
 | 
					 | 
				
			||||||
				.res = param.res,
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn Write(comptime Value: type) type {
 | 
					 | 
				
			||||||
	return Shared(Value, .{
 | 
					 | 
				
			||||||
		.thread_restriction = states.thread_restriction(Value),
 | 
					 | 
				
			||||||
		.read_only = false,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn parameter_type(comptime Value: type) *const system.Info.Parameter {
 | 
					 | 
				
			||||||
	const ValueParams = Params(Value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (@sizeOf(Value) == 0) {
 | 
					 | 
				
			||||||
		@compileError("System parameters must have a non-zero size");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const parameters = struct {
 | 
					 | 
				
			||||||
		fn bind(allocator: std.mem.Allocator, context: system.BindContext) std.mem.Allocator.Error!*anyopaque {
 | 
					 | 
				
			||||||
			const value_name = @typeName(Value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (!@hasDecl(Value, "bind")) {
 | 
					 | 
				
			||||||
				@compileError(
 | 
					 | 
				
			||||||
					"a `bind` declaration on " ++
 | 
					 | 
				
			||||||
					value_name ++
 | 
					 | 
				
			||||||
					" is requied for parameter types with a `Param` declaration");
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const bind_type = @typeInfo(@TypeOf(Value.bind));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (bind_type != .Fn) {
 | 
					 | 
				
			||||||
				@compileError("`bind` declaration on " ++ value_name ++ " must be a fn");
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (bind_type.Fn.params.len != 1 or bind_type.Fn.params[0].type.? != system.BindContext) {
 | 
					 | 
				
			||||||
				@compileError(
 | 
					 | 
				
			||||||
					"`bind` fn on " ++
 | 
					 | 
				
			||||||
					value_name ++
 | 
					 | 
				
			||||||
					" must accept " ++
 | 
					 | 
				
			||||||
					@typeName(system.BindContext) ++
 | 
					 | 
				
			||||||
					" as it's one and only argument");
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const params_node = try allocator.create(ValueParams.Node);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			params_node.* = .{
 | 
					 | 
				
			||||||
				.param = switch (bind_type.Fn.return_type.?) {
 | 
					 | 
				
			||||||
					Value.Param => Value.bind(context),
 | 
					 | 
				
			||||||
					std.mem.Allocator.Error!Value.Param => try Value.bind(context),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					else => @compileError(std.fmt.comptimePrint("`bind` fn on {s} must return {s} or {s}", .{
 | 
					 | 
				
			||||||
						@typeName(Value),
 | 
					 | 
				
			||||||
						@typeName(Value.Param),
 | 
					 | 
				
			||||||
						@typeName(std.mem.Allocator.Error!Value.Param)
 | 
					 | 
				
			||||||
					})),
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (context.world.get_state(ValueParams)) |value_params| {
 | 
					 | 
				
			||||||
				if (value_params.has_tail) |tail| {
 | 
					 | 
				
			||||||
					tail.has_next = params_node;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				params_node.has_prev = value_params.has_tail;
 | 
					 | 
				
			||||||
				value_params.has_tail = params_node;
 | 
					 | 
				
			||||||
			} else {
 | 
					 | 
				
			||||||
				try context.world.set_state(ValueParams{
 | 
					 | 
				
			||||||
					.has_head = params_node,
 | 
					 | 
				
			||||||
					.has_tail = params_node,
 | 
					 | 
				
			||||||
				});
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return @ptrCast(params_node);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn init(argument: *anyopaque, erased_node: *anyopaque) void {
 | 
					 | 
				
			||||||
			const value_name = @typeName(Value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (!@hasDecl(Value, "init")) {
 | 
					 | 
				
			||||||
				@compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types");
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const init_type = @typeInfo(@TypeOf(Value.init));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (init_type != .Fn) {
 | 
					 | 
				
			||||||
				@compileError("`init` declaration on " ++ value_name ++ " must be a fn");
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (init_type.Fn.return_type.? != Value) {
 | 
					 | 
				
			||||||
				@compileError("`init` fn on " ++ value_name ++ " must return a " ++ value_name);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			const concrete_argument = @as(*Value, @ptrCast(@alignCast(argument)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.Param) {
 | 
					 | 
				
			||||||
				@compileError("`init` fn on " ++ value_name ++ " must accept a " ++ @typeName(*Value.Param));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			concrete_argument.* = Value.init(&@as(*ValueParams.Node, @ptrCast(@alignCast(erased_node))).param);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn unbind(allocator: std.mem.Allocator, erased_node: *anyopaque, context: system.UnbindContext) void {
 | 
					 | 
				
			||||||
			if (@hasDecl(Value, "unbind")) {
 | 
					 | 
				
			||||||
				const node = @as(*ValueParams.Node, @ptrCast(@alignCast(erased_node)));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (node.has_prev) |prev| {
 | 
					 | 
				
			||||||
					prev.has_next = node.has_next;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (node.has_next) |next| {
 | 
					 | 
				
			||||||
					next.has_prev = node.has_prev;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (context.world.get_state(ValueParams)) |params| {
 | 
					 | 
				
			||||||
					if (node.has_prev == null) {
 | 
					 | 
				
			||||||
						params.has_head = node.has_next;
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					if (node.has_next == null) {
 | 
					 | 
				
			||||||
						params.has_tail = node.has_prev;
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				Value.unbind(&node.param, context);
 | 
					 | 
				
			||||||
				allocator.destroy(node);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return comptime &.{
 | 
					 | 
				
			||||||
		.thread_restriction = if (@hasDecl(Value, "thread_restriction")) Value.thread_restriction else .none,
 | 
					 | 
				
			||||||
		.init = parameters.init,
 | 
					 | 
				
			||||||
		.bind = parameters.bind,
 | 
					 | 
				
			||||||
		.unbind = parameters.unbind,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn system_fn(comptime call: anytype) *const system.Info {
 | 
					 | 
				
			||||||
	const Call = @TypeOf(call);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const system_info = comptime generate: {
 | 
					 | 
				
			||||||
		switch (@typeInfo(Call)) {
 | 
					 | 
				
			||||||
			.Fn => |call_fn| {
 | 
					 | 
				
			||||||
				if (call_fn.params.len > system.max_parameters) {
 | 
					 | 
				
			||||||
					@compileError("number of parameters to `call` cannot be more than 16");
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				const systems = struct {
 | 
					 | 
				
			||||||
					fn run(parameters: []const *const system.Info.Parameter, data: *const [system.max_parameters]*anyopaque) anyerror!void {
 | 
					 | 
				
			||||||
						var call_args = @as(std.meta.ArgsTuple(Call), undefined);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						inline for (parameters, &call_args, data[0 .. parameters.len]) |parameter, *call_arg, state| {
 | 
					 | 
				
			||||||
							parameter.init(call_arg, state);
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						switch (@typeInfo(call_fn.return_type.?)) {
 | 
					 | 
				
			||||||
							.Void => @call(.auto, call, call_args),
 | 
					 | 
				
			||||||
							.ErrorUnion => try @call(.auto, call, call_args),
 | 
					 | 
				
			||||||
							else => @compileError("number of parameters to `call` must return void or !void"),
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				var parameters = @as([system.max_parameters]*const system.Info.Parameter, undefined);
 | 
					 | 
				
			||||||
				var thread_restriction = states.ThreadRestriction.none;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				for (0 .. call_fn.params.len) |index| {
 | 
					 | 
				
			||||||
					const CallParam = call_fn.params[index].type.?;
 | 
					 | 
				
			||||||
					const parameter = parameter_type(CallParam);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					if (parameter.thread_restriction != .none) {
 | 
					 | 
				
			||||||
						if (thread_restriction != .none and thread_restriction != parameter.thread_restriction) {
 | 
					 | 
				
			||||||
							@compileError("a system may not have conflicting thread restrictions");
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						thread_restriction = parameter.thread_restriction;
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					parameters[index] = parameter;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				break: generate &.{
 | 
					 | 
				
			||||||
					.parameters = parameters,
 | 
					 | 
				
			||||||
					.parameter_count = call_fn.params.len,
 | 
					 | 
				
			||||||
					.execute = systems.run,
 | 
					 | 
				
			||||||
					.thread_restriction = thread_restriction,
 | 
					 | 
				
			||||||
				};
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			else => @compileError("parameter `call` must be a function"),
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return system_info;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,87 +0,0 @@
 | 
				
			|||||||
const coral = @import("coral");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Table = struct {
 | 
					 | 
				
			||||||
	arena: std.heap.ArenaAllocator,
 | 
					 | 
				
			||||||
	table: coral.map.Hashed(TypeID, Entry, coral.map.enum_traits(TypeID)),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const Entry = struct {
 | 
					 | 
				
			||||||
		ptr: *anyopaque,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn deinit(self: *Table) void {
 | 
					 | 
				
			||||||
		self.table.deinit();
 | 
					 | 
				
			||||||
		self.arena.deinit();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		self.* = undefined;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn get(self: Table, comptime Resource: type) ?*Resource {
 | 
					 | 
				
			||||||
		if (self.table.get(type_id(Resource))) |entry| {
 | 
					 | 
				
			||||||
			return @ptrCast(@alignCast(entry.ptr));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return null;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn init() Table {
 | 
					 | 
				
			||||||
		return .{
 | 
					 | 
				
			||||||
			.arena = std.heap.ArenaAllocator.init(coral.heap.allocator),
 | 
					 | 
				
			||||||
			.table = .{.allocator = coral.heap.allocator},
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn set_get(self: *Table, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
 | 
					 | 
				
			||||||
		try self.set(value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return self.get(@TypeOf(value)).?;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn set(self: *Table, value: anytype) std.mem.Allocator.Error!void {
 | 
					 | 
				
			||||||
		const Value = @TypeOf(value);
 | 
					 | 
				
			||||||
		const value_id = type_id(Value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (self.table.get(value_id)) |entry| {
 | 
					 | 
				
			||||||
			@as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value;
 | 
					 | 
				
			||||||
		} else {
 | 
					 | 
				
			||||||
			const resource_allocator = self.arena.allocator();
 | 
					 | 
				
			||||||
			const allocated_resource = try resource_allocator.create(Value);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			errdefer resource_allocator.destroy(allocated_resource);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			std.debug.assert(try self.table.emplace(value_id, .{
 | 
					 | 
				
			||||||
				.ptr = allocated_resource,
 | 
					 | 
				
			||||||
			}));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			allocated_resource.* = value;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const ThreadRestriction = enum {
 | 
					 | 
				
			||||||
	none,
 | 
					 | 
				
			||||||
	main,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const TypeID = enum (usize) { _ };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn type_id(comptime T: type) TypeID {
 | 
					 | 
				
			||||||
	const TypeHandle = struct {
 | 
					 | 
				
			||||||
		comptime {
 | 
					 | 
				
			||||||
			_ = T;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var byte: u8 = 0;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return @enumFromInt(@intFromPtr(&TypeHandle.byte));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn thread_restriction(comptime State: type) ThreadRestriction {
 | 
					 | 
				
			||||||
	if (@hasDecl(State, "thread_restriction")) {
 | 
					 | 
				
			||||||
		return State.thread_restriction;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return .none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										274
									
								
								src/input/input.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								src/input/input.zig
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,274 @@
 | 
				
			|||||||
 | 
					const ona = @import("ona");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Axis = struct {
 | 
				
			||||||
 | 
						keys: ?[2]Event.Key = null,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Event = union (enum) {
 | 
				
			||||||
 | 
						key_up: Key,
 | 
				
			||||||
 | 
						key_down: Key,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Key = enum (u32) {
 | 
				
			||||||
 | 
						no_event = 0x00,
 | 
				
			||||||
 | 
						error_rollover = 0x01,
 | 
				
			||||||
 | 
						post_fail = 0x02,
 | 
				
			||||||
 | 
						error_undefined = 0x03,
 | 
				
			||||||
 | 
						a = 0x04,
 | 
				
			||||||
 | 
						b = 0x05,
 | 
				
			||||||
 | 
						c = 0x06,
 | 
				
			||||||
 | 
						d = 0x07,
 | 
				
			||||||
 | 
						e = 0x08,
 | 
				
			||||||
 | 
						f = 0x09,
 | 
				
			||||||
 | 
						g = 0x0A,
 | 
				
			||||||
 | 
						h = 0x0B,
 | 
				
			||||||
 | 
						i = 0x0C,
 | 
				
			||||||
 | 
						j = 0x0D,
 | 
				
			||||||
 | 
						k = 0x0E,
 | 
				
			||||||
 | 
						l = 0x0F,
 | 
				
			||||||
 | 
						m = 0x10,
 | 
				
			||||||
 | 
						n = 0x11,
 | 
				
			||||||
 | 
						o = 0x12,
 | 
				
			||||||
 | 
						p = 0x13,
 | 
				
			||||||
 | 
						q = 0x14,
 | 
				
			||||||
 | 
						r = 0x15,
 | 
				
			||||||
 | 
						s = 0x16,
 | 
				
			||||||
 | 
						t = 0x17,
 | 
				
			||||||
 | 
						u = 0x18,
 | 
				
			||||||
 | 
						v = 0x19,
 | 
				
			||||||
 | 
						w = 0x1A,
 | 
				
			||||||
 | 
						x = 0x1B,
 | 
				
			||||||
 | 
						y = 0x1C,
 | 
				
			||||||
 | 
						z = 0x1D,
 | 
				
			||||||
 | 
						one = 0x1E,
 | 
				
			||||||
 | 
						two = 0x1F,
 | 
				
			||||||
 | 
						three = 0x20,
 | 
				
			||||||
 | 
						four = 0x21,
 | 
				
			||||||
 | 
						five = 0x22,
 | 
				
			||||||
 | 
						six = 0x23,
 | 
				
			||||||
 | 
						seven = 0x24,
 | 
				
			||||||
 | 
						eight = 0x25,
 | 
				
			||||||
 | 
						nine = 0x26,
 | 
				
			||||||
 | 
						zero = 0x27,
 | 
				
			||||||
 | 
						enter = 0x28,
 | 
				
			||||||
 | 
						escape = 0x29,
 | 
				
			||||||
 | 
						backspace = 0x2A,
 | 
				
			||||||
 | 
						tab = 0x2B,
 | 
				
			||||||
 | 
						space = 0x2C,
 | 
				
			||||||
 | 
						minus = 0x2D,
 | 
				
			||||||
 | 
						equal = 0x2E,
 | 
				
			||||||
 | 
						left_bracket = 0x2F,
 | 
				
			||||||
 | 
						right_bracket = 0x30,
 | 
				
			||||||
 | 
						backslash = 0x31,
 | 
				
			||||||
 | 
						non_us_pound = 0x32,
 | 
				
			||||||
 | 
						semicolon = 0x33,
 | 
				
			||||||
 | 
						quote = 0x34,
 | 
				
			||||||
 | 
						grave = 0x35,
 | 
				
			||||||
 | 
						comma = 0x36,
 | 
				
			||||||
 | 
						period = 0x37,
 | 
				
			||||||
 | 
						slash = 0x38,
 | 
				
			||||||
 | 
						caps_lock = 0x39,
 | 
				
			||||||
 | 
						f1 = 0x3A,
 | 
				
			||||||
 | 
						f2 = 0x3B,
 | 
				
			||||||
 | 
						f3 = 0x3C,
 | 
				
			||||||
 | 
						f4 = 0x3D,
 | 
				
			||||||
 | 
						f5 = 0x3E,
 | 
				
			||||||
 | 
						f6 = 0x3F,
 | 
				
			||||||
 | 
						f7 = 0x40,
 | 
				
			||||||
 | 
						f8 = 0x41,
 | 
				
			||||||
 | 
						f9 = 0x42,
 | 
				
			||||||
 | 
						f10 = 0x43,
 | 
				
			||||||
 | 
						f11 = 0x44,
 | 
				
			||||||
 | 
						f12 = 0x45,
 | 
				
			||||||
 | 
						print_screen = 0x46,
 | 
				
			||||||
 | 
						scroll_lock = 0x47,
 | 
				
			||||||
 | 
						pause = 0x48,
 | 
				
			||||||
 | 
						insert = 0x49,
 | 
				
			||||||
 | 
						home = 0x4A,
 | 
				
			||||||
 | 
						page_up = 0x4B,
 | 
				
			||||||
 | 
						delete = 0x4C,
 | 
				
			||||||
 | 
						end = 0x4D,
 | 
				
			||||||
 | 
						page_down = 0x4E,
 | 
				
			||||||
 | 
						right_arrow = 0x4F,
 | 
				
			||||||
 | 
						left_arrow = 0x50,
 | 
				
			||||||
 | 
						down_arrow = 0x51,
 | 
				
			||||||
 | 
						up_arrow = 0x52,
 | 
				
			||||||
 | 
						num_lock = 0x53,
 | 
				
			||||||
 | 
						keypad_slash = 0x54,
 | 
				
			||||||
 | 
						keypad_asterisk = 0x55,
 | 
				
			||||||
 | 
						keypad_minus = 0x56,
 | 
				
			||||||
 | 
						keypad_plus = 0x57,
 | 
				
			||||||
 | 
						keypad_enter = 0x58,
 | 
				
			||||||
 | 
						keypad_one = 0x59,
 | 
				
			||||||
 | 
						keypad_two = 0x5A,
 | 
				
			||||||
 | 
						keypad_three = 0x5B,
 | 
				
			||||||
 | 
						keypad_four = 0x5C,
 | 
				
			||||||
 | 
						keypad_five = 0x5D,
 | 
				
			||||||
 | 
						keypad_six = 0x5E,
 | 
				
			||||||
 | 
						keypad_seven = 0x5F,
 | 
				
			||||||
 | 
						keypad_eight = 0x60,
 | 
				
			||||||
 | 
						keypad_nine = 0x61,
 | 
				
			||||||
 | 
						keypad_zero = 0x62,
 | 
				
			||||||
 | 
						keypad_period = 0x63,
 | 
				
			||||||
 | 
						non_us_backslash = 0x64,
 | 
				
			||||||
 | 
						application = 0x65,
 | 
				
			||||||
 | 
						power = 0x66,
 | 
				
			||||||
 | 
						keypad_equal = 0x67,
 | 
				
			||||||
 | 
						f13 = 0x68,
 | 
				
			||||||
 | 
						f14 = 0x69,
 | 
				
			||||||
 | 
						f15 = 0x6A,
 | 
				
			||||||
 | 
						f16 = 0x6B,
 | 
				
			||||||
 | 
						f17 = 0x6C,
 | 
				
			||||||
 | 
						f18 = 0x6D,
 | 
				
			||||||
 | 
						f19 = 0x6E,
 | 
				
			||||||
 | 
						f20 = 0x6F,
 | 
				
			||||||
 | 
						f21 = 0x70,
 | 
				
			||||||
 | 
						f22 = 0x71,
 | 
				
			||||||
 | 
						f23 = 0x72,
 | 
				
			||||||
 | 
						f24 = 0x73,
 | 
				
			||||||
 | 
						execute = 0x74,
 | 
				
			||||||
 | 
						help = 0x75,
 | 
				
			||||||
 | 
						menu = 0x76,
 | 
				
			||||||
 | 
						select = 0x77,
 | 
				
			||||||
 | 
						stop = 0x78,
 | 
				
			||||||
 | 
						again = 0x79,
 | 
				
			||||||
 | 
						undo = 0x7A,
 | 
				
			||||||
 | 
						cut = 0x7B,
 | 
				
			||||||
 | 
						copy = 0x7C,
 | 
				
			||||||
 | 
						paste = 0x7D,
 | 
				
			||||||
 | 
						find = 0x7E,
 | 
				
			||||||
 | 
						mute = 0x7F,
 | 
				
			||||||
 | 
						volume_up = 0x80,
 | 
				
			||||||
 | 
						volume_down = 0x81,
 | 
				
			||||||
 | 
						lock_caps_lock = 0x82,
 | 
				
			||||||
 | 
						lock_num_lock = 0x83,
 | 
				
			||||||
 | 
						lock_scroll_lock = 0x84,
 | 
				
			||||||
 | 
						keypad_comma = 0x85,
 | 
				
			||||||
 | 
						keypad_equal_sign = 0x86,
 | 
				
			||||||
 | 
						international1 = 0x87,
 | 
				
			||||||
 | 
						international2 = 0x88,
 | 
				
			||||||
 | 
						international3 = 0x89,
 | 
				
			||||||
 | 
						international4 = 0x8A,
 | 
				
			||||||
 | 
						international5 = 0x8B,
 | 
				
			||||||
 | 
						international6 = 0x8C,
 | 
				
			||||||
 | 
						international7 = 0x8D,
 | 
				
			||||||
 | 
						international8 = 0x8E,
 | 
				
			||||||
 | 
						international9 = 0x8F,
 | 
				
			||||||
 | 
						lang1 = 0x90,
 | 
				
			||||||
 | 
						lang2 = 0x91,
 | 
				
			||||||
 | 
						lang3 = 0x92,
 | 
				
			||||||
 | 
						lang4 = 0x93,
 | 
				
			||||||
 | 
						lang5 = 0x94,
 | 
				
			||||||
 | 
						lang6 = 0x95,
 | 
				
			||||||
 | 
						lang7 = 0x96,
 | 
				
			||||||
 | 
						lang8 = 0x97,
 | 
				
			||||||
 | 
						lang9 = 0x98,
 | 
				
			||||||
 | 
						alternate_erase = 0x99,
 | 
				
			||||||
 | 
						sys_req_attention = 0x9A,
 | 
				
			||||||
 | 
						cancel = 0x9B,
 | 
				
			||||||
 | 
						clear = 0x9C,
 | 
				
			||||||
 | 
						prior = 0x9D,
 | 
				
			||||||
 | 
						return_key = 0x9E,
 | 
				
			||||||
 | 
						separator = 0x9F,
 | 
				
			||||||
 | 
						out = 0xA0,
 | 
				
			||||||
 | 
						oper = 0xA1,
 | 
				
			||||||
 | 
						clear_again = 0xA2,
 | 
				
			||||||
 | 
						cr_sel_props = 0xA3,
 | 
				
			||||||
 | 
						ex_sel = 0xA4,
 | 
				
			||||||
 | 
						left_control = 0xE0,
 | 
				
			||||||
 | 
						left_shift = 0xE1,
 | 
				
			||||||
 | 
						left_alt = 0xE2,
 | 
				
			||||||
 | 
						left_gui = 0xE3,
 | 
				
			||||||
 | 
						right_control = 0xE4,
 | 
				
			||||||
 | 
						right_shift = 0xE5,
 | 
				
			||||||
 | 
						right_alt = 0xE6,
 | 
				
			||||||
 | 
						right_gui = 0xE7,
 | 
				
			||||||
 | 
						_,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Mapping = struct {
 | 
				
			||||||
 | 
						keys_pressed: ScancodeSet = ScancodeSet.initEmpty(),
 | 
				
			||||||
 | 
						keys_held: ScancodeSet = ScancodeSet.initEmpty(),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const ScancodeSet = std.bit_set.StaticBitSet(512);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn axis_strength(self: Mapping, axis: Axis) f32 {
 | 
				
			||||||
 | 
							if (axis.keys) |keys| {
 | 
				
			||||||
 | 
								const key_down, const key_up = keys;
 | 
				
			||||||
 | 
								const is_key_down_held = self.keys_held.isSet(@intFromEnum(key_down));
 | 
				
			||||||
 | 
								const is_key_up_held = self.keys_held.isSet(@intFromEnum(key_up));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (is_key_down_held or is_key_up_held) {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
										@as(f32, @floatFromInt(@intFromBool(is_key_up_held))) -
 | 
				
			||||||
 | 
										@as(f32, @floatFromInt(@intFromBool(is_key_down_held)));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "mapping values" {
 | 
				
			||||||
 | 
						const axis = Axis{
 | 
				
			||||||
 | 
							.keys = .{.minus, .equal},
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							var mapping = Mapping{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try std.testing.expectEqual(mapping.axis_strength(axis), 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							var mapping = Mapping{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mapping.keys_held.set(@intFromEnum(Key.equal));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try std.testing.expectEqual(mapping.axis_strength(axis), 1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							var mapping = Mapping{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mapping.keys_held.set(@intFromEnum(Key.minus));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try std.testing.expectEqual(mapping.axis_strength(axis), -1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							var mapping = Mapping{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mapping.keys_held.set(@intFromEnum(Key.minus));
 | 
				
			||||||
 | 
							mapping.keys_held.set(@intFromEnum(Key.equal));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try std.testing.expectEqual(mapping.axis_strength(axis), 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn setup(world: *ona.World, events: ona.App.Events) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
						try world.set_state(Mapping{});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try world.on_event(events.pre_update, ona.system_fn(update), .{
 | 
				
			||||||
 | 
							.label = "update actions",
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn update(inputs: ona.msg.Receive(ona.gfx.Input), mapping: ona.Write(Mapping)) void {
 | 
				
			||||||
 | 
						mapping.state.keys_pressed = Mapping.ScancodeSet.initEmpty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (inputs.messages()) |message| {
 | 
				
			||||||
 | 
							switch (message) {
 | 
				
			||||||
 | 
								.key_down => |key| {
 | 
				
			||||||
 | 
									mapping.state.keys_pressed.set(key.scancode());
 | 
				
			||||||
 | 
									mapping.state.keys_held.set(key.scancode());
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								.key_up => |key| {
 | 
				
			||||||
 | 
									mapping.state.keys_held.unset(key.scancode());
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										124
									
								
								src/main.zig
									
									
									
									
									
								
							
							
						
						
									
										124
									
								
								src/main.zig
									
									
									
									
									
								
							@ -1,124 +0,0 @@
 | 
				
			|||||||
const coral = @import("coral");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ona = @import("ona");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ChromaticAberration = extern struct {
 | 
					 | 
				
			||||||
	effect_magnitude: f32,
 | 
					 | 
				
			||||||
	padding: [12]u8 = undefined,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const CRT = extern struct {
 | 
					 | 
				
			||||||
	width: f32,
 | 
					 | 
				
			||||||
	height: f32,
 | 
					 | 
				
			||||||
	time: f32,
 | 
					 | 
				
			||||||
	padding: [4]u8 = undefined,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Actors = struct {
 | 
					 | 
				
			||||||
	instances: coral.stack.Sequential(@Vector(2, f32)) = .{.allocator = coral.heap.allocator},
 | 
					 | 
				
			||||||
	body_texture: ona.gfx.Texture = .default,
 | 
					 | 
				
			||||||
	render_texture: ona.gfx.Texture = .default,
 | 
					 | 
				
			||||||
	ca_effect: ona.gfx.Effect = .default,
 | 
					 | 
				
			||||||
	crt_effect: ona.gfx.Effect = .default,
 | 
					 | 
				
			||||||
	staging_texture: ona.gfx.Texture = .default,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Player = struct {
 | 
					 | 
				
			||||||
	move_x: ona.act.Axis = .{.keys = .{.a, .d}},
 | 
					 | 
				
			||||||
	move_y: ona.act.Axis = .{.keys = .{.w, .s}},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const main = ona.start(setup, .{
 | 
					 | 
				
			||||||
	.tick_rate = 60,
 | 
					 | 
				
			||||||
	.execution = .{.thread_share = 0.1},
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn load(config: ona.Write(ona.gfx.Config), actors: ona.Write(Actors), assets: ona.Write(ona.gfx.Assets)) !void {
 | 
					 | 
				
			||||||
	config.res.width, config.res.height = .{1280, 720};
 | 
					 | 
				
			||||||
	actors.res.body_texture = try assets.res.load_texture_file(coral.files.bundle, "actor.bmp");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	actors.res.render_texture = try assets.res.load_texture(.{
 | 
					 | 
				
			||||||
		.format = .rgba8,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		.access = .{
 | 
					 | 
				
			||||||
			.render = .{
 | 
					 | 
				
			||||||
				.width = config.res.width,
 | 
					 | 
				
			||||||
				.height = config.res.height,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	actors.res.ca_effect = try assets.res.load_effect_file(coral.files.bundle, "./ca.frag.spv");
 | 
					 | 
				
			||||||
	actors.res.crt_effect = try assets.res.load_effect_file(coral.files.bundle, "./crt.frag.spv");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try actors.res.instances.push_grow(.{0, 0});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn exit(actors: ona.Write(Actors)) void {
 | 
					 | 
				
			||||||
	actors.res.instances.deinit();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn render(commands: ona.gfx.Commands, actors: ona.Write(Actors), app: ona.Read(ona.App)) !void {
 | 
					 | 
				
			||||||
	try commands.set_target(.{
 | 
					 | 
				
			||||||
		.texture = actors.res.render_texture,
 | 
					 | 
				
			||||||
		.clear_color = ona.gfx.colors.black,
 | 
					 | 
				
			||||||
		.clear_depth = 0,
 | 
					 | 
				
			||||||
		.clear_stencil = 0,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try commands.draw_texture(.{
 | 
					 | 
				
			||||||
		.texture = .default,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		.transform = .{
 | 
					 | 
				
			||||||
			.origin = .{1280 / 2, 720 / 2},
 | 
					 | 
				
			||||||
			.xbasis = .{1280, 0},
 | 
					 | 
				
			||||||
			.ybasis = .{0, 720},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try commands.set_effect(.{
 | 
					 | 
				
			||||||
		.effect = actors.res.crt_effect,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		.properties = std.mem.asBytes(&CRT{
 | 
					 | 
				
			||||||
			.width = 1280,
 | 
					 | 
				
			||||||
			.height = 720,
 | 
					 | 
				
			||||||
			.time = @floatCast(app.res.elapsed_time),
 | 
					 | 
				
			||||||
		}),
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try commands.set_target(.{
 | 
					 | 
				
			||||||
		.texture = .backbuffer,
 | 
					 | 
				
			||||||
		.clear_color = null,
 | 
					 | 
				
			||||||
		.clear_depth = null,
 | 
					 | 
				
			||||||
		.clear_stencil = null,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try commands.draw_texture(.{
 | 
					 | 
				
			||||||
		.texture = actors.res.render_texture,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		.transform = .{
 | 
					 | 
				
			||||||
			.origin = .{1280 / 2, 720 / 2},
 | 
					 | 
				
			||||||
			.xbasis = .{1280, 0},
 | 
					 | 
				
			||||||
			.ybasis = .{0, 720},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn update(player: ona.Read(Player), actors: ona.Write(Actors), mapping: ona.Read(ona.act.Mapping)) !void {
 | 
					 | 
				
			||||||
	actors.res.instances.values[0] += .{
 | 
					 | 
				
			||||||
		mapping.res.axis_strength(player.res.move_x) * 10,
 | 
					 | 
				
			||||||
		mapping.res.axis_strength(player.res.move_y) * 10,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn setup(world: *ona.World, events: ona.App.Events) !void {
 | 
					 | 
				
			||||||
	try world.set_state(Actors{});
 | 
					 | 
				
			||||||
	try world.set_state(Player{});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try world.on_event(events.load, ona.system_fn(load), .{.label = "load"});
 | 
					 | 
				
			||||||
	try world.on_event(events.update, ona.system_fn(update), .{.label = "update"});
 | 
					 | 
				
			||||||
	try world.on_event(events.exit, ona.system_fn(exit), .{.label = "exit"});
 | 
					 | 
				
			||||||
	try world.on_event(events.render, ona.system_fn(render), .{.label = "render actors"});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,21 +1,19 @@
 | 
				
			|||||||
const coral = @import("coral");
 | 
					const builtin = @import("builtin");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const dag = @import("./dag.zig");
 | 
					const ona = @import("./ona.zig");
 | 
				
			||||||
 | 
					 | 
				
			||||||
const states = @import("./states.zig");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const World = @import("./World.zig");
 | 
					thread_pool: ?*std.Thread.Pool = null,
 | 
				
			||||||
 | 
					thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable,
 | 
				
			||||||
 | 
					event_systems: ona.stack.Sequential(Schedule),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const BindContext = struct {
 | 
					pub const BindContext = struct {
 | 
				
			||||||
	node: dag.Node,
 | 
						node: ona.dag.Node,
 | 
				
			||||||
	systems: *Schedule,
 | 
						systems: *Schedule,
 | 
				
			||||||
	world: *World,
 | 
						world: *Self,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub const ResourceAccess = std.meta.Tag(Schedule.ResourceAccess);
 | 
						pub fn accesses_state(self: BindContext, access: std.meta.Tag(StateAccess), id: ona.TypeID) bool {
 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn accesses_resource(self: BindContext, access: ResourceAccess, id: states.TypeID) bool {
 | 
					 | 
				
			||||||
		const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses;
 | 
							const resource_accesses = &self.systems.graph.get_ptr(self.node).?.resource_accesses;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (resource_accesses.values) |resource_access| {
 | 
							for (resource_accesses.values) |resource_access| {
 | 
				
			||||||
@ -42,23 +40,23 @@ pub const BindContext = struct {
 | 
				
			|||||||
			return null;
 | 
								return null;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const id = states.type_id(Resource);
 | 
							const id = ona.type_id(Resource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!self.accesses_resource(.read_write, id)) {
 | 
							if (!self.accesses_state(.read_write, id)) {
 | 
				
			||||||
			try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id});
 | 
								try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_write = id});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const read_write_resource_nodes = lazily_create: {
 | 
							const read_write_resource_nodes = lazily_create: {
 | 
				
			||||||
			break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: {
 | 
								break: lazily_create self.systems.read_write_resource_id_nodes.get(id) orelse insert: {
 | 
				
			||||||
				std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
 | 
									std.debug.assert(try self.systems.read_write_resource_id_nodes.emplace(id, .{
 | 
				
			||||||
					.allocator = coral.heap.allocator,
 | 
										.allocator = ona.heap.allocator,
 | 
				
			||||||
				}));
 | 
									}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				break: insert self.systems.read_write_resource_id_nodes.get(id).?;
 | 
									break: insert self.systems.read_write_resource_id_nodes.get(id).?;
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (std.mem.indexOfScalar(dag.Node, read_write_resource_nodes.values, self.node) == null) {
 | 
							if (std.mem.indexOfScalar(ona.dag.Node, read_write_resource_nodes.values, self.node) == null) {
 | 
				
			||||||
			try read_write_resource_nodes.push_grow(self.node);
 | 
								try read_write_resource_nodes.push_grow(self.node);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -70,23 +68,23 @@ pub const BindContext = struct {
 | 
				
			|||||||
			return null;
 | 
								return null;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const id = states.type_id(Resource);
 | 
							const id = ona.type_id(Resource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (!self.accesses_resource(.read_only, id)) {
 | 
							if (!self.accesses_state(.read_only, id)) {
 | 
				
			||||||
			try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id});
 | 
								try self.systems.graph.get_ptr(self.node).?.resource_accesses.push_grow(.{.read_only = id});
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const read_only_resource_nodes = lazily_create: {
 | 
							const read_only_resource_nodes = lazily_create: {
 | 
				
			||||||
			break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: {
 | 
								break: lazily_create self.systems.read_only_resource_id_nodes.get(id) orelse insert: {
 | 
				
			||||||
				std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
 | 
									std.debug.assert(try self.systems.read_only_resource_id_nodes.emplace(id, .{
 | 
				
			||||||
					.allocator = coral.heap.allocator,
 | 
										.allocator = ona.heap.allocator,
 | 
				
			||||||
				}));
 | 
									}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				break: insert self.systems.read_only_resource_id_nodes.get(id).?;
 | 
									break: insert self.systems.read_only_resource_id_nodes.get(id).?;
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (std.mem.indexOfScalar(dag.Node, read_only_resource_nodes.values, self.node) == null) {
 | 
							if (std.mem.indexOfScalar(ona.dag.Node, read_only_resource_nodes.values, self.node) == null) {
 | 
				
			||||||
			try read_only_resource_nodes.push_grow(self.node);
 | 
								try read_only_resource_nodes.push_grow(self.node);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -94,40 +92,6 @@ pub const BindContext = struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Info = struct {
 | 
					 | 
				
			||||||
	execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void,
 | 
					 | 
				
			||||||
	parameters: [max_parameters]*const Parameter = undefined,
 | 
					 | 
				
			||||||
	parameter_count: u4 = 0,
 | 
					 | 
				
			||||||
	thread_restriction: states.ThreadRestriction = .none,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const Parameter = struct {
 | 
					 | 
				
			||||||
		thread_restriction: states.ThreadRestriction,
 | 
					 | 
				
			||||||
		init: *const fn (*anyopaque, *anyopaque) void,
 | 
					 | 
				
			||||||
		bind: *const fn (std.mem.Allocator, BindContext) std.mem.Allocator.Error!*anyopaque,
 | 
					 | 
				
			||||||
		unbind: *const fn (std.mem.Allocator, *anyopaque, UnbindContext) void,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn used_parameters(self: *const Info) []const *const Parameter {
 | 
					 | 
				
			||||||
		return self.parameters[0 .. self.parameter_count];
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Order = struct {
 | 
					 | 
				
			||||||
	label: []const u8 = "",
 | 
					 | 
				
			||||||
	run_after: []const *const Info = &.{},
 | 
					 | 
				
			||||||
	run_before: []const *const Info = &.{},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Schedule = struct {
 | 
					 | 
				
			||||||
	label: [:0]const u8,
 | 
					 | 
				
			||||||
	graph: Graph,
 | 
					 | 
				
			||||||
	arena: std.heap.ArenaAllocator,
 | 
					 | 
				
			||||||
	system_id_nodes: coral.map.Hashed(usize, NodeBundle, coral.map.usize_traits),
 | 
					 | 
				
			||||||
	read_write_resource_id_nodes: ResourceNodeBundle,
 | 
					 | 
				
			||||||
	read_only_resource_id_nodes: ResourceNodeBundle,
 | 
					 | 
				
			||||||
	parallel_work_bundles: ParallelNodeBundles,
 | 
					 | 
				
			||||||
	blocking_work: NodeBundle,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const Dependency = struct {
 | 
					const Dependency = struct {
 | 
				
			||||||
	kind: Kind,
 | 
						kind: Kind,
 | 
				
			||||||
	id: usize,
 | 
						id: usize,
 | 
				
			||||||
@ -138,26 +102,38 @@ pub const Schedule = struct {
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const Graph = dag.Graph(struct {
 | 
					const Graph = ona.dag.Graph(struct {
 | 
				
			||||||
		info: *const Info,
 | 
						info: *const ona.SystemInfo,
 | 
				
			||||||
	label: [:0]u8,
 | 
						label: [:0]u8,
 | 
				
			||||||
	dependencies: []Dependency,
 | 
						dependencies: []Dependency,
 | 
				
			||||||
		parameter_states: [max_parameters]*anyopaque = [_]*anyopaque{undefined} ** max_parameters,
 | 
						parameter_states: [ona.SystemInfo.max_parameters]*anyopaque = [_]*anyopaque{undefined} ** ona.SystemInfo.max_parameters,
 | 
				
			||||||
		resource_accesses: coral.stack.Sequential(ResourceAccess),
 | 
						resource_accesses: ona.stack.Sequential(StateAccess),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const NodeBundle = coral.stack.Sequential(dag.Node);
 | 
					pub const Event = enum (usize) { _ };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const ParallelNodeBundles = coral.stack.Sequential(NodeBundle);
 | 
					const ParallelNodeBundles = ona.stack.Sequential(NodeBundle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const ResourceAccess = union (enum) {
 | 
					const NodeBundle = ona.stack.Sequential(ona.dag.Node);
 | 
				
			||||||
		read_only: states.TypeID,
 | 
					
 | 
				
			||||||
		read_write: states.TypeID,
 | 
					const StateAccess = union (enum) {
 | 
				
			||||||
 | 
						read_only: ona.TypeID,
 | 
				
			||||||
 | 
						read_write: ona.TypeID,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const ResourceNodeBundle = coral.map.Hashed(states.TypeID, NodeBundle, coral.map.enum_traits(states.TypeID));
 | 
					const ResourceNodeBundle = ona.map.Hashed(ona.TypeID, NodeBundle, ona.map.enum_traits(ona.TypeID));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn deinit(self: *Schedule, world: *World) void {
 | 
					const Schedule = struct {
 | 
				
			||||||
 | 
						label: [:0]const u8,
 | 
				
			||||||
 | 
						graph: Graph,
 | 
				
			||||||
 | 
						arena: std.heap.ArenaAllocator,
 | 
				
			||||||
 | 
						system_id_nodes: ona.map.Hashed(usize, NodeBundle, ona.map.usize_traits),
 | 
				
			||||||
 | 
						read_write_resource_id_nodes: ResourceNodeBundle,
 | 
				
			||||||
 | 
						read_only_resource_id_nodes: ResourceNodeBundle,
 | 
				
			||||||
 | 
						parallel_work_bundles: ParallelNodeBundles,
 | 
				
			||||||
 | 
						blocking_work: NodeBundle,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn deinit(self: *Schedule, world: *Self) void {
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			var nodes = self.system_id_nodes.entries();
 | 
								var nodes = self.system_id_nodes.entries();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -194,8 +170,8 @@ pub const Schedule = struct {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			system.resource_accesses.deinit();
 | 
								system.resource_accesses.deinit();
 | 
				
			||||||
			coral.heap.allocator.free(system.dependencies);
 | 
								ona.heap.allocator.free(system.dependencies);
 | 
				
			||||||
			coral.heap.allocator.free(system.label);
 | 
								ona.heap.allocator.free(system.label);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (self.parallel_work_bundles.values) |*bundle| {
 | 
							for (self.parallel_work_bundles.values) |*bundle| {
 | 
				
			||||||
@ -211,7 +187,7 @@ pub const Schedule = struct {
 | 
				
			|||||||
		self.arena.deinit();
 | 
							self.arena.deinit();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn run(self: *Schedule, world: *World) anyerror!void {
 | 
						pub fn run(self: *Schedule, world: *Self) anyerror!void {
 | 
				
			||||||
		if (self.is_invalidated()) {
 | 
							if (self.is_invalidated()) {
 | 
				
			||||||
			const work = struct {
 | 
								const work = struct {
 | 
				
			||||||
				fn regenerate_graph(schedule: *Schedule) !void {
 | 
									fn regenerate_graph(schedule: *Schedule) !void {
 | 
				
			||||||
@ -276,7 +252,7 @@ pub const Schedule = struct {
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: dag.Node) !void {
 | 
									fn populate_bundle(bundle: *NodeBundle, graph: *Graph, node: ona.dag.Node) !void {
 | 
				
			||||||
					std.debug.assert(graph.mark_visited(node));
 | 
										std.debug.assert(graph.mark_visited(node));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					for (graph.edge_nodes(node).?) |edge| {
 | 
										for (graph.edge_nodes(node).?) |edge| {
 | 
				
			||||||
@ -300,7 +276,7 @@ pub const Schedule = struct {
 | 
				
			|||||||
							continue;
 | 
												continue;
 | 
				
			||||||
						}
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						try schedule.parallel_work_bundles.push_grow(.{.allocator = coral.heap.allocator});
 | 
											try schedule.parallel_work_bundles.push_grow(.{.allocator = ona.heap.allocator});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						const bundle = schedule.parallel_work_bundles.get_ptr().?;
 | 
											const bundle = schedule.parallel_work_bundles.get_ptr().?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -381,21 +357,21 @@ pub const Schedule = struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule {
 | 
						pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule {
 | 
				
			||||||
		var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
 | 
							var arena = std.heap.ArenaAllocator.init(ona.heap.allocator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		errdefer arena.deinit();
 | 
							errdefer arena.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const duped_label = try arena.allocator().dupeZ(u8, label);
 | 
							const duped_label = try arena.allocator().dupeZ(u8, label);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return .{
 | 
							return .{
 | 
				
			||||||
			.graph = Graph.init(coral.heap.allocator),
 | 
								.graph = Graph.init(ona.heap.allocator),
 | 
				
			||||||
			.label = duped_label,
 | 
								.label = duped_label,
 | 
				
			||||||
			.arena = arena,
 | 
								.arena = arena,
 | 
				
			||||||
			.system_id_nodes = .{.allocator = coral.heap.allocator},
 | 
								.system_id_nodes = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
			.read_write_resource_id_nodes = .{.allocator = coral.heap.allocator},
 | 
								.read_write_resource_id_nodes = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
			.read_only_resource_id_nodes = .{.allocator = coral.heap.allocator},
 | 
								.read_only_resource_id_nodes = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
			.parallel_work_bundles = .{.allocator = coral.heap.allocator},
 | 
								.parallel_work_bundles = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
			.blocking_work = .{.allocator = coral.heap.allocator},
 | 
								.blocking_work = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -413,7 +389,7 @@ pub const Schedule = struct {
 | 
				
			|||||||
		return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty();
 | 
							return self.parallel_work_bundles.is_empty() and self.blocking_work.is_empty();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn then(self: *Schedule, world: *World, info: *const Info, order: Order) std.mem.Allocator.Error!void {
 | 
						pub fn then(self: *Schedule, world: *Self, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
 | 
				
			||||||
		const nodes = lazily_create: {
 | 
							const nodes = lazily_create: {
 | 
				
			||||||
			const system_id = @intFromPtr(info);
 | 
								const system_id = @intFromPtr(info);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -428,7 +404,7 @@ pub const Schedule = struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		const dependencies = init: {
 | 
							const dependencies = init: {
 | 
				
			||||||
			const total_run_orders = order.run_after.len + order.run_before.len;
 | 
								const total_run_orders = order.run_after.len + order.run_before.len;
 | 
				
			||||||
			const dependencies = try coral.heap.allocator.alloc(Dependency, total_run_orders);
 | 
								const dependencies = try ona.heap.allocator.alloc(Dependency, total_run_orders);
 | 
				
			||||||
			var dependencies_written = @as(usize, 0);
 | 
								var dependencies_written = @as(usize, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for (order.run_after) |after_system| {
 | 
								for (order.run_after) |after_system| {
 | 
				
			||||||
@ -452,17 +428,17 @@ pub const Schedule = struct {
 | 
				
			|||||||
			break: init dependencies;
 | 
								break: init dependencies;
 | 
				
			||||||
		};
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		errdefer coral.heap.allocator.free(dependencies);
 | 
							errdefer ona.heap.allocator.free(dependencies);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const label = try coral.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
 | 
							const label = try ona.heap.allocator.dupeZ(u8, if (order.label.len == 0) "anonymous system" else order.label);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		errdefer coral.heap.allocator.free(label);
 | 
							errdefer ona.heap.allocator.free(label);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const node = try self.graph.append(.{
 | 
							const node = try self.graph.append(.{
 | 
				
			||||||
			.info = info,
 | 
								.info = info,
 | 
				
			||||||
			.label = label,
 | 
								.label = label,
 | 
				
			||||||
			.dependencies = dependencies,
 | 
								.dependencies = dependencies,
 | 
				
			||||||
			.resource_accesses = .{.allocator = coral.heap.allocator},
 | 
								.resource_accesses = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const system = self.graph.get_ptr(node).?;
 | 
							const system = self.graph.get_ptr(node).?;
 | 
				
			||||||
@ -491,8 +467,144 @@ pub const Schedule = struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const UnbindContext = struct {
 | 
					const Self = @This();
 | 
				
			||||||
	world: *World,
 | 
					
 | 
				
			||||||
 | 
					const StateTable = struct {
 | 
				
			||||||
 | 
						arena: std.heap.ArenaAllocator,
 | 
				
			||||||
 | 
						table: ona.map.Hashed(ona.TypeID, Entry, ona.map.enum_traits(ona.TypeID)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const Entry = struct {
 | 
				
			||||||
 | 
							ptr: *anyopaque,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const max_parameters = 16;
 | 
						fn deinit(self: *StateTable) void {
 | 
				
			||||||
 | 
							self.table.deinit();
 | 
				
			||||||
 | 
							self.arena.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							self.* = undefined;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fn get(self: StateTable, comptime Resource: type) ?*Resource {
 | 
				
			||||||
 | 
							if (self.table.get(ona.type_id(Resource))) |entry| {
 | 
				
			||||||
 | 
								return @ptrCast(@alignCast(entry.ptr));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fn init() StateTable {
 | 
				
			||||||
 | 
							return .{
 | 
				
			||||||
 | 
								.arena = std.heap.ArenaAllocator.init(ona.heap.allocator),
 | 
				
			||||||
 | 
								.table = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fn set_get(self: *StateTable, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
 | 
				
			||||||
 | 
							try self.set(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return self.get(@TypeOf(value)).?;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fn set(self: *StateTable, value: anytype) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
							const Value = @TypeOf(value);
 | 
				
			||||||
 | 
							const value_id = ona.type_id(Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (self.table.get(value_id)) |entry| {
 | 
				
			||||||
 | 
								@as(*Value, @ptrCast(@alignCast(entry.ptr))).* = value;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								const resource_allocator = self.arena.allocator();
 | 
				
			||||||
 | 
								const allocated_resource = try resource_allocator.create(Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								errdefer resource_allocator.destroy(allocated_resource);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								std.debug.assert(try self.table.emplace(value_id, .{
 | 
				
			||||||
 | 
									.ptr = allocated_resource,
 | 
				
			||||||
 | 
								}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								allocated_resource.* = value;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const UnbindContext = struct {
 | 
				
			||||||
 | 
						world: *Self,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn create_event(self: *Self, label: []const u8) std.mem.Allocator.Error!Event {
 | 
				
			||||||
 | 
						var systems = try Schedule.init(label);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errdefer systems.deinit(self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const index = self.event_systems.len();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try self.event_systems.push_grow(systems);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return @enumFromInt(index);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
						for (self.event_systems.values) |*schedule| {
 | 
				
			||||||
 | 
							schedule.deinit(self);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (&self.thread_restricted_resources) |*resources| {
 | 
				
			||||||
 | 
							resources.deinit();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (self.thread_pool) |thread_pool| {
 | 
				
			||||||
 | 
							thread_pool.deinit();
 | 
				
			||||||
 | 
							ona.heap.allocator.destroy(thread_pool);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						self.event_systems.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						self.* = undefined;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get_params(self: Self, comptime Value: type) ona.Params(Value) {
 | 
				
			||||||
 | 
						const params = self.get_state(ona.Params(Value)) orelse {
 | 
				
			||||||
 | 
							return .{};
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return params.*;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn get_state(self: Self, comptime Value: type) ?*Value {
 | 
				
			||||||
 | 
						return @ptrCast(@alignCast(self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(Value))].get(Value)));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@TypeOf(value) {
 | 
				
			||||||
 | 
						return self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(@TypeOf(value)))].set_get(value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn init(thread_count: u32) std.Thread.SpawnError!Self {
 | 
				
			||||||
 | 
						var world = Self{
 | 
				
			||||||
 | 
							.thread_restricted_resources = .{StateTable.init(), StateTable.init()},
 | 
				
			||||||
 | 
							.event_systems = .{.allocator = ona.heap.allocator},
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (thread_count != 0 and !builtin.single_threaded) {
 | 
				
			||||||
 | 
							const thread_pool = try ona.heap.allocator.create(std.Thread.Pool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							try thread_pool.init(.{
 | 
				
			||||||
 | 
								.allocator = ona.heap.allocator,
 | 
				
			||||||
 | 
								.n_jobs = thread_count,
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							world.thread_pool = thread_pool;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return world;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn on_event(self: *Self, event: Event, action: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
						try self.event_systems.values[@intFromEnum(event)].then(self, action, order);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn run_event(self: *Self, event: Event) anyerror!void {
 | 
				
			||||||
 | 
						try self.event_systems.values[@intFromEnum(event)].run(self);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
						try self.thread_restricted_resources[@intFromEnum(ona.thread_restriction(@TypeOf(value)))].set(value);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,59 +0,0 @@
 | 
				
			|||||||
const coral = @import("coral");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const flow = @import("flow");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ona = @import("./ona.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Axis = struct {
 | 
					 | 
				
			||||||
	keys: ?[2]ona.gfx.Input.Key = null,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Mapping = struct {
 | 
					 | 
				
			||||||
	keys_pressed: ScancodeSet = ScancodeSet.initEmpty(),
 | 
					 | 
				
			||||||
	keys_held: ScancodeSet = ScancodeSet.initEmpty(),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const ScancodeSet = std.bit_set.StaticBitSet(512);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn axis_strength(self: Mapping, axis: Axis) f32 {
 | 
					 | 
				
			||||||
		if (axis.keys) |keys| {
 | 
					 | 
				
			||||||
			const key_down, const key_up = keys;
 | 
					 | 
				
			||||||
			const is_key_down_held = self.keys_held.isSet(key_down.scancode());
 | 
					 | 
				
			||||||
			const is_key_up_held = self.keys_held.isSet(key_up.scancode());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (is_key_down_held or is_key_up_held) {
 | 
					 | 
				
			||||||
				return
 | 
					 | 
				
			||||||
					@as(f32, @floatFromInt(@intFromBool(is_key_up_held))) -
 | 
					 | 
				
			||||||
					@as(f32, @floatFromInt(@intFromBool(is_key_down_held)));
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return 0;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn setup(world: *flow.World, events: ona.App.Events) std.mem.Allocator.Error!void {
 | 
					 | 
				
			||||||
	try world.set_state(Mapping{});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try world.on_event(events.pre_update, flow.system_fn(update), .{
 | 
					 | 
				
			||||||
		.label = "update act",
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn update(inputs: ona.msg.Receive(ona.gfx.Input), mapping: flow.Write(Mapping)) void {
 | 
					 | 
				
			||||||
	mapping.res.keys_pressed = Mapping.ScancodeSet.initEmpty();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for (inputs.messages()) |message| {
 | 
					 | 
				
			||||||
		switch (message) {
 | 
					 | 
				
			||||||
			.key_down => |key| {
 | 
					 | 
				
			||||||
				mapping.res.keys_pressed.set(key.scancode());
 | 
					 | 
				
			||||||
				mapping.res.keys_held.set(key.scancode());
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			.key_up => |key| {
 | 
					 | 
				
			||||||
				mapping.res.keys_held.unset(key.scancode());
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,19 +1,10 @@
 | 
				
			|||||||
const coral = @import("./coral.zig");
 | 
					const ona = @import("./ona.zig");
 | 
				
			||||||
 | 
					 | 
				
			||||||
const io = @import("./io.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const scalars = @import("./scalars.zig");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const DecimalFormat = struct {
 | 
					pub const DecimalFormat = struct {
 | 
				
			||||||
	delimiter: []const coral.io.Byte,
 | 
						delimiter: []const u8 = "",
 | 
				
			||||||
	positive_prefix: enum {none, plus, space},
 | 
						positive_prefix: enum {none, plus, space} = .none,
 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const default = DecimalFormat{
 | 
					 | 
				
			||||||
		.delimiter = "",
 | 
					 | 
				
			||||||
		.positive_prefix = .none,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn parse(self: DecimalFormat, utf8: []const u8, comptime Decimal: type) ?Decimal {
 | 
						pub fn parse(self: DecimalFormat, utf8: []const u8, comptime Decimal: type) ?Decimal {
 | 
				
			||||||
		if (utf8.len == 0) {
 | 
							if (utf8.len == 0) {
 | 
				
			||||||
@ -35,14 +26,14 @@ pub const DecimalFormat = struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
					switch (code) {
 | 
										switch (code) {
 | 
				
			||||||
						'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
 | 
											'0', '1', '2', '3', '4', '5', '6', '7', '8', '9' => {
 | 
				
			||||||
							const offset_code = scalars.sub(code, '0') orelse return null;
 | 
												const offset_code = ona.scalars.sub(code, '0') orelse return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							result = scalars.mul(result, radix) orelse return null;
 | 
												result = ona.scalars.mul(result, radix) orelse return null;
 | 
				
			||||||
							result = scalars.add(result, offset_code) orelse return null;
 | 
												result = ona.scalars.add(result, offset_code) orelse return null;
 | 
				
			||||||
						},
 | 
											},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
						else => {
 | 
											else => {
 | 
				
			||||||
							if (self.delimiter.len == 0 or !coral.are_equal(self.delimiter, utf8[index ..])) {
 | 
												if (self.delimiter.len == 0 or !std.mem.eql(u8, self.delimiter, utf8[index ..])) {
 | 
				
			||||||
								return null;
 | 
													return null;
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
						},
 | 
											},
 | 
				
			||||||
@ -115,9 +106,9 @@ pub const DecimalFormat = struct {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn format(self: DecimalFormat, writer: io.Writer, value: anytype) io.Error!void {
 | 
						pub fn format(self: DecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void {
 | 
				
			||||||
		if (value == 0) {
 | 
							if (value == 0) {
 | 
				
			||||||
			return io.write_all(writer, switch (self.positive_prefix) {
 | 
								return ona.io.write_all(writer, switch (self.positive_prefix) {
 | 
				
			||||||
				.none => "0",
 | 
									.none => "0",
 | 
				
			||||||
				.plus => "+0",
 | 
									.plus => "+0",
 | 
				
			||||||
				.space => " 0",
 | 
									.space => " 0",
 | 
				
			||||||
@ -151,12 +142,12 @@ pub const DecimalFormat = struct {
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				return io.write_all(writer, buffer[buffer_start ..]);
 | 
									return ona.io.write_all(writer, buffer[buffer_start ..]);
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			.Float => |float| {
 | 
								.Float => |float| {
 | 
				
			||||||
				if (value < 0) {
 | 
									if (value < 0) {
 | 
				
			||||||
					try io.write_all(writer, "-");
 | 
										try ona.io.write_all(writer, "-");
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				const Float = @TypeOf(value);
 | 
									const Float = @TypeOf(value);
 | 
				
			||||||
@ -164,7 +155,7 @@ pub const DecimalFormat = struct {
 | 
				
			|||||||
				const integer = @as(Int, @intFromFloat(value));
 | 
									const integer = @as(Int, @intFromFloat(value));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				try self.format(writer, integer);
 | 
									try self.format(writer, integer);
 | 
				
			||||||
				try io.write_all(writer, ".");
 | 
									try ona.io.write_all(writer, ".");
 | 
				
			||||||
				try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
 | 
									try self.format(writer, @as(Int, @intFromFloat((value - @as(Float, @floatFromInt(integer))) * 100)));
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -173,6 +164,12 @@ pub const DecimalFormat = struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "decimal parsing" {
 | 
				
			||||||
 | 
						const format = DecimalFormat{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try std.testing.expectEqual(format.parse("69", i64), 69);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const HexadecimalFormat = struct {
 | 
					pub const HexadecimalFormat = struct {
 | 
				
			||||||
	delimiter: []const u8 = "",
 | 
						delimiter: []const u8 = "",
 | 
				
			||||||
	positive_prefix: enum {none, plus, space} = .none,
 | 
						positive_prefix: enum {none, plus, space} = .none,
 | 
				
			||||||
@ -184,7 +181,7 @@ pub const HexadecimalFormat = struct {
 | 
				
			|||||||
		.casing = .lower,
 | 
							.casing = .lower,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn format(self: HexadecimalFormat, writer: io.Writer, value: anytype) io.Error!void {
 | 
						pub fn format(self: HexadecimalFormat, writer: ona.io.Writer, value: anytype) ona.io.Error!void {
 | 
				
			||||||
		// TODO: Implement.
 | 
							// TODO: Implement.
 | 
				
			||||||
		_ = self;
 | 
							_ = self;
 | 
				
			||||||
		_ = writer;
 | 
							_ = writer;
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
const coral = @import("coral");
 | 
					const ona = @import("./ona.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -7,9 +7,9 @@ pub fn Graph(comptime Payload: type) type {
 | 
				
			|||||||
		node_count: usize = 0,
 | 
							node_count: usize = 0,
 | 
				
			||||||
		table: NodeTables,
 | 
							table: NodeTables,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const NodeTables = coral.stack.Parallel(struct {
 | 
							const NodeTables = ona.stack.Parallel(struct {
 | 
				
			||||||
			payload: Payload,
 | 
								payload: Payload,
 | 
				
			||||||
			edges: coral.stack.Sequential(Node),
 | 
								edges: ona.stack.Sequential(Node),
 | 
				
			||||||
			is_occupied: bool = true,
 | 
								is_occupied: bool = true,
 | 
				
			||||||
			is_visited: bool = false,
 | 
								is_visited: bool = false,
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
@ -1,4 +0,0 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
pub usingnamespace @cImport({
 | 
					 | 
				
			||||||
	@cInclude("SDL2/SDL.h");
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
@ -28,14 +28,14 @@ pub const Storage = struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	pub const VTable = struct {
 | 
						pub const VTable = struct {
 | 
				
			||||||
		stat: *const fn (*anyopaque, []const u8) AccessError!Stat,
 | 
							stat: *const fn (*anyopaque, []const u8) AccessError!Stat,
 | 
				
			||||||
		read: *const fn (*anyopaque, []const u8, usize, []io.Byte) AccessError!usize,
 | 
							read: *const fn (*anyopaque, []const u8, usize, []u8) AccessError!usize,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn read(self: Storage, path: []const u8, output: []io.Byte, offset: u64) AccessError!usize {
 | 
						pub fn read(self: Storage, path: []const u8, output: []u8, offset: u64) AccessError!usize {
 | 
				
			||||||
		return self.vtable.read(self.userdata, path, offset, output);
 | 
							return self.vtable.read(self.userdata, path, offset, output);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn read_all(self: Storage, path: []const u8, output: []io.Byte, options: ReadAllOptions) ReadAllError![]const io.Byte {
 | 
						pub fn read_all(self: Storage, path: []const u8, output: []u8, options: ReadAllOptions) ReadAllError![]const u8 {
 | 
				
			||||||
		const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output);
 | 
							const bytes_read = try self.vtable.read(self.userdata, path, options.offset, output);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) {
 | 
							if (try self.vtable.read(self.userdata, path, options.offset, output) != output.len) {
 | 
				
			||||||
@ -45,8 +45,8 @@ pub const Storage = struct {
 | 
				
			|||||||
		return output[0 .. bytes_read];
 | 
							return output[0 .. bytes_read];
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]io.Byte {
 | 
						pub fn read_alloc(self: Storage, path: []const u8, allocator: std.mem.Allocator, options: ReadAllOptions) (std.mem.Allocator.Error || ReadAllError)![]u8 {
 | 
				
			||||||
		const buffer = try allocator.alloc(io.Byte, @min((try self.stat(path)).size, options.limit));
 | 
							const buffer = try allocator.alloc(u8, @min((try self.stat(path)).size, options.limit));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		errdefer allocator.free(buffer);
 | 
							errdefer allocator.free(buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -69,7 +69,7 @@ pub const Storage = struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
 | 
						pub fn read_native(self: Storage, path: []const u8, offset: u64, comptime Type: type) AccessError!?Type {
 | 
				
			||||||
		var buffer = @as([@sizeOf(Type)]io.Byte, undefined);
 | 
							var buffer = @as([@sizeOf(Type)]u8, undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
 | 
							if (try self.vtable.read(self.userdata, path, offset, &buffer) != buffer.len) {
 | 
				
			||||||
			return null;
 | 
								return null;
 | 
				
			||||||
@ -105,7 +105,7 @@ pub const bundle = init: {
 | 
				
			|||||||
			return buffer;
 | 
								return buffer;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn read(_: *anyopaque, path: []const u8, offset: usize, output: []io.Byte) AccessError!usize {
 | 
							fn read(_: *anyopaque, path: []const u8, offset: usize, output: []u8) AccessError!usize {
 | 
				
			||||||
			var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
 | 
								var file = std.fs.openFileAbsoluteZ(&(try full_path(path)), .{.mode = .read_only}) catch |open_error| {
 | 
				
			||||||
				return switch (open_error) {
 | 
									return switch (open_error) {
 | 
				
			||||||
					error.FileNotFound => error.FileNotFound,
 | 
										error.FileNotFound => error.FileNotFound,
 | 
				
			||||||
							
								
								
									
										534
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							
							
						
						
									
										534
									
								
								src/ona/gfx.zig
									
									
									
									
									
								
							@ -1,534 +0,0 @@
 | 
				
			|||||||
pub const colors = @import("./gfx/colors.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const coral = @import("coral");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ext = @import("./ext.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const flow = @import("flow");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ona = @import("./ona.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const rendering = @import("./gfx/rendering.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Assets = struct {
 | 
					 | 
				
			||||||
	window: *ext.SDL_Window,
 | 
					 | 
				
			||||||
	texture_formats: coral.stack.Sequential(TextureFormat),
 | 
					 | 
				
			||||||
	frame_rendered: std.Thread.ResetEvent = .{},
 | 
					 | 
				
			||||||
	pending_work: WorkQueue = .{},
 | 
					 | 
				
			||||||
	has_worker_thread: ?std.Thread = null,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const LoadError = std.mem.Allocator.Error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const LoadFileError = LoadError || coral.files.ReadAllError || error {
 | 
					 | 
				
			||||||
		FormatUnsupported,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const TextureFormat = struct {
 | 
					 | 
				
			||||||
		extension: []const u8,
 | 
					 | 
				
			||||||
		load_file: *const fn (*std.heap.ArenaAllocator, coral.files.Storage, []const u8) LoadFileError!Texture.Desc,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const WorkQueue = coral.asyncio.BlockingQueue(1024, union (enum) {
 | 
					 | 
				
			||||||
		load_effect: LoadEffectWork,
 | 
					 | 
				
			||||||
		load_texture: LoadTextureWork,
 | 
					 | 
				
			||||||
		render_frame: RenderFrameWork,
 | 
					 | 
				
			||||||
		shutdown,
 | 
					 | 
				
			||||||
		unload_effect: UnloadEffectWork,
 | 
					 | 
				
			||||||
		unload_texture: UnloadTextureWork,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const LoadEffectWork = struct {
 | 
					 | 
				
			||||||
			desc: Effect.Desc,
 | 
					 | 
				
			||||||
			loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Effect),
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const LoadTextureWork = struct {
 | 
					 | 
				
			||||||
			desc: Texture.Desc,
 | 
					 | 
				
			||||||
			loaded: *coral.asyncio.Future(std.mem.Allocator.Error!Texture),
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const RenderFrameWork = struct {
 | 
					 | 
				
			||||||
			clear_color: Color,
 | 
					 | 
				
			||||||
			width: u16,
 | 
					 | 
				
			||||||
			height: u16,
 | 
					 | 
				
			||||||
			finished: *std.Thread.ResetEvent,
 | 
					 | 
				
			||||||
			has_command_params: ?*flow.Params(Commands).Node,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const UnloadEffectWork = struct {
 | 
					 | 
				
			||||||
			handle: Effect,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const UnloadTextureWork = struct {
 | 
					 | 
				
			||||||
			handle: Texture,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fn deinit(self: *Assets) void {
 | 
					 | 
				
			||||||
		self.pending_work.enqueue(.shutdown);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		if (self.has_worker_thread) |worker_thread| {
 | 
					 | 
				
			||||||
			worker_thread.join();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		self.texture_formats.deinit();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		self.* = undefined;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fn init() !Assets {
 | 
					 | 
				
			||||||
		const window = create: {
 | 
					 | 
				
			||||||
			const position = ext.SDL_WINDOWPOS_CENTERED;
 | 
					 | 
				
			||||||
			const flags = ext.SDL_WINDOW_OPENGL;
 | 
					 | 
				
			||||||
			const width = 640;
 | 
					 | 
				
			||||||
			const height = 480;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse {
 | 
					 | 
				
			||||||
				return error.Unsupported;
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		errdefer {
 | 
					 | 
				
			||||||
			ext.SDL_DestroyWindow(window);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return .{
 | 
					 | 
				
			||||||
			.texture_formats = .{.allocator = coral.heap.allocator},
 | 
					 | 
				
			||||||
			.window = window,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn load_effect_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Effect {
 | 
					 | 
				
			||||||
		if (!std.mem.endsWith(u8, path, ".spv")) {
 | 
					 | 
				
			||||||
			return error.FormatUnsupported;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const fragment_file_stat = try storage.stat(path);
 | 
					 | 
				
			||||||
		const fragment_spirv_ops = try coral.heap.allocator.alloc(u32, fragment_file_stat.size / @alignOf(u32));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		defer {
 | 
					 | 
				
			||||||
			coral.heap.allocator.free(fragment_spirv_ops);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const bytes_read = try storage.read_all(path, std.mem.sliceAsBytes(fragment_spirv_ops), .{});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		std.debug.assert(bytes_read.len == fragment_file_stat.size);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Effect){};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		self.pending_work.enqueue(.{
 | 
					 | 
				
			||||||
			.load_effect = .{
 | 
					 | 
				
			||||||
				.desc = .{
 | 
					 | 
				
			||||||
					.fragment_spirv_ops = fragment_spirv_ops,
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				.loaded = &loaded,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return loaded.get().*;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn load_texture(self: *Assets, desc: Texture.Desc) std.mem.Allocator.Error!Texture {
 | 
					 | 
				
			||||||
		var loaded = coral.asyncio.Future(std.mem.Allocator.Error!Texture){};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		self.pending_work.enqueue(.{
 | 
					 | 
				
			||||||
			.load_texture = .{
 | 
					 | 
				
			||||||
				.desc = desc,
 | 
					 | 
				
			||||||
				.loaded = &loaded,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return loaded.get().*;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn load_texture_file(self: *Assets, storage: coral.files.Storage, path: []const u8) LoadFileError!Texture {
 | 
					 | 
				
			||||||
		var arena = std.heap.ArenaAllocator.init(coral.heap.allocator);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		defer {
 | 
					 | 
				
			||||||
			arena.deinit();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		for (self.texture_formats.values) |format| {
 | 
					 | 
				
			||||||
			if (!std.mem.endsWith(u8, path, format.extension)) {
 | 
					 | 
				
			||||||
				continue;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			return self.load_texture(try format.load_file(&arena, storage, path));
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return error.FormatUnsupported;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const thread_restriction = .main;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Color = @Vector(4, f32);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Commands = struct {
 | 
					 | 
				
			||||||
	pending: *List,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const Command = union (enum) {
 | 
					 | 
				
			||||||
		draw_texture: DrawTextureCommand,
 | 
					 | 
				
			||||||
		set_effect: SetEffectCommand,
 | 
					 | 
				
			||||||
		set_target: SetTargetCommand,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const DrawTextureCommand = struct {
 | 
					 | 
				
			||||||
		texture: Texture,
 | 
					 | 
				
			||||||
		transform: Transform2D,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const SetEffectCommand = struct {
 | 
					 | 
				
			||||||
		effect: Effect,
 | 
					 | 
				
			||||||
		properties: []const coral.io.Byte,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const SetTargetCommand = struct {
 | 
					 | 
				
			||||||
		texture: Texture,
 | 
					 | 
				
			||||||
		clear_color: ?Color,
 | 
					 | 
				
			||||||
		clear_depth: ?f32,
 | 
					 | 
				
			||||||
		clear_stencil: ?u8,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const List = struct {
 | 
					 | 
				
			||||||
		arena: std.heap.ArenaAllocator,
 | 
					 | 
				
			||||||
		stack: coral.stack.Sequential(Command),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn clear(self: *List) void {
 | 
					 | 
				
			||||||
			self.stack.clear();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (!self.arena.reset(.retain_capacity)) {
 | 
					 | 
				
			||||||
				std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{});
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn deinit(self: *List) void {
 | 
					 | 
				
			||||||
			self.arena.deinit();
 | 
					 | 
				
			||||||
			self.stack.deinit();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			self.* = undefined;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn init(allocator: std.mem.Allocator) List {
 | 
					 | 
				
			||||||
			return .{
 | 
					 | 
				
			||||||
				.arena = std.heap.ArenaAllocator.init(allocator),
 | 
					 | 
				
			||||||
				.stack = .{.allocator = allocator},
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const Param = struct {
 | 
					 | 
				
			||||||
		swap_lists: [2]List,
 | 
					 | 
				
			||||||
		swap_state: u1 = 0,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn deinit(self: *Param) void {
 | 
					 | 
				
			||||||
			for (&self.swap_lists) |*list| {
 | 
					 | 
				
			||||||
				list.deinit();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			self.* = undefined;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn pending_list(self: *Param) *List {
 | 
					 | 
				
			||||||
			return &self.swap_lists[self.swap_state];
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn rotate(self: *Param) void {
 | 
					 | 
				
			||||||
			const swapped_state = self.swap_state ^ 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			self.swap_lists[swapped_state].clear();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			self.swap_state = swapped_state;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn submitted_commands(self: Param) []const Command {
 | 
					 | 
				
			||||||
			return self.swap_lists[self.swap_state ^ 1].stack.values;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn bind(_: flow.system.BindContext) std.mem.Allocator.Error!Param {
 | 
					 | 
				
			||||||
		return .{
 | 
					 | 
				
			||||||
			.swap_lists = .{
 | 
					 | 
				
			||||||
				List.init(coral.heap.allocator),
 | 
					 | 
				
			||||||
				List.init(coral.heap.allocator),
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn init(param: *Param) Commands {
 | 
					 | 
				
			||||||
		return .{
 | 
					 | 
				
			||||||
			.pending = param.pending_list(),
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void {
 | 
					 | 
				
			||||||
		try self.pending.stack.push_grow(.{.draw_texture = command});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn set_effect(self: Commands, command: SetEffectCommand) std.mem.Allocator.Error!void {
 | 
					 | 
				
			||||||
		try self.pending.stack.push_grow(.{
 | 
					 | 
				
			||||||
			.set_effect = .{
 | 
					 | 
				
			||||||
				.properties = try self.pending.arena.allocator().dupe(coral.io.Byte, command.properties),
 | 
					 | 
				
			||||||
				.effect = command.effect,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn unbind(param: *Param, _: flow.system.UnbindContext) void {
 | 
					 | 
				
			||||||
		param.deinit();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void {
 | 
					 | 
				
			||||||
		try self.pending.stack.push_grow(.{.set_target = command});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Config = struct {
 | 
					 | 
				
			||||||
	width: u16 = 1280,
 | 
					 | 
				
			||||||
	height: u16 = 720,
 | 
					 | 
				
			||||||
	clear_color: Color = colors.black,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Input = union (enum) {
 | 
					 | 
				
			||||||
	key_up: Key,
 | 
					 | 
				
			||||||
	key_down: Key,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const Key = enum (u32) {
 | 
					 | 
				
			||||||
		a = ext.SDL_SCANCODE_A,
 | 
					 | 
				
			||||||
		d = ext.SDL_SCANCODE_D,
 | 
					 | 
				
			||||||
		s = ext.SDL_SCANCODE_S,
 | 
					 | 
				
			||||||
		w = ext.SDL_SCANCODE_W,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn scancode(key: Key) u32 {
 | 
					 | 
				
			||||||
			return @intFromEnum(key);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Effect = enum (u32) {
 | 
					 | 
				
			||||||
	default,
 | 
					 | 
				
			||||||
	_,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const Desc = struct {
 | 
					 | 
				
			||||||
		fragment_spirv_ops: []const u32,
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Texture = enum (u32) {
 | 
					 | 
				
			||||||
	default,
 | 
					 | 
				
			||||||
	backbuffer,
 | 
					 | 
				
			||||||
	_,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const Desc = struct {
 | 
					 | 
				
			||||||
		format: Format,
 | 
					 | 
				
			||||||
		access: Access,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub const Access = union (enum) {
 | 
					 | 
				
			||||||
			static: StaticAccess,
 | 
					 | 
				
			||||||
			render: RenderAccess,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub const StaticAccess = struct {
 | 
					 | 
				
			||||||
			width: u16,
 | 
					 | 
				
			||||||
			data: []const coral.io.Byte,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub const RenderAccess = struct {
 | 
					 | 
				
			||||||
			width: u16,
 | 
					 | 
				
			||||||
			height: u16,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	pub const Format = enum {
 | 
					 | 
				
			||||||
		rgba8,
 | 
					 | 
				
			||||||
		bgra8,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn byte_size(self: Format) usize {
 | 
					 | 
				
			||||||
			return switch (self) {
 | 
					 | 
				
			||||||
				.rgba8, .bgra8 => 4,
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Transform2D = extern struct {
 | 
					 | 
				
			||||||
	xbasis: Vector = .{1, 0},
 | 
					 | 
				
			||||||
	ybasis: Vector = .{0, 1},
 | 
					 | 
				
			||||||
	origin: Vector = @splat(0),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const Vector = @Vector(2, f32);
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn load_bmp_texture(arena: *std.heap.ArenaAllocator, storage: coral.files.Storage, path: []const u8) !Texture.Desc {
 | 
					 | 
				
			||||||
	const header = try storage.read_little(path, 0, extern struct {
 | 
					 | 
				
			||||||
		type: [2]u8 align (1),
 | 
					 | 
				
			||||||
		file_size: u32 align (1),
 | 
					 | 
				
			||||||
		reserved: [2]u16 align (1),
 | 
					 | 
				
			||||||
		image_offset: u32 align (1),
 | 
					 | 
				
			||||||
		header_size: u32 align (1),
 | 
					 | 
				
			||||||
		pixel_width: i32 align (1),
 | 
					 | 
				
			||||||
		pixel_height: i32 align (1),
 | 
					 | 
				
			||||||
		color_planes: u16 align (1),
 | 
					 | 
				
			||||||
		bits_per_pixel: u16 align (1),
 | 
					 | 
				
			||||||
		compression_method: u32 align (1),
 | 
					 | 
				
			||||||
		image_size: u32 align(1),
 | 
					 | 
				
			||||||
		pixels_per_meter_x: i32 align (1),
 | 
					 | 
				
			||||||
		pixels_per_meter_y: i32 align (1),
 | 
					 | 
				
			||||||
		palette_colors_used: u32 align (1),
 | 
					 | 
				
			||||||
		important_colors_used: u32 align (1),
 | 
					 | 
				
			||||||
	}) orelse {
 | 
					 | 
				
			||||||
		return error.FormatUnsupported;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (!std.mem.eql(u8, &header.type, "BM")) {
 | 
					 | 
				
			||||||
		return error.FormatUnsupported;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const pixel_width = std.math.cast(u16, header.pixel_width) orelse {
 | 
					 | 
				
			||||||
		return error.FormatUnsupported;
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const pixels = try arena.allocator().alloc(coral.io.Byte, header.image_size);
 | 
					 | 
				
			||||||
	const bytes_per_pixel = header.bits_per_pixel / coral.io.bits_per_byte;
 | 
					 | 
				
			||||||
	const alignment = 4;
 | 
					 | 
				
			||||||
	const byte_stride = pixel_width * bytes_per_pixel;
 | 
					 | 
				
			||||||
	const padded_byte_stride = alignment * @divTrunc((byte_stride + alignment - 1), alignment);
 | 
					 | 
				
			||||||
	const byte_padding = coral.scalars.sub(padded_byte_stride, byte_stride) orelse 0;
 | 
					 | 
				
			||||||
	var buffer_offset: usize = 0;
 | 
					 | 
				
			||||||
	var file_offset = @as(usize, header.image_offset);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	switch (header.bits_per_pixel) {
 | 
					 | 
				
			||||||
		32 => {
 | 
					 | 
				
			||||||
			while (buffer_offset < pixels.len) {
 | 
					 | 
				
			||||||
				const line = pixels[buffer_offset .. buffer_offset + byte_stride];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (try storage.read(path, line, file_offset) != byte_stride) {
 | 
					 | 
				
			||||||
					return error.FormatUnsupported;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				for (0 .. pixel_width) |i| {
 | 
					 | 
				
			||||||
					const line_offset = i * 4;
 | 
					 | 
				
			||||||
					const pixel = line[line_offset .. line_offset + 4];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					std.mem.swap(u8, &pixel[0], &pixel[2]);
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				file_offset += line.len + byte_padding;
 | 
					 | 
				
			||||||
				buffer_offset += padded_byte_stride;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		else => return error.FormatUnsupported,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return .{
 | 
					 | 
				
			||||||
		.format = .rgba8,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		.access = .{
 | 
					 | 
				
			||||||
			.static = .{
 | 
					 | 
				
			||||||
				.width = pixel_width,
 | 
					 | 
				
			||||||
				.data = pixels,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn poll(app: flow.Write(ona.App), inputs: ona.msg.Send(Input)) !void {
 | 
					 | 
				
			||||||
	var event = @as(ext.SDL_Event, undefined);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	while (ext.SDL_PollEvent(&event) != 0) {
 | 
					 | 
				
			||||||
		switch (event.type) {
 | 
					 | 
				
			||||||
			ext.SDL_QUIT => app.res.quit(),
 | 
					 | 
				
			||||||
			ext.SDL_KEYUP => try inputs.push(.{.key_up = @enumFromInt(event.key.keysym.scancode)}),
 | 
					 | 
				
			||||||
			ext.SDL_KEYDOWN => try inputs.push(.{.key_down = @enumFromInt(event.key.keysym.scancode)}),
 | 
					 | 
				
			||||||
			else => {},
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn setup(world: *flow.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void {
 | 
					 | 
				
			||||||
	if (ext.SDL_Init(ext.SDL_INIT_VIDEO) != 0) {
 | 
					 | 
				
			||||||
		return error.Unsupported;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const assets = create: {
 | 
					 | 
				
			||||||
		var assets = try Assets.init();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		errdefer {
 | 
					 | 
				
			||||||
			assets.deinit();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		break: create try world.set_get_state(assets);
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	assets.frame_rendered.set();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	errdefer {
 | 
					 | 
				
			||||||
		assets.deinit();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	assets.has_worker_thread = try std.Thread.spawn(.{}, rendering.process_work, .{
 | 
					 | 
				
			||||||
		&assets.pending_work,
 | 
					 | 
				
			||||||
		assets.window,
 | 
					 | 
				
			||||||
	});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	const builtin_texture_formats = [_]Assets.TextureFormat{
 | 
					 | 
				
			||||||
		.{
 | 
					 | 
				
			||||||
			.extension = "bmp",
 | 
					 | 
				
			||||||
			.load_file = load_bmp_texture,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	for (builtin_texture_formats) |format| {
 | 
					 | 
				
			||||||
		try assets.texture_formats.push_grow(format);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	try world.set_state(Config{});
 | 
					 | 
				
			||||||
	try world.on_event(events.pre_update, flow.system_fn(poll), .{.label = "poll gfx"});
 | 
					 | 
				
			||||||
	try world.on_event(events.exit, flow.system_fn(stop), .{.label = "stop gfx"});
 | 
					 | 
				
			||||||
	try world.on_event(events.finish, flow.system_fn(synchronize), .{.label = "synchronize gfx"});
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn stop(assets: flow.Write(Assets)) void {
 | 
					 | 
				
			||||||
	assets.res.deinit();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn synchronize(exclusive: flow.Exclusive) !void {
 | 
					 | 
				
			||||||
	const assets = exclusive.world.get_state(Assets).?;
 | 
					 | 
				
			||||||
	const config = exclusive.world.get_state(Config).?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	assets.frame_rendered.wait();
 | 
					 | 
				
			||||||
	assets.frame_rendered.reset();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		var has_command_param = exclusive.world.get_params(Commands).has_head;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		while (has_command_param) |command_param| : (has_command_param = command_param.has_next) {
 | 
					 | 
				
			||||||
			command_param.param.rotate();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	var display_width, var display_height = [_]c_int{0, 0};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	ext.SDL_GL_GetDrawableSize(assets.window, &display_width, &display_height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (config.width != display_width or config.height != display_height) {
 | 
					 | 
				
			||||||
		ext.SDL_SetWindowSize(assets.window, config.width, config.height);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if (exclusive.world.get_params(Commands).has_head) |command_param| {
 | 
					 | 
				
			||||||
		assets.pending_work.enqueue(.{
 | 
					 | 
				
			||||||
			.render_frame = .{
 | 
					 | 
				
			||||||
				.has_command_params = command_param,
 | 
					 | 
				
			||||||
				.width = config.width,
 | 
					 | 
				
			||||||
				.height = config.height,
 | 
					 | 
				
			||||||
				.clear_color = config.clear_color,
 | 
					 | 
				
			||||||
				.finished = &assets.frame_rendered,
 | 
					 | 
				
			||||||
			},
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		assets.frame_rendered.set();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,13 +0,0 @@
 | 
				
			|||||||
const gfx = @import("../gfx.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const black = greyscale(0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const white = greyscale(1);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn greyscale(v: f32) gfx.Color {
 | 
					 | 
				
			||||||
	return .{v, v, v, 1};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn rgb(r: f32, g: f32, b: f32) gfx.Color {
 | 
					 | 
				
			||||||
	return .{r, g, b, 1};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,13 +1,9 @@
 | 
				
			|||||||
const builtin = @import("builtin");
 | 
					const builtin = @import("builtin");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const coral = @import("./coral.zig");
 | 
					const ona = @import("./ona.zig");
 | 
				
			||||||
 | 
					 | 
				
			||||||
const slices = @import("./slices.zig");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Byte = u8;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Error = error {
 | 
					pub const Error = error {
 | 
				
			||||||
	UnavailableResource,
 | 
						UnavailableResource,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@ -111,7 +107,7 @@ pub fn Generator(comptime Output: type, comptime input_types: []const type) type
 | 
				
			|||||||
pub const NullWritable = struct {
 | 
					pub const NullWritable = struct {
 | 
				
			||||||
	written: usize = 0,
 | 
						written: usize = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn write(self: *NullWritable, buffer: []const Byte) Error!usize {
 | 
						pub fn write(self: *NullWritable, buffer: []const u8) Error!usize {
 | 
				
			||||||
		self.written += buffer.len;
 | 
							self.written += buffer.len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return buffer.len;
 | 
							return buffer.len;
 | 
				
			||||||
@ -122,12 +118,12 @@ pub const NullWritable = struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Reader = Generator(Error!usize, &.{[]Byte});
 | 
					pub const Reader = Generator(Error!usize, &.{[]u8});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Writer = Generator(Error!usize, &.{[]const Byte});
 | 
					pub const Writer = Generator(Error!usize, &.{[]const u8});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.Byte {
 | 
					pub fn alloc_read(input: ona.io.Reader, allocator: std.mem.Allocator) []u8 {
 | 
				
			||||||
	const buffer = coral.Stack(coral.Byte){.allocator = allocator};
 | 
						const buffer = ona.stack.Sequential(u8){.allocator = allocator};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	errdefer buffer.deinit();
 | 
						errdefer buffer.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -139,7 +135,7 @@ pub fn alloc_read(input: coral.io.Reader, allocator: std.mem.Allocator) []coral.
 | 
				
			|||||||
pub const bits_per_byte = 8;
 | 
					pub const bits_per_byte = 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn skip_n(input: Reader, distance: u64) Error!void {
 | 
					pub fn skip_n(input: Reader, distance: u64) Error!void {
 | 
				
			||||||
	var buffer = @as([512]coral.Byte, undefined);
 | 
						var buffer = @as([512]u8, undefined);
 | 
				
			||||||
	var remaining = distance;
 | 
						var remaining = distance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	while (remaining != 0) {
 | 
						while (remaining != 0) {
 | 
				
			||||||
@ -154,7 +150,7 @@ pub fn skip_n(input: Reader, distance: u64) Error!void {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn stream_all(input: Reader, output: Writer) Error!usize {
 | 
					pub fn stream_all(input: Reader, output: Writer) Error!usize {
 | 
				
			||||||
	var buffer = @as([512]coral.Byte, undefined);
 | 
						var buffer = @as([512]u8, undefined);
 | 
				
			||||||
	var copied = @as(usize, 0);
 | 
						var copied = @as(usize, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	while (true) {
 | 
						while (true) {
 | 
				
			||||||
@ -173,7 +169,7 @@ pub fn stream_all(input: Reader, output: Writer) Error!usize {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize {
 | 
					pub fn stream_n(input: Reader, output: Writer, limit: usize) Error!usize {
 | 
				
			||||||
	var buffer = @as([512]coral.Byte, undefined);
 | 
						var buffer = @as([512]u8, undefined);
 | 
				
			||||||
	var remaining = limit;
 | 
						var remaining = limit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	while (true) {
 | 
						while (true) {
 | 
				
			||||||
@ -1,18 +1,5 @@
 | 
				
			|||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn Matrix(comptime n: usize, comptime Element: type) type {
 | 
					 | 
				
			||||||
	return [n]@Vector(n, Element);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const ProjectionMatrix = Matrix(4, f32);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Rect = struct {
 | 
					 | 
				
			||||||
	left: f32,
 | 
					 | 
				
			||||||
	top: f32,
 | 
					 | 
				
			||||||
	right: f32,
 | 
					 | 
				
			||||||
	bottom: f32,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn cross(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child {
 | 
					pub fn cross(v1: anytype, v2: anytype) @typeInfo(@TypeOf(v1, v2)).Vector.child {
 | 
				
			||||||
	const multipled = v1 * v2;
 | 
						const multipled = v1 * v2;
 | 
				
			||||||
	const vector_info = @typeInfo(@TypeOf(v1)).Vector;
 | 
						const vector_info = @typeInfo(@TypeOf(v1)).Vector;
 | 
				
			||||||
@ -62,15 +49,3 @@ pub fn normal(v: anytype) @TypeOf(v) {
 | 
				
			|||||||
	return v;
 | 
						return v;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn orthographic_projection(near: f32, far: f32, viewport: Rect) Matrix(4, f32) {
 | 
					 | 
				
			||||||
	const width = viewport.right - viewport.left;
 | 
					 | 
				
			||||||
	const height = viewport.bottom - viewport.top;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return .{
 | 
					 | 
				
			||||||
		.{2 / width, 0, 0, 0},
 | 
					 | 
				
			||||||
		.{0, 2 / height, 0, 0},
 | 
					 | 
				
			||||||
		.{0, 0, 1 / (far - near), 0},
 | 
					 | 
				
			||||||
		.{-((viewport.left + viewport.right) / width), -((viewport.top + viewport.bottom) / height), near / (near - far), 1},
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@ -1,8 +1,4 @@
 | 
				
			|||||||
const coral = @import("./coral.zig");
 | 
					const ona = @import("./ona.zig");
 | 
				
			||||||
 | 
					 | 
				
			||||||
const hashes = @import("./hashes.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const io = @import("./io.zig");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -250,13 +246,17 @@ pub fn enum_traits(comptime Enum: type) Traits(Enum) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
pub const string_traits = init: {
 | 
					pub const string_traits = init: {
 | 
				
			||||||
	const strings = struct {
 | 
						const strings = struct {
 | 
				
			||||||
 | 
							fn are_equal(a: []const u8, b: []const u8) bool {
 | 
				
			||||||
 | 
								return std.mem.eql(u8, a, b);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn hash(value: []const u8) usize {
 | 
							fn hash(value: []const u8) usize {
 | 
				
			||||||
			return hashes.djb2(@typeInfo(usize).Int, value);
 | 
								return ona.hashes.djb2(@typeInfo(usize).Int, value);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	break: init Traits([]const u8){
 | 
						break: init Traits([]const u8){
 | 
				
			||||||
		.are_equal = coral.io.are_equal,
 | 
							.are_equal = strings.are_equal,
 | 
				
			||||||
		.hash = strings.hash,
 | 
							.hash = strings.hash,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
							
								
								
									
										134
									
								
								src/ona/msg.zig
									
									
									
									
									
								
							
							
						
						
									
										134
									
								
								src/ona/msg.zig
									
									
									
									
									
								
							@ -1,134 +0,0 @@
 | 
				
			|||||||
const coral = @import("coral");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const flow = @import("flow");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const ona = @import("./ona.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std = @import("std");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn Channel(comptime Message: type) type {
 | 
					 | 
				
			||||||
	return struct {
 | 
					 | 
				
			||||||
		buffers: [2]coral.stack.Sequential(Message),
 | 
					 | 
				
			||||||
		swap_index: u1 = 0,
 | 
					 | 
				
			||||||
		ticks: u1 = 0,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const Self = @This();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn cleanup(channel: flow.Write(Self)) void {
 | 
					 | 
				
			||||||
			channel.res.deinit();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn deinit(self: *Self) void {
 | 
					 | 
				
			||||||
			for (&self.buffers) |*buffer| {
 | 
					 | 
				
			||||||
				buffer.deinit();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			self.* = undefined;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn swap(channel: flow.Write(Self)) void {
 | 
					 | 
				
			||||||
			channel.res.ticks = coral.scalars.add(channel.res.ticks, 1) orelse 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			if (channel.res.ticks == 0) {
 | 
					 | 
				
			||||||
				channel.res.swap_index ^= 1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
				channel.res.buffers[channel.res.swap_index].clear();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn init(allocator: std.mem.Allocator) Self {
 | 
					 | 
				
			||||||
			return .{
 | 
					 | 
				
			||||||
				.buffers = .{
 | 
					 | 
				
			||||||
					.{.allocator = allocator},
 | 
					 | 
				
			||||||
					.{.allocator = allocator},
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn messages(self: Self) []const Message {
 | 
					 | 
				
			||||||
			return self.buffers[self.swap_index ^ 1].values;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
 | 
					 | 
				
			||||||
			try self.buffers[self.swap_index].push_grow(message);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn Receive(comptime Message: type) type {
 | 
					 | 
				
			||||||
	const TypedChannel = Channel(Message);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return struct {
 | 
					 | 
				
			||||||
		channel: *const TypedChannel,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const Self = @This();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub const Param = struct {
 | 
					 | 
				
			||||||
			channel: *const TypedChannel,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param {
 | 
					 | 
				
			||||||
			return .{
 | 
					 | 
				
			||||||
				.channel = (try context.register_readable_state_access(TypedChannel)) orelse set: {
 | 
					 | 
				
			||||||
					try context.world.set_state(TypedChannel.init(coral.heap.allocator));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					break: set (try context.register_readable_state_access(TypedChannel)).?;
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn init(param: *Param) Self {
 | 
					 | 
				
			||||||
			return .{
 | 
					 | 
				
			||||||
				.channel = param.channel,
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn messages(self: Self) []const Message {
 | 
					 | 
				
			||||||
			return self.channel.messages();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn Send(comptime Message: type) type {
 | 
					 | 
				
			||||||
	const TypedChannel = Channel(Message);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return struct {
 | 
					 | 
				
			||||||
		channel: *TypedChannel,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const Self = @This();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub const Param = struct {
 | 
					 | 
				
			||||||
			channel: *TypedChannel,
 | 
					 | 
				
			||||||
		};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn bind(context: flow.system.BindContext) std.mem.Allocator.Error!Param {
 | 
					 | 
				
			||||||
			return .{
 | 
					 | 
				
			||||||
				.channel = (try context.register_writable_state_access(TypedChannel)) orelse set: {
 | 
					 | 
				
			||||||
					try context.world.set_state(TypedChannel.init(coral.heap.allocator));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					const app = context.world.get_state(ona.App).?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					try context.world.on_event(app.events.post_update, flow.system_fn(TypedChannel.swap), .{
 | 
					 | 
				
			||||||
						.label = "swap channel of " ++ @typeName(Message),
 | 
					 | 
				
			||||||
					});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					try context.world.on_event(app.events.exit, flow.system_fn(TypedChannel.cleanup), .{
 | 
					 | 
				
			||||||
						.label = "clean up channel of " ++ @typeName(Message),
 | 
					 | 
				
			||||||
					});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
					break: set (try context.register_writable_state_access(TypedChannel)).?;
 | 
					 | 
				
			||||||
				},
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn init(param: *Param) Self {
 | 
					 | 
				
			||||||
			return .{
 | 
					 | 
				
			||||||
				.channel = param.channel,
 | 
					 | 
				
			||||||
			};
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		pub fn push(self: Self, message: Message) std.mem.Allocator.Error!void {
 | 
					 | 
				
			||||||
			try self.channel.push(message);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	};
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										777
									
								
								src/ona/ona.zig
									
									
									
									
									
								
							
							
						
						
									
										777
									
								
								src/ona/ona.zig
									
									
									
									
									
								
							@ -1,17 +1,33 @@
 | 
				
			|||||||
pub const act = @import("./act.zig");
 | 
					pub const ascii = @import("./ascii.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const coral = @import("coral");
 | 
					pub const asyncio = @import("./asyncio.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ext = @import("./ext.zig");
 | 
					pub const dag = @import("./dag.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const flow = @import("flow");
 | 
					pub const hashes = @import("./hashes.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const gfx = @import("./gfx.zig");
 | 
					pub const heap = @import("./heap.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const msg = @import("./msg.zig");
 | 
					pub const files = @import("./files.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const io = @import("./io.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const map = @import("./map.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const scalars = @import("./scalars.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const slices = @import("./slices.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const stack = @import("./stack.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const utf8 = @import("./utf8.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const World = @import("./World.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const lina = @import("./lina.zig");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const App = struct {
 | 
					pub const App = struct {
 | 
				
			||||||
	events: *const Events,
 | 
						events: *const Events,
 | 
				
			||||||
	target_frame_time: f64,
 | 
						target_frame_time: f64,
 | 
				
			||||||
@ -19,13 +35,13 @@ pub const App = struct {
 | 
				
			|||||||
	is_running: bool,
 | 
						is_running: bool,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub const Events = struct {
 | 
						pub const Events = struct {
 | 
				
			||||||
		load: flow.World.Event,
 | 
							load: World.Event,
 | 
				
			||||||
		pre_update: flow.World.Event,
 | 
							pre_update: World.Event,
 | 
				
			||||||
		update: flow.World.Event,
 | 
							update: World.Event,
 | 
				
			||||||
		post_update: flow.World.Event,
 | 
							post_update: World.Event,
 | 
				
			||||||
		render: flow.World.Event,
 | 
							render: World.Event,
 | 
				
			||||||
		finish: flow.World.Event,
 | 
							finish: World.Event,
 | 
				
			||||||
		exit: flow.World.Event,
 | 
							exit: World.Event,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub fn quit(self: *App) void {
 | 
						pub fn quit(self: *App) void {
 | 
				
			||||||
@ -33,18 +49,151 @@ pub const App = struct {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Flag = enum {
 | 
					fn Channel(comptime Message: type) type {
 | 
				
			||||||
 | 
						return struct {
 | 
				
			||||||
 | 
							buffers: [2]stack.Sequential(Message),
 | 
				
			||||||
 | 
							swap_index: u1 = 0,
 | 
				
			||||||
 | 
							ticks: u1 = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn cleanup(channel: Write(Self)) void {
 | 
				
			||||||
 | 
								channel.state.deinit();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
								for (&self.buffers) |*buffer| {
 | 
				
			||||||
 | 
									buffer.deinit();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.* = undefined;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn swap(channel: Write(Self)) void {
 | 
				
			||||||
 | 
								channel.state.ticks = scalars.add(channel.state.ticks, 1) orelse 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (channel.state.ticks == 0) {
 | 
				
			||||||
 | 
									channel.state.swap_index ^= 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									channel.state.buffers[channel.state.swap_index].clear();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn init(allocator: std.mem.Allocator) Self {
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.buffers = .{
 | 
				
			||||||
 | 
										.{.allocator = allocator},
 | 
				
			||||||
 | 
										.{.allocator = allocator},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn messages(self: Self) []const Message {
 | 
				
			||||||
 | 
								return self.buffers[self.swap_index ^ 1].values;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn push(self: *Self, message: Message) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
								try self.buffers[self.swap_index].push_grow(message);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Exclusive(comptime values: []const type) type {
 | 
				
			||||||
 | 
						comptime var qualifieds: [values.len]type = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (&qualifieds, values) |*qualified, Value| {
 | 
				
			||||||
 | 
							qualified.* = switch (@typeInfo(Value)) {
 | 
				
			||||||
 | 
								.Optional => @Type(.{
 | 
				
			||||||
 | 
									.Optional = .{
 | 
				
			||||||
 | 
										.child = .{
 | 
				
			||||||
 | 
											.Pointer = .{
 | 
				
			||||||
 | 
												.is_allowzero = false,
 | 
				
			||||||
 | 
												.sentinel = null,
 | 
				
			||||||
 | 
												.address_space = .generic,
 | 
				
			||||||
 | 
												.is_volatile = false,
 | 
				
			||||||
 | 
												.alignment = @alignOf(Value),
 | 
				
			||||||
 | 
												.size = .One,
 | 
				
			||||||
 | 
												.child = Value,
 | 
				
			||||||
 | 
												.is_const = false,
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								else => @Type(.{
 | 
				
			||||||
 | 
									.Pointer = .{
 | 
				
			||||||
 | 
										.is_allowzero = false,
 | 
				
			||||||
 | 
										.sentinel = null,
 | 
				
			||||||
 | 
										.address_space = .generic,
 | 
				
			||||||
 | 
										.is_volatile = false,
 | 
				
			||||||
 | 
										.alignment = @alignOf(Value),
 | 
				
			||||||
 | 
										.size = .One,
 | 
				
			||||||
 | 
										.child = Value,
 | 
				
			||||||
 | 
										.is_const = false,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const States = std.meta.Tuple(&qualifieds);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return struct {
 | 
				
			||||||
 | 
							states: States,
 | 
				
			||||||
 | 
							world: *World,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const Param = struct {
 | 
				
			||||||
 | 
								states: States,
 | 
				
			||||||
 | 
								world: *World,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
 | 
				
			||||||
 | 
								var param = Param{
 | 
				
			||||||
 | 
									.world = context.world,
 | 
				
			||||||
 | 
									.states = undefined,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								inline for (¶m.states, values) |*state, Value| {
 | 
				
			||||||
 | 
									const has_state = try context.register_writable_state_access(Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									state.* = switch (@typeInfo(Value)) {
 | 
				
			||||||
 | 
										.Optional => has_state,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										else => has_state orelse {
 | 
				
			||||||
 | 
											@panic(std.fmt.comptimePrint("attempt to use exclusive {s} that has not yet been set", .{
 | 
				
			||||||
 | 
												@typeName(Value),
 | 
				
			||||||
 | 
											}));
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return param;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn init(param: *Param) Self {
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.states = param.states,
 | 
				
			||||||
 | 
									.world = param.world,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const thread_restriction = .main;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const LaunchFlag = enum {
 | 
				
			||||||
	dump_shader_translations,
 | 
						dump_shader_translations,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	var launch_args = [_]?[]const u8{null} ** std.enums.values(Flag).len;
 | 
						var args = [_]?[]const u8{null} ** std.enums.values(LaunchFlag).len;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	const values = std.enums.values(Flag);
 | 
						const values = std.enums.values(LaunchFlag);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Options = struct {
 | 
					pub const Options = struct {
 | 
				
			||||||
	tick_rate: u64,
 | 
						tick_rate: u64,
 | 
				
			||||||
	execution: Execution,
 | 
						execution: Execution,
 | 
				
			||||||
	middlewares: []const *const Setup = default_middlewares,
 | 
						middleware: []const *const Setup,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pub const Execution = union (enum) {
 | 
						pub const Execution = union (enum) {
 | 
				
			||||||
		single_threaded,
 | 
							single_threaded,
 | 
				
			||||||
@ -52,29 +201,498 @@ pub const Options = struct {
 | 
				
			|||||||
	};
 | 
						};
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const Read = flow.Read;
 | 
					pub fn Params(comptime Value: type) type {
 | 
				
			||||||
 | 
						if (!@hasDecl(Value, "Param")) {
 | 
				
			||||||
pub const Setup = fn (*flow.World, App.Events) anyerror!void;
 | 
							@compileError("System parameters must have a Params type declaration");
 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const World = flow.World;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const Write = flow.Write;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub const default_middlewares = &.{
 | 
					 | 
				
			||||||
	gfx.setup,
 | 
					 | 
				
			||||||
	act.setup,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub fn launch_arg(flag: Flag) ?[]const u8 {
 | 
					 | 
				
			||||||
	return Flag.launch_args[@intFromEnum(flag)];
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
 | 
						return struct {
 | 
				
			||||||
 | 
							has_head: ?*Node = null,
 | 
				
			||||||
 | 
							has_tail: ?*Node = null,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const Node = struct {
 | 
				
			||||||
 | 
								param: Value.Param,
 | 
				
			||||||
 | 
								has_prev: ?*Node = null,
 | 
				
			||||||
 | 
								has_next: ?*Node = null,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Pool(comptime Value: type) type {
 | 
				
			||||||
 | 
						return struct {
 | 
				
			||||||
 | 
							entries: stack.Sequential(Entry),
 | 
				
			||||||
 | 
							first_free_index: usize = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const Entry = union (enum) {
 | 
				
			||||||
 | 
								free_index: usize,
 | 
				
			||||||
 | 
								occupied: Value,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const Values = struct {
 | 
				
			||||||
 | 
								cursor: usize = 0,
 | 
				
			||||||
 | 
								pool: *const Self,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								pub fn next(self: *Values) ?*Value {
 | 
				
			||||||
 | 
									while (self.cursor < self.pool.entries.len()) {
 | 
				
			||||||
 | 
										defer self.cursor += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										switch (self.pool.entries.values[self.cursor]) {
 | 
				
			||||||
 | 
											.free_index => {
 | 
				
			||||||
 | 
												continue;
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											.occupied => |*occupied| {
 | 
				
			||||||
 | 
												return occupied;
 | 
				
			||||||
 | 
											},
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn deinit(self: *Self) void {
 | 
				
			||||||
 | 
								self.entries.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.* = undefined;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn get(self: *Self, key: usize) ?*Value {
 | 
				
			||||||
 | 
								if (key >= self.entries.len()) {
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return switch (self.entries.values[key]) {
 | 
				
			||||||
 | 
									.free_index => null,
 | 
				
			||||||
 | 
									.occupied => |*occupied| occupied,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn init(allocator: std.mem.Allocator) Self {
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.entries = .{.allocator = allocator},
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn insert(self: *Self, value: Value) std.mem.Allocator.Error!usize {
 | 
				
			||||||
 | 
								const entries_count = self.entries.len();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (self.first_free_index == entries_count) {
 | 
				
			||||||
 | 
									try self.entries.push_grow(.{.occupied = value});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									self.first_free_index += 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									return entries_count;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const insersion_index = self.first_free_index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								self.first_free_index = self.entries.values[self.first_free_index].free_index;
 | 
				
			||||||
 | 
								self.entries.values[insersion_index] = .{.occupied = value};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return insersion_index;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn remove(self: *Self, key: usize) ?Value {
 | 
				
			||||||
 | 
								if (key >= self.entries.len()) {
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								switch (self.entries.values[key]) {
 | 
				
			||||||
 | 
									.free_index => {
 | 
				
			||||||
 | 
										return null;
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									.occupied => |occupied| {
 | 
				
			||||||
 | 
										self.entries.values[key] = .{.free_index = self.first_free_index};
 | 
				
			||||||
 | 
										self.first_free_index = key;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										return occupied;
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn values(self: *const Self) Values {
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.pool = self,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test "pooling" {
 | 
				
			||||||
 | 
						var pool = Pool(i32).init(std.testing.allocator);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						defer pool.deinit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						try std.testing.expectEqual(0, pool.first_free_index);
 | 
				
			||||||
 | 
						try std.testing.expectEqual(null, pool.get(0));
 | 
				
			||||||
 | 
						try std.testing.expectEqual(null, pool.remove(0));
 | 
				
			||||||
 | 
						try std.testing.expectEqual(0, try pool.insert(69));
 | 
				
			||||||
 | 
						try std.testing.expectEqual(1, pool.first_free_index);
 | 
				
			||||||
 | 
						try std.testing.expectEqual(1, try pool.insert(420));
 | 
				
			||||||
 | 
						try std.testing.expectEqual(69, pool.get(0).?.*);
 | 
				
			||||||
 | 
						try std.testing.expectEqual(420, pool.get(1).?.*);
 | 
				
			||||||
 | 
						try std.testing.expectEqual(69, pool.remove(0).?);
 | 
				
			||||||
 | 
						try std.testing.expectEqual(0, pool.first_free_index);
 | 
				
			||||||
 | 
						try std.testing.expectEqual(null, pool.get(0));
 | 
				
			||||||
 | 
						try std.testing.expectEqual(null, pool.remove(0));
 | 
				
			||||||
 | 
						try std.testing.expectEqual(0, try pool.insert(36));
 | 
				
			||||||
 | 
						try std.testing.expectEqual(36, pool.get(0).?.*);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Read(comptime Value: type) type {
 | 
				
			||||||
 | 
						return Shared(Value, .{
 | 
				
			||||||
 | 
							.thread_restriction = thread_restriction(Value),
 | 
				
			||||||
 | 
							.read_only = true,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Receive(comptime Message: type) type {
 | 
				
			||||||
 | 
						const TypedChannel = Channel(Message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return struct {
 | 
				
			||||||
 | 
							channel: *const TypedChannel,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const Param = struct {
 | 
				
			||||||
 | 
								channel: *const TypedChannel,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.channel = (try context.register_readable_state_access(TypedChannel)) orelse set: {
 | 
				
			||||||
 | 
										try context.world.set_state(TypedChannel.init(heap.allocator));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										break: set (try context.register_readable_state_access(TypedChannel)).?;
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn init(param: *Param) Self {
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.channel = param.channel,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn messages(self: Self) []const Message {
 | 
				
			||||||
 | 
								return self.channel.messages();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Send(comptime Message: type) type {
 | 
				
			||||||
 | 
						const TypedChannel = Channel(Message);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return struct {
 | 
				
			||||||
 | 
							channel: *TypedChannel,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const Param = struct {
 | 
				
			||||||
 | 
								channel: *TypedChannel,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.channel = (try context.register_writable_state_access(TypedChannel)) orelse set: {
 | 
				
			||||||
 | 
										try context.world.set_state(TypedChannel.init(heap.allocator));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										const app = context.world.get_state(App) orelse {
 | 
				
			||||||
 | 
											@panic("Send system parameters depend on a " ++ @typeName(App) ++ " state to work");
 | 
				
			||||||
 | 
										};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										try context.world.on_event(app.events.post_update, system_fn(TypedChannel.swap), .{
 | 
				
			||||||
 | 
											.label = "swap channel of " ++ @typeName(Message),
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										try context.world.on_event(app.events.exit, system_fn(TypedChannel.cleanup), .{
 | 
				
			||||||
 | 
											.label = "clean up channel of " ++ @typeName(Message),
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										break: set (try context.register_writable_state_access(TypedChannel)).?;
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn init(param: *Param) Self {
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.channel = param.channel,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn push(self: Self, message: Message) std.mem.Allocator.Error!void {
 | 
				
			||||||
 | 
								try self.channel.push(message);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const Setup = fn (*World, App.Events) anyerror!void;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const ShareInfo = struct {
 | 
				
			||||||
 | 
						thread_restriction: ThreadRestriction,
 | 
				
			||||||
 | 
						read_only: bool,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Shared(comptime Value: type, comptime info: ShareInfo) type {
 | 
				
			||||||
 | 
						const value_info = @typeInfo(Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const Qualified = switch (value_info) {
 | 
				
			||||||
 | 
							.Optional => @Type(.{
 | 
				
			||||||
 | 
								.Optional = .{
 | 
				
			||||||
 | 
									.child = .{
 | 
				
			||||||
 | 
										.Pointer = .{
 | 
				
			||||||
 | 
											.is_allowzero = false,
 | 
				
			||||||
 | 
											.sentinel = null,
 | 
				
			||||||
 | 
											.address_space = .generic,
 | 
				
			||||||
 | 
											.is_volatile = false,
 | 
				
			||||||
 | 
											.alignment = @alignOf(Value),
 | 
				
			||||||
 | 
											.size = .One,
 | 
				
			||||||
 | 
											.child = Value,
 | 
				
			||||||
 | 
											.is_const = info.read_only,
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							else => @Type(.{
 | 
				
			||||||
 | 
								.Pointer = .{
 | 
				
			||||||
 | 
									.is_allowzero = false,
 | 
				
			||||||
 | 
									.sentinel = null,
 | 
				
			||||||
 | 
									.address_space = .generic,
 | 
				
			||||||
 | 
									.is_volatile = false,
 | 
				
			||||||
 | 
									.alignment = @alignOf(Value),
 | 
				
			||||||
 | 
									.size = .One,
 | 
				
			||||||
 | 
									.child = Value,
 | 
				
			||||||
 | 
									.is_const = info.read_only,
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							}),
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return struct {
 | 
				
			||||||
 | 
							state: Qualified,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const Param = struct {
 | 
				
			||||||
 | 
								state: Qualified,
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn bind(context: World.BindContext) std.mem.Allocator.Error!Param {
 | 
				
			||||||
 | 
								const thread_restriction_name = switch (info.thread_restriction) {
 | 
				
			||||||
 | 
									.main => "main thread-restricted ",
 | 
				
			||||||
 | 
									.none => ""
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const state = switch (info.read_only) {
 | 
				
			||||||
 | 
									true => (try context.register_readable_state_access(Value)),
 | 
				
			||||||
 | 
									false => (try context.register_writable_state_access(Value)),
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.state = switch (value_info) {
 | 
				
			||||||
 | 
										.Optional => state,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										else => state orelse {
 | 
				
			||||||
 | 
											@panic(std.fmt.comptimePrint("attempt to use {s}{s} {s} that has not yet been set", .{
 | 
				
			||||||
 | 
												thread_restriction_name,
 | 
				
			||||||
 | 
												if (info.read_only) "read-only" else "read-write",
 | 
				
			||||||
 | 
												@typeName(Value),
 | 
				
			||||||
 | 
											}));
 | 
				
			||||||
 | 
										},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub fn init(param: *Param) Self {
 | 
				
			||||||
 | 
								return .{
 | 
				
			||||||
 | 
									.state = param.state,
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							pub const thread_restriction = info.thread_restriction;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const SystemInfo = struct {
 | 
				
			||||||
 | 
						execute: *const fn ([]const *const Parameter, *const [max_parameters]*anyopaque) anyerror!void,
 | 
				
			||||||
 | 
						parameters: [max_parameters]*const Parameter = undefined,
 | 
				
			||||||
 | 
						parameter_count: u4 = 0,
 | 
				
			||||||
 | 
						thread_restriction: ThreadRestriction = .none,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const Parameter = struct {
 | 
				
			||||||
 | 
							thread_restriction: ThreadRestriction,
 | 
				
			||||||
 | 
							init: *const fn (*anyopaque, *anyopaque) void,
 | 
				
			||||||
 | 
							bind: *const fn (std.mem.Allocator, World.BindContext) std.mem.Allocator.Error!*anyopaque,
 | 
				
			||||||
 | 
							unbind: *const fn (std.mem.Allocator, *anyopaque, World.UnbindContext) void,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub const max_parameters = 16;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pub fn used_parameters(self: *const SystemInfo) []const *const Parameter {
 | 
				
			||||||
 | 
							return self.parameters[0 .. self.parameter_count];
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const SystemOrder = struct {
 | 
				
			||||||
 | 
						label: []const u8 = "",
 | 
				
			||||||
 | 
						run_after: []const *const SystemInfo = &.{},
 | 
				
			||||||
 | 
						run_before: []const *const SystemInfo = &.{},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const ThreadRestriction = enum {
 | 
				
			||||||
 | 
						none,
 | 
				
			||||||
 | 
						main,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub const TypeID = enum (usize) { _ };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn Write(comptime Value: type) type {
 | 
				
			||||||
 | 
						return Shared(Value, .{
 | 
				
			||||||
 | 
							.thread_restriction = thread_restriction(Value),
 | 
				
			||||||
 | 
							.read_only = false,
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn launch_arg(flag: LaunchFlag) ?[]const u8 {
 | 
				
			||||||
 | 
						return LaunchFlag.args[@intFromEnum(flag)];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn parameter_type(comptime Value: type) *const SystemInfo.Parameter {
 | 
				
			||||||
 | 
						const ValueParams = Params(Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (@sizeOf(Value) == 0) {
 | 
				
			||||||
 | 
							@compileError("System parameters must have a non-zero size");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const parameters = struct {
 | 
				
			||||||
 | 
							fn bind(allocator: std.mem.Allocator, context: World.BindContext) std.mem.Allocator.Error!*anyopaque {
 | 
				
			||||||
 | 
								const value_name = @typeName(Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!@hasDecl(Value, "bind")) {
 | 
				
			||||||
 | 
									@compileError(
 | 
				
			||||||
 | 
										"a `bind` declaration on " ++
 | 
				
			||||||
 | 
										value_name ++
 | 
				
			||||||
 | 
										" is requied for parameter types with a `Param` declaration");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const bind_type = @typeInfo(@TypeOf(Value.bind));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (bind_type != .Fn) {
 | 
				
			||||||
 | 
									@compileError("`bind` declaration on " ++ value_name ++ " must be a fn");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (bind_type.Fn.params.len != 1 or bind_type.Fn.params[0].type.? != World.BindContext) {
 | 
				
			||||||
 | 
									@compileError(
 | 
				
			||||||
 | 
										"`bind` fn on " ++
 | 
				
			||||||
 | 
										value_name ++
 | 
				
			||||||
 | 
										" must accept " ++
 | 
				
			||||||
 | 
										@typeName(World.BindContext) ++
 | 
				
			||||||
 | 
										" as it's one and only argument");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const params_node = try allocator.create(ValueParams.Node);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								params_node.* = .{
 | 
				
			||||||
 | 
									.param = switch (bind_type.Fn.return_type.?) {
 | 
				
			||||||
 | 
										Value.Param => Value.bind(context),
 | 
				
			||||||
 | 
										std.mem.Allocator.Error!Value.Param => try Value.bind(context),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										else => @compileError(std.fmt.comptimePrint("`bind` fn on {s} must return {s} or {s}", .{
 | 
				
			||||||
 | 
											@typeName(Value),
 | 
				
			||||||
 | 
											@typeName(Value.Param),
 | 
				
			||||||
 | 
											@typeName(std.mem.Allocator.Error!Value.Param)
 | 
				
			||||||
 | 
										})),
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (context.world.get_state(ValueParams)) |value_params| {
 | 
				
			||||||
 | 
									if (value_params.has_tail) |tail| {
 | 
				
			||||||
 | 
										tail.has_next = params_node;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									params_node.has_prev = value_params.has_tail;
 | 
				
			||||||
 | 
									value_params.has_tail = params_node;
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									try context.world.set_state(ValueParams{
 | 
				
			||||||
 | 
										.has_head = params_node,
 | 
				
			||||||
 | 
										.has_tail = params_node,
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								return @ptrCast(params_node);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn init(argument: *anyopaque, erased_node: *anyopaque) void {
 | 
				
			||||||
 | 
								const value_name = @typeName(Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (!@hasDecl(Value, "init")) {
 | 
				
			||||||
 | 
									@compileError("an `init` declaration on " ++ value_name ++ " is requied for parameter types");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const init_type = @typeInfo(@TypeOf(Value.init));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (init_type != .Fn) {
 | 
				
			||||||
 | 
									@compileError("`init` declaration on " ++ value_name ++ " must be a fn");
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (init_type.Fn.return_type.? != Value) {
 | 
				
			||||||
 | 
									@compileError("`init` fn on " ++ value_name ++ " must return a " ++ value_name);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const concrete_argument = @as(*Value, @ptrCast(@alignCast(argument)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (init_type.Fn.params.len != 1 or init_type.Fn.params[0].type.? != *Value.Param) {
 | 
				
			||||||
 | 
									@compileError("`init` fn on " ++ value_name ++ " must accept a " ++ @typeName(*Value.Param));
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								concrete_argument.* = Value.init(&@as(*ValueParams.Node, @ptrCast(@alignCast(erased_node))).param);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fn unbind(allocator: std.mem.Allocator, erased_node: *anyopaque, context: World.UnbindContext) void {
 | 
				
			||||||
 | 
								if (@hasDecl(Value, "unbind")) {
 | 
				
			||||||
 | 
									const node = @as(*ValueParams.Node, @ptrCast(@alignCast(erased_node)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (node.has_prev) |prev| {
 | 
				
			||||||
 | 
										prev.has_next = node.has_next;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (node.has_next) |next| {
 | 
				
			||||||
 | 
										next.has_prev = node.has_prev;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (context.world.get_state(ValueParams)) |params| {
 | 
				
			||||||
 | 
										if (node.has_prev == null) {
 | 
				
			||||||
 | 
											params.has_head = node.has_next;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (node.has_next == null) {
 | 
				
			||||||
 | 
											params.has_tail = node.has_prev;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									Value.unbind(&node.param, context);
 | 
				
			||||||
 | 
									allocator.destroy(node);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return comptime &.{
 | 
				
			||||||
 | 
							.thread_restriction = if (@hasDecl(Value, "thread_restriction")) Value.thread_restriction else .none,
 | 
				
			||||||
 | 
							.init = parameters.init,
 | 
				
			||||||
 | 
							.bind = parameters.bind,
 | 
				
			||||||
 | 
							.unbind = parameters.unbind,
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn start(comptime setup: Setup, comptime options: Options) fn () anyerror!void {
 | 
				
			||||||
	const Start = struct {
 | 
						const Start = struct {
 | 
				
			||||||
		fn main() anyerror!void {
 | 
							fn main() anyerror!void {
 | 
				
			||||||
			defer {
 | 
								defer {
 | 
				
			||||||
				coral.heap.trace_leaks();
 | 
									heap.trace_leaks();
 | 
				
			||||||
				ext.SDL_Quit();
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			parse_args: for (std.os.argv[1 ..]) |arg| {
 | 
								parse_args: for (std.os.argv[1 ..]) |arg| {
 | 
				
			||||||
@ -82,14 +700,14 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
 | 
				
			|||||||
				const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len;
 | 
									const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len;
 | 
				
			||||||
				const arg_name = arg_span[0 .. arg_split_index];
 | 
									const arg_name = arg_span[0 .. arg_split_index];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				for (Flag.values) |value| {
 | 
									for (LaunchFlag.values) |value| {
 | 
				
			||||||
					const name = @tagName(value);
 | 
										const name = @tagName(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					if (!std.mem.eql(u8, arg_name, name)) {
 | 
										if (!std.mem.eql(u8, arg_name, name)) {
 | 
				
			||||||
						continue;
 | 
											continue;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					Flag.launch_args[@intFromEnum(value)] =
 | 
										LaunchFlag.args[@intFromEnum(value)] =
 | 
				
			||||||
						if (arg_split_index == arg_span.len)
 | 
											if (arg_split_index == arg_span.len)
 | 
				
			||||||
							name
 | 
												name
 | 
				
			||||||
						else
 | 
											else
 | 
				
			||||||
@ -100,7 +718,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			var world = try switch (options.execution) {
 | 
								var world = try switch (options.execution) {
 | 
				
			||||||
				.single_threaded => flow.World.init(0),
 | 
									.single_threaded => World.init(0),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				.thread_share => |thread_share| init: {
 | 
									.thread_share => |thread_share| init: {
 | 
				
			||||||
					const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
 | 
										const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| {
 | 
				
			||||||
@ -111,7 +729,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
 | 
				
			|||||||
						});
 | 
											});
 | 
				
			||||||
					}, 0, std.math.maxInt(u32))));
 | 
										}, 0, std.math.maxInt(u32))));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					break: init flow.World.init(coral.scalars.fractional(cpu_count, thread_share) orelse 0);
 | 
										break: init World.init(scalars.fractional(cpu_count, thread_share) orelse 0);
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -134,7 +752,7 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
 | 
				
			|||||||
				.is_running = true,
 | 
									.is_running = true,
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			for (options.middlewares) |setup_middleware| {
 | 
								inline for (options.middleware) |setup_middleware| {
 | 
				
			||||||
				try setup_middleware(&world, events);
 | 
									try setup_middleware(&world, events);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -172,4 +790,81 @@ pub fn start(comptime setup: Setup, options: Options) fn () anyerror!void {
 | 
				
			|||||||
	return Start.main;
 | 
						return Start.main;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub const system_fn = flow.system_fn;
 | 
					pub fn system_fn(comptime call: anytype) *const SystemInfo {
 | 
				
			||||||
 | 
						const Call = @TypeOf(call);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						const system_info = comptime generate: {
 | 
				
			||||||
 | 
							switch (@typeInfo(Call)) {
 | 
				
			||||||
 | 
								.Fn => |call_fn| {
 | 
				
			||||||
 | 
									if (call_fn.params.len > SystemInfo.max_parameters) {
 | 
				
			||||||
 | 
										@compileError("number of parameters to `call` cannot be more than 16");
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									const systems = struct {
 | 
				
			||||||
 | 
										fn run(parameters: []const *const SystemInfo.Parameter, data: *const [SystemInfo.max_parameters]*anyopaque) anyerror!void {
 | 
				
			||||||
 | 
											var call_args = @as(std.meta.ArgsTuple(Call), undefined);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											inline for (parameters, &call_args, data[0 .. parameters.len]) |parameter, *call_arg, state| {
 | 
				
			||||||
 | 
												parameter.init(call_arg, state);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											switch (@typeInfo(call_fn.return_type.?)) {
 | 
				
			||||||
 | 
												.Void => @call(.auto, call, call_args),
 | 
				
			||||||
 | 
												.ErrorUnion => try @call(.auto, call, call_args),
 | 
				
			||||||
 | 
												else => @compileError("number of parameters to `call` must return void or !void"),
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									var parameters = @as([SystemInfo.max_parameters]*const SystemInfo.Parameter, undefined);
 | 
				
			||||||
 | 
									var system_thread_restriction = ThreadRestriction.none;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									for (0 .. call_fn.params.len) |index| {
 | 
				
			||||||
 | 
										const CallParam = call_fn.params[index].type.?;
 | 
				
			||||||
 | 
										const parameter = parameter_type(CallParam);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										if (parameter.thread_restriction != .none) {
 | 
				
			||||||
 | 
											if (system_thread_restriction != .none and system_thread_restriction != parameter.thread_restriction) {
 | 
				
			||||||
 | 
												@compileError("a system may not have conflicting thread restrictions");
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											system_thread_restriction = parameter.thread_restriction;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										parameters[index] = parameter;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									break: generate &.{
 | 
				
			||||||
 | 
										.parameters = parameters,
 | 
				
			||||||
 | 
										.parameter_count = call_fn.params.len,
 | 
				
			||||||
 | 
										.execute = systems.run,
 | 
				
			||||||
 | 
										.thread_restriction = system_thread_restriction,
 | 
				
			||||||
 | 
									};
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								else => @compileError("parameter `call` must be a function"),
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return system_info;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn type_id(comptime T: type) TypeID {
 | 
				
			||||||
 | 
						const TypeHandle = struct {
 | 
				
			||||||
 | 
							comptime {
 | 
				
			||||||
 | 
								_ = T;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							var byte: u8 = 0;
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return @enumFromInt(@intFromPtr(&TypeHandle.byte));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn thread_restriction(comptime State: type) ThreadRestriction {
 | 
				
			||||||
 | 
						if (@hasDecl(State, "thread_restriction")) {
 | 
				
			||||||
 | 
							return State.thread_restriction;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return .none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,3 @@
 | 
				
			|||||||
const io = @import("./io.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn ElementPtr(comptime Slice: type) type {
 | 
					pub fn ElementPtr(comptime Slice: type) type {
 | 
				
			||||||
@ -25,7 +23,7 @@ pub fn Parallel(comptime Type: type) type {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return struct {
 | 
						return struct {
 | 
				
			||||||
		len: usize = 0,
 | 
							len: usize = 0,
 | 
				
			||||||
		ptrs: [fields.len][*]align (alignment) io.Byte = undefined,
 | 
							ptrs: [fields.len][*]align (alignment) u8 = undefined,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn Element(comptime field: Field) type {
 | 
							pub fn Element(comptime field: Field) type {
 | 
				
			||||||
			return fields[@intFromEnum(field)].type;
 | 
								return fields[@intFromEnum(field)].type;
 | 
				
			||||||
@ -118,7 +116,7 @@ pub fn get_ptr(slice: anytype, index: usize) ?ElementPtr(@TypeOf(slice)) {
 | 
				
			|||||||
pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) {
 | 
					pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Parallel(Element) {
 | 
				
			||||||
	const alignment = @alignOf(Element);
 | 
						const alignment = @alignOf(Element);
 | 
				
			||||||
	const Slices = Parallel(Element);
 | 
						const Slices = Parallel(Element);
 | 
				
			||||||
	var buffers = @as([std.enums.values(Slices.Field).len][]align (alignment) io.Byte, undefined);
 | 
						var buffers = @as([std.enums.values(Slices.Field).len][]align (alignment) u8, undefined);
 | 
				
			||||||
	var buffers_allocated = @as(usize, 0);
 | 
						var buffers_allocated = @as(usize, 0);
 | 
				
			||||||
	var allocated = Slices{.len = n};
 | 
						var allocated = Slices{.len = n};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -131,7 +129,7 @@ pub fn parallel_alloc(comptime Element: type, allocator: std.mem.Allocator, n: u
 | 
				
			|||||||
	const fields = @typeInfo(Element).Struct.fields;
 | 
						const fields = @typeInfo(Element).Struct.fields;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	inline for (0 .. fields.len) |i| {
 | 
						inline for (0 .. fields.len) |i| {
 | 
				
			||||||
		buffers[i] = try allocator.alignedAlloc(io.Byte, alignment, @sizeOf(fields[i].type) * n);
 | 
							buffers[i] = try allocator.alignedAlloc(u8, alignment, @sizeOf(fields[i].type) * n);
 | 
				
			||||||
		buffers_allocated += 1;
 | 
							buffers_allocated += 1;
 | 
				
			||||||
		allocated.ptrs[i] = buffers[i].ptr;
 | 
							allocated.ptrs[i] = buffers[i].ptr;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@ -1,14 +1,10 @@
 | 
				
			|||||||
const io = @import("./io.zig");
 | 
					const ona = @import("./ona.zig");
 | 
				
			||||||
 | 
					 | 
				
			||||||
const scalars = @import("./scalars.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const slices = @import("./slices.zig");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn Sequential(comptime Value: type) type {
 | 
					pub fn Sequential(comptime Value: type) type {
 | 
				
			||||||
	return struct {
 | 
						return struct {
 | 
				
			||||||
		allocator: std.mem.Allocator,
 | 
							allocator: std.mem.Allocator = ona.heap.allocator,
 | 
				
			||||||
		values: []Value = &.{},
 | 
							values: []Value = &.{},
 | 
				
			||||||
		cap: usize = 0,
 | 
							cap: usize = 0,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -82,7 +78,7 @@ pub fn Sequential(comptime Value: type) type {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub fn pop_many(self: *Self, n: usize) bool {
 | 
							pub fn pop_many(self: *Self, n: usize) bool {
 | 
				
			||||||
			const new_length = scalars.sub(self.values.len, n) orelse {
 | 
								const new_length = ona.scalars.sub(self.values.len, n) orelse {
 | 
				
			||||||
				return false;
 | 
									return false;
 | 
				
			||||||
			};
 | 
								};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -190,12 +186,12 @@ pub fn Sequential(comptime Value: type) type {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		pub const writer = switch (Value) {
 | 
							pub const writer = switch (Value) {
 | 
				
			||||||
			io.Byte => struct {
 | 
								u8 => struct {
 | 
				
			||||||
				fn writer(self: *Self) io.Writer {
 | 
									fn writer(self: *Self) ona.io.Writer {
 | 
				
			||||||
					return io.Writer.bind(Self, self, write);
 | 
										return ona.io.Writer.bind(Self, self, write);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				fn write(self: *Self, buffer: []const io.Byte) io.Error!usize {
 | 
									fn write(self: *Self, buffer: []const u8) ona.io.Error!usize {
 | 
				
			||||||
					self.push_all(buffer) catch return error.UnavailableResource;
 | 
										self.push_all(buffer) catch return error.UnavailableResource;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					return buffer.len;
 | 
										return buffer.len;
 | 
				
			||||||
@ -208,7 +204,7 @@ pub fn Sequential(comptime Value: type) type {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn Parallel(comptime Value: type) type {
 | 
					pub fn Parallel(comptime Value: type) type {
 | 
				
			||||||
	const Slices = slices.Parallel(Value);
 | 
						const Slices = ona.slices.Parallel(Value);
 | 
				
			||||||
	const alignment = @alignOf(Value);
 | 
						const alignment = @alignOf(Value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return struct {
 | 
						return struct {
 | 
				
			||||||
@ -229,7 +225,7 @@ pub fn Parallel(comptime Value: type) type {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			capacity_slice.len = self.cap;
 | 
								capacity_slice.len = self.cap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			slices.parallel_free(Value, self.allocator, capacity_slice);
 | 
								ona.slices.parallel_free(Value, self.allocator, capacity_slice);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			self.* = undefined;
 | 
								self.* = undefined;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@ -244,11 +240,11 @@ pub fn Parallel(comptime Value: type) type {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
 | 
							pub fn grow(self: *Self, additional: usize) std.mem.Allocator.Error!void {
 | 
				
			||||||
			const grown_capacity = self.cap + additional;
 | 
								const grown_capacity = self.cap + additional;
 | 
				
			||||||
			const buffer = try slices.parallel_alloc(Value, self.allocator, grown_capacity);
 | 
								const buffer = try ona.slices.parallel_alloc(Value, self.allocator, grown_capacity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (self.cap != 0) {
 | 
								if (self.cap != 0) {
 | 
				
			||||||
				slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values);
 | 
									ona.slices.parallel_copy(Value, buffer.slice_all(0, self.values.len).?, self.values);
 | 
				
			||||||
				slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?);
 | 
									ona.slices.parallel_free(Value, self.allocator, self.values.slice_all(0, self.cap).?);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			self.cap = grown_capacity;
 | 
								self.cap = grown_capacity;
 | 
				
			||||||
@ -1,8 +1,4 @@
 | 
				
			|||||||
const ascii = @import("./ascii.zig");
 | 
					const ona = @import("./ona.zig");
 | 
				
			||||||
 | 
					 | 
				
			||||||
const coral = @import("./coral.zig");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const io = @import("./io.zig");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const std = @import("std");
 | 
					const std = @import("std");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -12,26 +8,26 @@ pub fn alloc_formatted(allocator: std.mem.Allocator, comptime format: []const u8
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	errdefer allocator.free(buffer);
 | 
						errdefer allocator.free(buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: This is dumb.
 | 
						// TODO: This is messy.
 | 
				
			||||||
	return @constCast(print_formatted(buffer, format, args) catch unreachable);
 | 
						return @constCast(print_formatted(buffer, format, args) catch unreachable);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
 | 
					pub fn count_formatted(comptime format: []const u8, args: anytype) usize {
 | 
				
			||||||
	var count = io.NullWritable{};
 | 
						var count = ona.io.NullWritable{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	write_formatted(count.writer(), format, args) catch unreachable;
 | 
						write_formatted(count.writer(), format, args) catch unreachable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return count.written;
 | 
						return count.written;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, args: anytype) io.Error![:0]const u8 {
 | 
					pub fn print_formatted(buffer: [:0]u8, comptime format: []const u8, args: anytype) ona.io.Error![:0]const u8 {
 | 
				
			||||||
	const Seekable = struct {
 | 
						const Seekable = struct {
 | 
				
			||||||
		buffer: []coral.io.Byte,
 | 
							buffer: []u8,
 | 
				
			||||||
		cursor: usize,
 | 
							cursor: usize,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		const Self = @This();
 | 
							const Self = @This();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		fn write(self: *Self, input: []const coral.io.Byte) io.Error!usize {
 | 
							fn write(self: *Self, input: []const u8) ona.io.Error!usize {
 | 
				
			||||||
			const range = @min(input.len, self.buffer.len - self.cursor);
 | 
								const range = @min(input.len, self.buffer.len - self.cursor);
 | 
				
			||||||
			const tail = self.cursor + range;
 | 
								const tail = self.cursor + range;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -54,7 +50,7 @@ pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, a
 | 
				
			|||||||
		.cursor = 0,
 | 
							.cursor = 0,
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try write_formatted(coral.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args);
 | 
						try write_formatted(ona.io.Writer.bind(Seekable, &seekable, Seekable.write), format, args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (buffer.len < len) {
 | 
						if (buffer.len < len) {
 | 
				
			||||||
		buffer[len] = 0;
 | 
							buffer[len] = 0;
 | 
				
			||||||
@ -63,7 +59,7 @@ pub fn print_formatted(buffer: [:0]coral.io.Byte, comptime format: []const u8, a
 | 
				
			|||||||
	return buffer[0 .. len:0];
 | 
						return buffer[0 .. len:0];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: anytype) io.Error!void {
 | 
					pub fn write_formatted(writer: ona.io.Writer, comptime format: []const u8, args: anytype) ona.io.Error!void {
 | 
				
			||||||
	switch (@typeInfo(@TypeOf(args))) {
 | 
						switch (@typeInfo(@TypeOf(args))) {
 | 
				
			||||||
		.Struct => |arguments_struct| {
 | 
							.Struct => |arguments_struct| {
 | 
				
			||||||
			comptime var arg_index = 0;
 | 
								comptime var arg_index = 0;
 | 
				
			||||||
@ -80,7 +76,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
					switch (format[tail]) {
 | 
										switch (format[tail]) {
 | 
				
			||||||
						'{' => {
 | 
											'{' => {
 | 
				
			||||||
							try io.print(writer, format[head .. (tail - 1)]);
 | 
												try ona.io.print(writer, format[head .. (tail - 1)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							tail += 1;
 | 
												tail += 1;
 | 
				
			||||||
							head = tail;
 | 
												head = tail;
 | 
				
			||||||
@ -91,7 +87,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
 | 
				
			|||||||
								@compileError("all format specifiers must be named when using a named struct");
 | 
													@compileError("all format specifiers must be named when using a named struct");
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							try io.print(writer, args[arg_index]);
 | 
												try ona.io.print(writer, args[arg_index]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							arg_index += 1;
 | 
												arg_index += 1;
 | 
				
			||||||
							tail += 1;
 | 
												tail += 1;
 | 
				
			||||||
@ -103,7 +99,7 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
 | 
				
			|||||||
								@compileError("format specifiers cannot be named when using a tuple struct");
 | 
													@compileError("format specifiers cannot be named when using a tuple struct");
 | 
				
			||||||
							}
 | 
												}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							try io.write_all(writer, format[head .. (tail - 1)]);
 | 
												try ona.io.write_all(writer, format[head .. (tail - 1)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
							head = tail;
 | 
												head = tail;
 | 
				
			||||||
							tail += 1;
 | 
												tail += 1;
 | 
				
			||||||
@ -129,25 +125,27 @@ pub fn write_formatted(writer: io.Writer, comptime format: []const u8, args: any
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			try io.write_all(writer, format[head .. ]);
 | 
								try ona.io.write_all(writer, format[head .. ]);
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		else => @compileError("`arguments` must be a struct type"),
 | 
							else => @compileError("`arguments` must be a struct type"),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
noinline fn print_formatted_value(writer: io.Writer, value: anytype) io.Error!void {
 | 
					noinline fn print_formatted_value(writer: ona.io.Writer, value: anytype) ona.io.Error!void {
 | 
				
			||||||
	const Value = @TypeOf(value);
 | 
						const Value = @TypeOf(value);
 | 
				
			||||||
 | 
						const hexadecimal_format = ona.ascii.HexadecimalFormat{};
 | 
				
			||||||
 | 
						const decimal_format = ona.ascii.DecimalFormat{};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return switch (@typeInfo(Value)) {
 | 
						return switch (@typeInfo(Value)) {
 | 
				
			||||||
		.Int => ascii.DecimalFormat.default.format(writer, value),
 | 
							.Int => decimal_format.format(writer, value),
 | 
				
			||||||
		.Float => ascii.DecimalFormat.default.format(writer, value),
 | 
							.Float => decimal_format.format(writer, value),
 | 
				
			||||||
		.Enum => io.print(writer, @tagName(value)),
 | 
							.Enum => ona.io.write_all(writer, @tagName(value)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		.Pointer => |pointer| switch (pointer.size) {
 | 
							.Pointer => |pointer| switch (pointer.size) {
 | 
				
			||||||
			.Many, .C => ascii.HexadecimalFormat.default.format(writer, @intFromPtr(value)),
 | 
								.Many, .C => hexadecimal_format.format(writer, @intFromPtr(value)),
 | 
				
			||||||
			.One => if (pointer.child == []const u8) io.write_all(writer, *value) else ascii.HexadecimalFormat.default.print(writer, @intFromPtr(value)),
 | 
								.One => if (pointer.child == []const u8) ona.io.write_all(writer, *value) else hexadecimal_format.format(writer, @intFromPtr(value)),
 | 
				
			||||||
			.Slice => if (pointer.child == u8) io.write_all(writer, value) else @compileError(unformattableMessage(Value)),
 | 
								.Slice => if (pointer.child == u8) ona.io.write_all(writer, value) else @compileError(unformattableMessage(Value)),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		else => @compileError(unformattableMessage(Value)),
 | 
							else => @compileError(unformattableMessage(Value)),
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user