Add GUI module
This commit is contained in:
		
							parent
							
								
									bb1d383ccb
								
							
						
					
					
						commit
						7e8691c050
					
				
							
								
								
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.vscode/launch.json
									
									
									
									
										vendored
									
									
								
							| @ -5,7 +5,7 @@ | |||||||
| 			"name": "Runner", | 			"name": "Runner", | ||||||
| 			"type": "gdb", | 			"type": "gdb", | ||||||
| 			"request": "launch", | 			"request": "launch", | ||||||
| 			"target": "${workspaceRoot}/demos/effects.out", | 			"target": "${workspaceRoot}/demos/canvas.out", | ||||||
| 			"cwd": "${workspaceRoot}/demos/", | 			"cwd": "${workspaceRoot}/demos/", | ||||||
| 			"valuesFormatting": "prettyPrinters", | 			"valuesFormatting": "prettyPrinters", | ||||||
| 			"preLaunchTask": "Build All" | 			"preLaunchTask": "Build All" | ||||||
|  | |||||||
							
								
								
									
										14
									
								
								build.zig
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								build.zig
									
									
									
									
									
								
							| @ -254,6 +254,20 @@ pub fn build(b: *std.Build) !void { | |||||||
| 
 | 
 | ||||||
| 	gfx_module.link_libc = true; | 	gfx_module.link_libc = true; | ||||||
| 
 | 
 | ||||||
|  | 	_ = try project.add_module(b, "gui", .{ | ||||||
|  | 		.imports = &.{ | ||||||
|  | 			.{ | ||||||
|  | 				.name = "ona", | ||||||
|  | 				.module = ona_module, | ||||||
|  | 			}, | ||||||
|  | 
 | ||||||
|  | 			.{ | ||||||
|  | 				.name = "gfx", | ||||||
|  | 				.module = gfx_module, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
| 	project.find_demos(b.step("demos", "Build demos")); | 	project.find_demos(b.step("demos", "Build demos")); | ||||||
| 	project.find_tests(b.step("tests", "Build and run tests")); | 	project.find_tests(b.step("tests", "Build and run tests")); | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								demos/canvas.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								demos/canvas.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | const gfx = @import("gfx"); | ||||||
|  | 
 | ||||||
|  | const gui = @import("gui"); | ||||||
|  | 
 | ||||||
|  | const hid = @import("hid"); | ||||||
|  | 
 | ||||||
|  | const ona = @import("ona"); | ||||||
|  | 
 | ||||||
|  | pub const main = ona.App.game | ||||||
|  | 	.with_module(gfx) | ||||||
|  | 	.with_module(hid) | ||||||
|  | 	.with_module(gui) | ||||||
|  | 	.with_system(.load, ona.system_fn(load), .{ .label = "Hello Ona" }).build(); | ||||||
|  | 
 | ||||||
|  | pub fn load(canvas: ona.Write(gui.Canvas)) !void { | ||||||
|  | 	const item = try canvas.state.append("", .{ | ||||||
|  | 		.left = 0, | ||||||
|  | 		.top = 0, | ||||||
|  | 		.width = 256, | ||||||
|  | 		.height = 256, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	canvas.state.set_block(item, .{ | ||||||
|  | 		.has_background = .default, | ||||||
|  | 		.has_color = gfx.colors.purple, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	try canvas.state.set_label(item, .{ | ||||||
|  | 		.has_text = "Hello, world", | ||||||
|  | 	}); | ||||||
|  | } | ||||||
| @ -56,23 +56,23 @@ fn load(display: ona.Write(gfx.Display), effects: ona.Write(Effects), assets: on | |||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub const main = ona.App.setup. | pub const main = ona.App.game | ||||||
| 	with_module(gfx). | 	.with_module(gfx) | ||||||
| 	with_state(Effects{}). | 	.with_state(Effects{}) | ||||||
| 	with_system(.load, ona.system_fn(load), .{.label = "load effects"}). | 	.with_system(.load, ona.system_fn(load), .{.label = "load effects"}) | ||||||
| 	with_system(.update, ona.system_fn(update), .{.label = "update effects"}). | 	.with_system(.update, ona.system_fn(update), .{.label = "update effects"}) | ||||||
| 	with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build(); | 	.with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build(); | ||||||
| 
 | 
 | ||||||
| fn update(effects: ona.Write(Effects), app: ona.Read(ona.App)) void { | fn update(effects: ona.Write(Effects), loop: ona.Read(ona.Loop)) void { | ||||||
| 	const update_seconds = 5; | 	const update_seconds = 5; | ||||||
| 
 | 
 | ||||||
| 	if ((app.state.elapsed_time - effects.state.last_time) > update_seconds) { | 	if ((loop.state.elapsed_time - effects.state.last_time) > update_seconds) { | ||||||
| 		effects.state.image_index = (effects.state.image_index + 1) % effects.state.image_textures.len; | 		effects.state.image_index = (effects.state.image_index + 1) % effects.state.image_textures.len; | ||||||
| 		effects.state.last_time = app.state.elapsed_time; | 		effects.state.last_time = loop.state.elapsed_time; | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona.App), display: ona.Write(gfx.Display)) !void { | fn render(commands: gfx.Commands(.background), effects: ona.Write(Effects), loop: ona.Read(ona.Loop), display: ona.Write(gfx.Display)) !void { | ||||||
| 	try commands.set_target(.{ | 	try commands.set_target(.{ | ||||||
| 		.texture = effects.state.render_texture, | 		.texture = effects.state.render_texture, | ||||||
| 		.clear_color = gfx.colors.black, | 		.clear_color = gfx.colors.black, | ||||||
| @ -80,26 +80,21 @@ fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona | |||||||
| 		.clear_stencil = 0, | 		.clear_stencil = 0, | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	const display_width: f32 = @floatFromInt(display.state.width); | 	const width: f32 = @floatFromInt(display.state.width); | ||||||
| 	const display_height: f32 = @floatFromInt(display.state.height); | 	const height: f32 = @floatFromInt(display.state.height); | ||||||
| 
 |  | ||||||
| 	const display_transform = gfx.transform_2d(.{ |  | ||||||
| 		.translation = .{display_width / 2, display_height / 2}, |  | ||||||
| 	}); |  | ||||||
| 
 | 
 | ||||||
| 	try commands.draw_texture(.{ | 	try commands.draw_texture(.{ | ||||||
| 		.texture = effects.state.image_textures[effects.state.image_index], | 		.texture = effects.state.image_textures[effects.state.image_index], | ||||||
| 		.transform = display_transform, | 		.size = .{width, height}, | ||||||
| 		.resolution = .{display.state.width, display.state.height}, |  | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	try commands.set_effect(.{ | 	try commands.set_effect(.{ | ||||||
| 		.effect = effects.state.crt_effect, | 		.effect = effects.state.crt_effect, | ||||||
| 
 | 
 | ||||||
| 		.properties = std.mem.asBytes(&CRT{ | 		.properties = std.mem.asBytes(&CRT{ | ||||||
| 			.width = display_width, | 			.width = width, | ||||||
| 			.height = display_height, | 			.height = height, | ||||||
| 			.time = @floatCast(app.state.elapsed_time), | 			.time = @floatCast(loop.state.elapsed_time), | ||||||
| 		}), | 		}), | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| @ -111,7 +106,7 @@ fn render(commands: gfx.Commands, effects: ona.Write(Effects), app: ona.Read(ona | |||||||
| 
 | 
 | ||||||
| 	try commands.draw_texture(.{ | 	try commands.draw_texture(.{ | ||||||
| 		.texture = effects.state.render_texture, | 		.texture = effects.state.render_texture, | ||||||
| 		.transform = display_transform, | 		.size = .{width, height}, | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -9,7 +9,8 @@ const std = @import("std"); | |||||||
| const Spawned = struct { | const Spawned = struct { | ||||||
| 	visual: struct { | 	visual: struct { | ||||||
| 		color: gfx.Color, | 		color: gfx.Color, | ||||||
| 		transform: gfx.Transform2D, | 		position: gfx.Vector2, | ||||||
|  | 		rotation: f32, | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	lifetime_seconds: f32, | 	lifetime_seconds: f32, | ||||||
| @ -23,7 +24,6 @@ const Visuals = struct { | |||||||
| 
 | 
 | ||||||
| fn cleanup(visuals: ona.Write(Visuals)) !void { | fn cleanup(visuals: ona.Write(Visuals)) !void { | ||||||
| 	visuals.state.spawned.deinit(); | 	visuals.state.spawned.deinit(); | ||||||
| 	visuals.state.spawned.deinit(); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn load(visuals: ona.Write(Visuals)) !void { | fn load(visuals: ona.Write(Visuals)) !void { | ||||||
| @ -32,14 +32,14 @@ fn load(visuals: ona.Write(Visuals)) !void { | |||||||
| 	try visuals.state.spawned.grow(initial_spawn_capacity); | 	try visuals.state.spawned.grow(initial_spawn_capacity); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub const main = ona.App.setup. | pub const main = ona.App.game | ||||||
| 	with_module(gfx). | 	.with_module(gfx) | ||||||
| 	with_module(hid). | 	.with_module(hid) | ||||||
| 	with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)}). | 	.with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)}) | ||||||
| 	with_system(.load, ona.system_fn(load), .{.label = "load visuals"}). | 	.with_system(.load, ona.system_fn(load), .{.label = "load visuals"}) | ||||||
| 	with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"}). | 	.with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"}) | ||||||
| 	with_system(.render, ona.system_fn(render), .{.label = "render visuals"}). | 	.with_system(.render, ona.system_fn(render), .{.label = "render visuals"}) | ||||||
| 	with_system(.exit, ona.system_fn(cleanup), .{.label = "clean up visuals"}).build(); | 	.with_system(.exit, ona.system_fn(cleanup), .{.label = "clean up visuals"}).build(); | ||||||
| 
 | 
 | ||||||
| fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: ona.Read(gfx.Display)) !void { | fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: ona.Read(gfx.Display)) !void { | ||||||
| 	update_spawned(&visuals.state.spawned); | 	update_spawned(&visuals.state.spawned); | ||||||
| @ -47,7 +47,6 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: | |||||||
| 	const random = visuals.state.random.random(); | 	const random = visuals.state.random.random(); | ||||||
| 	const width: f32 = @floatFromInt(display.state.width); | 	const width: f32 = @floatFromInt(display.state.width); | ||||||
| 	const height: f32 = @floatFromInt(display.state.height); | 	const height: f32 = @floatFromInt(display.state.height); | ||||||
| 	const icon_scale = .{8, 8}; |  | ||||||
| 
 | 
 | ||||||
| 	for (events.messages()) |event| { | 	for (events.messages()) |event| { | ||||||
| 		switch (event) { | 		switch (event) { | ||||||
| @ -57,12 +56,8 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: | |||||||
| 
 | 
 | ||||||
| 					.visual = .{ | 					.visual = .{ | ||||||
| 						.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, | 						.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, | ||||||
| 
 | 						.position = .{width * random.float(f32), height}, | ||||||
| 						.transform = gfx.transform_2d(.{ | 						.rotation = std.math.pi * random.float(f32), | ||||||
| 							.translation = .{width * random.float(f32), height}, |  | ||||||
| 							.rotation = std.math.pi * random.float(f32), |  | ||||||
| 							.scale = icon_scale, |  | ||||||
| 						}), |  | ||||||
| 					}, | 					}, | ||||||
| 				}); | 				}); | ||||||
| 			}, | 			}, | ||||||
| @ -73,12 +68,8 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: | |||||||
| 
 | 
 | ||||||
| 					.visual = .{ | 					.visual = .{ | ||||||
| 						.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, | 						.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, | ||||||
| 
 | 						.position = visuals.state.mouse_position, | ||||||
| 						.transform = gfx.transform_2d(.{ | 						.rotation = std.math.pi * random.float(f32), | ||||||
| 							.translation = visuals.state.mouse_position, |  | ||||||
| 							.rotation = std.math.pi * random.float(f32), |  | ||||||
| 							.scale = icon_scale, |  | ||||||
| 						}), |  | ||||||
| 					}, | 					}, | ||||||
| 				}); | 				}); | ||||||
| 			}, | 			}, | ||||||
| @ -96,7 +87,7 @@ fn update_spawned(spawned: *ona.stack.Parallel(Spawned)) void { | |||||||
| 	const float_speed = 6; | 	const float_speed = 6; | ||||||
| 
 | 
 | ||||||
| 	for (spawned.values.slice(.visual)) |*visual| { | 	for (spawned.values.slice(.visual)) |*visual| { | ||||||
| 		visual.transform = visual.transform.translated(.{0, -float_speed}); | 		visual.position -= .{0, float_speed}; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	{ | 	{ | ||||||
| @ -119,11 +110,13 @@ fn update_spawned(spawned: *ona.stack.Parallel(Spawned)) void { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| fn render(visuals: ona.Write(Visuals), commands: gfx.Commands) !void { | fn render(visuals: ona.Write(Visuals), commands: gfx.Commands(.foreground)) !void { | ||||||
| 	for (visuals.state.spawned.values.slice(.visual)) |visual| { | 	for (visuals.state.spawned.values.slice(.visual)) |visual| { | ||||||
| 		try commands.draw_rect(.{ | 		try commands.draw_texture(.{ | ||||||
| 			.transform = visual.transform, | 			.anchor = @splat(0.5), | ||||||
| 			.color = visual.color, | 			.position = visual.position, | ||||||
|  | 			.tint = visual.color, | ||||||
|  | 			.size = @as(gfx.Vector2, @splat(64)), | ||||||
| 		}); | 		}); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  | |||||||
| @ -38,7 +38,7 @@ pub const Effect = struct { | |||||||
| 			}, | 			}, | ||||||
| 
 | 
 | ||||||
| 			.vertex_source = .{ | 			.vertex_source = .{ | ||||||
| 				.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), | 				.ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.vert.spv")), | ||||||
| 			}, | 			}, | ||||||
| 
 | 
 | ||||||
| 			.fragment_source = .{ | 			.fragment_source = .{ | ||||||
| @ -197,12 +197,12 @@ pub const Effect = struct { | |||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
| 			attrs[2] = .{ | 			attrs[2] = .{ | ||||||
| 				.format = .FLOAT2, | 				.format = .FLOAT, | ||||||
| 				.buffer_index = 1, | 				.buffer_index = 1, | ||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
| 			attrs[3] = .{ | 			attrs[3] = .{ | ||||||
| 				.format = .FLOAT2, | 				.format = .FLOAT, | ||||||
| 				.buffer_index = 1, | 				.buffer_index = 1, | ||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
| @ -212,12 +212,12 @@ pub const Effect = struct { | |||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
| 			attrs[5] = .{ | 			attrs[5] = .{ | ||||||
| 				.format = .UBYTE4N, | 				.format = .FLOAT2, | ||||||
| 				.buffer_index = 1, | 				.buffer_index = 1, | ||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
| 			attrs[6] = .{ | 			attrs[6] = .{ | ||||||
| 				.format = .FLOAT, | 				.format = .FLOAT2, | ||||||
| 				.buffer_index = 1, | 				.buffer_index = 1, | ||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
| @ -226,6 +226,11 @@ pub const Effect = struct { | |||||||
| 				.buffer_index = 1, | 				.buffer_index = 1, | ||||||
| 			}; | 			}; | ||||||
| 
 | 
 | ||||||
|  | 			attrs[8] = .{ | ||||||
|  | 				.format = .FLOAT4, | ||||||
|  | 				.buffer_index = 1, | ||||||
|  | 			}; | ||||||
|  | 
 | ||||||
| 			break: get attrs; | 			break: get attrs; | ||||||
| 		}, | 		}, | ||||||
| 
 | 
 | ||||||
| @ -466,7 +471,7 @@ pub fn init() !Self { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	assert.is_default_handle(try pools.create_effect(.{ | 	assert.is_default_handle(try pools.create_effect(.{ | ||||||
| 		.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/2d_default.frag.spv")), | 		.fragment_spirv_ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.frag.spv")), | ||||||
| 	})); | 	})); | ||||||
| 
 | 
 | ||||||
| 	assert.is_default_handle(try pools.create_texture(try descs.solid_texture(.{ | 	assert.is_default_handle(try pools.create_texture(try descs.solid_texture(.{ | ||||||
|  | |||||||
							
								
								
									
										418
									
								
								src/gfx/gfx.zig
									
									
									
									
									
								
							
							
						
						
									
										418
									
								
								src/gfx/gfx.zig
									
									
									
									
									
								
							| @ -27,40 +27,7 @@ pub const Assets = struct { | |||||||
| 		load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc, | 		load_file: *const fn (*std.heap.ArenaAllocator, ona.files.Storage, []const u8) LoadFileError!Texture.Desc, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	pub const WorkQueue = ona.asyncio.BlockingQueue(1024, union (enum) { | 	pub const WorkQueue = ona.asyncio.BlockingQueue(1024, Work); | ||||||
| 		load_effect: LoadEffectWork, |  | ||||||
| 		load_texture: LoadTextureWork, |  | ||||||
| 		render_frame: RenderFrameWork, |  | ||||||
| 		shutdown, |  | ||||||
| 		unload_effect: UnloadEffectWork, |  | ||||||
| 		unload_texture: UnloadTextureWork, |  | ||||||
| 
 |  | ||||||
| 		const LoadEffectWork = struct { |  | ||||||
| 			desc: Effect.Desc, |  | ||||||
| 			loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect), |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		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 { | 	fn deinit(self: *Assets) void { | ||||||
| 		self.pending_work.enqueue(.shutdown); | 		self.pending_work.enqueue(.shutdown); | ||||||
| @ -72,12 +39,10 @@ pub const Assets = struct { | |||||||
| 		self.* = undefined; | 		self.* = undefined; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fn init() !Assets { | 	fn init(width: u16, height: u16) !Assets { | ||||||
| 		const window = create: { | 		const window = create: { | ||||||
| 			const position = ext.SDL_WINDOWPOS_CENTERED; | 			const position = ext.SDL_WINDOWPOS_CENTERED; | ||||||
| 			const flags = ext.SDL_WINDOW_OPENGL; | 			const flags = ext.SDL_WINDOW_OPENGL; | ||||||
| 			const width = 640; |  | ||||||
| 			const height = 480; |  | ||||||
| 
 | 
 | ||||||
| 			break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse { | 			break: create ext.SDL_CreateWindow("Ona", position, position, width, height, flags) orelse { | ||||||
| 				return error.Unsupported; | 				return error.Unsupported; | ||||||
| @ -138,139 +103,171 @@ pub const Assets = struct { | |||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub const thread_restriction = .main; | 	pub const thread_restriction = .main; | ||||||
|  | 
 | ||||||
|  | 	pub fn unload_effect(self: *Assets, handle: Effect) void { | ||||||
|  | 		self.pending_work.enqueue(.{.unload_effect = handle}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn unload_texture(self: *Assets, handle: Texture) void { | ||||||
|  | 		self.pending_work.enqueue(.{.unload_texture = handle}); | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const Color = @Vector(4, f32); | pub const Color = @Vector(4, f32); | ||||||
| 
 | 
 | ||||||
| pub const Commands = struct { | pub const Command = union (enum) { | ||||||
| 	pending: *List, | 	draw_font: DrawFont, | ||||||
|  | 	draw_texture: DrawTexture, | ||||||
|  | 	set_effect: SetEffect, | ||||||
|  | 	set_target: SetTarget, | ||||||
| 
 | 
 | ||||||
| 	pub const Command = union (enum) { | 	pub const DrawFont = struct { | ||||||
| 		draw_rect: DrawRectCommand, | 		position: Vector2 = @splat(0), | ||||||
| 		draw_texture: DrawTextureCommand, | 		size: Vector2, | ||||||
| 		set_effect: SetEffectCommand, | 		text: []const u8, | ||||||
| 		set_target: SetTargetCommand, | 		font: Font, | ||||||
|  | 		tint: Color, | ||||||
|  | 		depth: f32, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	pub const DrawRectCommand = struct { | 	pub const DrawTexture = struct { | ||||||
| 		color: Color, | 		anchor: Vector2 = @splat(0), | ||||||
| 		transform: Transform2D, | 		size: ?Vector2 = null, | ||||||
|  | 		source: Rect = .{.left = 0, .top = 0, .right = 1, .bottom = 1}, | ||||||
|  | 		position: Vector2 = @splat(0), | ||||||
|  | 		tint: Color = colors.white, | ||||||
|  | 		texture: Texture = .default, | ||||||
|  | 		rotation: f32 = 0, | ||||||
|  | 		depth: f32 = 0, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	pub const DrawTextureCommand = struct { | 	pub const SetEffect = struct { | ||||||
| 		texture: Texture, |  | ||||||
| 		transform: Transform2D, |  | ||||||
| 		resolution: ?@Vector(2, u16) = null, |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	pub const SetEffectCommand = struct { |  | ||||||
| 		effect: Effect, | 		effect: Effect, | ||||||
| 		properties: []const u8, | 		properties: []const u8, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	pub const SetTargetCommand = struct { | 	pub const SetTarget = struct { | ||||||
| 		texture: ?Texture = null, | 		texture: ?Texture = null, | ||||||
| 		clear_color: ?Color, | 		clear_color: ?Color, | ||||||
| 		clear_depth: ?f32, | 		clear_depth: ?f32, | ||||||
| 		clear_stencil: ?u8, | 		clear_stencil: ?u8, | ||||||
| 	}; | 	}; | ||||||
|  | }; | ||||||
| 
 | 
 | ||||||
| 	pub const List = struct { | pub fn Commands(comptime layer: Layer) type { | ||||||
| 		arena: std.heap.ArenaAllocator, | 	return struct { | ||||||
| 		stack: ona.stack.Sequential(Command), | 		pending: *List, | ||||||
| 
 | 
 | ||||||
| 		fn clear(self: *List) void { | 		pub const List = struct { | ||||||
| 			self.stack.clear(); | 			arena: std.heap.ArenaAllocator, | ||||||
|  | 			stack: ona.stack.Sequential(Command), | ||||||
| 
 | 
 | ||||||
| 			if (!self.arena.reset(.retain_capacity)) { | 			fn clear(self: *List) void { | ||||||
| 				std.log.warn("failed to reset the buffer of a gfx queue with retained capacity", .{}); | 				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 { | 			fn deinit(self: *List) void { | ||||||
| 			self.arena.deinit(); | 				self.arena.deinit(); | ||||||
| 			self.stack.deinit(); | 				self.stack.deinit(); | ||||||
| 
 | 
 | ||||||
| 			self.* = undefined; | 				self.* = undefined; | ||||||
| 		} | 			} | ||||||
| 
 | 
 | ||||||
| 		fn init(allocator: std.mem.Allocator) List { | 			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; | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		const Self = @This(); | ||||||
|  | 
 | ||||||
|  | 		pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param { | ||||||
| 			return .{ | 			return .{ | ||||||
| 				.arena = std.heap.ArenaAllocator.init(allocator), | 				.swap_lists = .{ | ||||||
| 				.stack = .{.allocator = allocator}, | 					List.init(ona.heap.allocator), | ||||||
|  | 					List.init(ona.heap.allocator), | ||||||
|  | 				}, | ||||||
| 			}; | 			}; | ||||||
| 		} | 		} | ||||||
| 	}; |  | ||||||
| 
 | 
 | ||||||
| 	pub const Param = struct { | 		pub fn init(param: *Param) Self { | ||||||
| 		swap_lists: [2]List, | 			return .{ | ||||||
| 		swap_state: u1 = 0, | 				.pending = param.pending_list(), | ||||||
| 
 | 			}; | ||||||
| 		fn deinit(self: *Param) void { |  | ||||||
| 			for (&self.swap_lists) |*list| { |  | ||||||
| 				list.deinit(); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			self.* = undefined; |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fn pending_list(self: *Param) *List { | 		pub fn draw_font(self: Self, command: Command.DrawFont) std.mem.Allocator.Error!void { | ||||||
| 			return &self.swap_lists[self.swap_state]; | 			try self.pending.stack.push_grow(.{ | ||||||
|  | 				.draw_font = .{ | ||||||
|  | 					.text = try self.pending.arena.allocator().dupe(u8, command.text), | ||||||
|  | 					.position = command.position, | ||||||
|  | 					.size = command.size, | ||||||
|  | 					.font = command.font, | ||||||
|  | 					.tint = command.tint, | ||||||
|  | 					.depth = command.depth, | ||||||
|  | 				}, | ||||||
|  | 			}); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		fn rotate(self: *Param) void { | 		pub fn draw_texture(self: Self, command: Command.DrawTexture) std.mem.Allocator.Error!void { | ||||||
| 			const swapped_state = self.swap_state ^ 1; | 			try self.pending.stack.push_grow(.{.draw_texture = command}); | ||||||
| 
 |  | ||||||
| 			self.swap_lists[swapped_state].clear(); |  | ||||||
| 
 |  | ||||||
| 			self.swap_state = swapped_state; |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		pub fn submitted_commands(self: Param) []const Command { | 		pub fn set_effect(self: Self, command: Command.SetEffect) std.mem.Allocator.Error!void { | ||||||
| 			return self.swap_lists[self.swap_state ^ 1].stack.values; | 			try self.pending.stack.push_grow(.{ | ||||||
|  | 				.set_effect = .{ | ||||||
|  | 					.properties = try self.pending.arena.allocator().dupe(u8, command.properties), | ||||||
|  | 					.effect = command.effect, | ||||||
|  | 				}, | ||||||
|  | 			}); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		pub const _ = layer; | ||||||
|  | 
 | ||||||
|  | 		pub fn unbind(param: *Param, _: ona.World.UnbindContext) void { | ||||||
|  | 			param.deinit(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		pub fn set_target(self: Self, command: Command.SetTarget) std.mem.Allocator.Error!void { | ||||||
|  | 			try self.pending.stack.push_grow(.{.set_target = command}); | ||||||
| 		} | 		} | ||||||
| 	}; | 	}; | ||||||
| 
 | } | ||||||
| 	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_rect(self: Commands, command: DrawRectCommand) std.mem.Allocator.Error!void { |  | ||||||
| 		try self.pending.stack.push_grow(.{.draw_rect = command}); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	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 Descs = struct { | pub const Descs = struct { | ||||||
| 	arena: std.heap.ArenaAllocator, | 	arena: std.heap.ArenaAllocator, | ||||||
| @ -431,7 +428,24 @@ pub const Effect = enum (u32) { | |||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const Rect = struct { | pub const Font = enum (u32) { | ||||||
|  | 	default, | ||||||
|  | 	_, | ||||||
|  | 
 | ||||||
|  | 	pub const Desc = struct { | ||||||
|  | 
 | ||||||
|  | 	}; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const Layer = enum { | ||||||
|  | 	background, | ||||||
|  | 	foreground, | ||||||
|  | 	overlay, | ||||||
|  | 
 | ||||||
|  | 	const values = std.enums.values(Layer); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const Rect = extern struct { | ||||||
| 	left: f32, | 	left: f32, | ||||||
| 	top: f32, | 	top: f32, | ||||||
| 	right: f32, | 	right: f32, | ||||||
| @ -474,58 +488,47 @@ pub const Texture = enum (u32) { | |||||||
| 	}; | 	}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const Transform2D = extern struct { | pub const Vector2 = @Vector(2, f32); | ||||||
| 	xbasis: Vector = .{1, 0}, |  | ||||||
| 	ybasis: Vector = .{0, 1}, |  | ||||||
| 	origin: Vector = @splat(0), |  | ||||||
| 
 | 
 | ||||||
| 	pub const Simplified = struct { | const Work = union (enum) { | ||||||
| 		translation: Vector = @splat(0), | 	load_effect: LoadEffect, | ||||||
| 		rotation: f32 = 0, | 	load_texture: LoadTexture, | ||||||
| 		scale: Vector = @splat(1), | 	render_frame: RenderFrame, | ||||||
| 		skew: f32 = 0, | 	shutdown, | ||||||
|  | 	unload_effect: UnloadEffect, | ||||||
|  | 	unload_texture: UnloadTexture, | ||||||
|  | 
 | ||||||
|  | 	const LoadEffect = struct { | ||||||
|  | 		desc: Effect.Desc, | ||||||
|  | 		loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect), | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	pub const Vector = @Vector(2, f32); | 	const LoadTexture = struct { | ||||||
|  | 		desc: Texture.Desc, | ||||||
|  | 		loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture), | ||||||
|  | 	}; | ||||||
| 
 | 
 | ||||||
| 	pub fn scaled(self: Transform2D, scale: Vector) Transform2D { | 	const RenderFrame = struct { | ||||||
| 		var transform = self; | 		clear_color: Color, | ||||||
| 		const scale_x, const scale_y = scale; | 		width: u16, | ||||||
|  | 		height: u16, | ||||||
|  | 		finished: *std.Thread.ResetEvent, | ||||||
|  | 		commands_set: CommandsSet, | ||||||
| 
 | 
 | ||||||
| 		transform.xbasis *= @splat(scale_x); | 		const CommandsSet = struct { | ||||||
|         transform.ybasis *= @splat(scale_y); | 			?*ona.Params(Commands(.background)).Node, | ||||||
| 
 | 			?*ona.Params(Commands(.foreground)).Node, | ||||||
| 		return transform; | 			?*ona.Params(Commands(.overlay)).Node, | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	pub fn transformed(self: Transform2D, other: Transform2D) Transform2D { |  | ||||||
| 		const xbasis_x, const xbasis_y = other.xbasis; |  | ||||||
| 		const ybasis_x, const ybasis_y = other.ybasis; |  | ||||||
| 		const origin_x, const origin_y = other.origin; |  | ||||||
| 
 |  | ||||||
| 		return .{ |  | ||||||
| 			.xbasis = |  | ||||||
| 				(self.xbasis * @as(Vector, @splat(xbasis_x))) + |  | ||||||
| 				(self.ybasis * @as(Vector, @splat(xbasis_y))), |  | ||||||
| 
 |  | ||||||
| 			.ybasis = |  | ||||||
| 				(self.xbasis * @as(Vector, @splat(ybasis_x))) + |  | ||||||
| 				(self.ybasis * @as(Vector, @splat(ybasis_y))), |  | ||||||
| 
 |  | ||||||
| 			.origin = |  | ||||||
| 				(self.xbasis * @as(Vector, @splat(origin_x))) + |  | ||||||
| 				(self.ybasis * @as(Vector, @splat(origin_y))) + |  | ||||||
| 				self.origin, |  | ||||||
| 		}; | 		}; | ||||||
| 	} | 	}; | ||||||
| 
 | 
 | ||||||
| 	pub fn translated(self: Transform2D, translation: Vector) Transform2D { | 	const UnloadEffect = struct { | ||||||
| 		var transform = self; | 		handle: Effect, | ||||||
|  | 	}; | ||||||
| 
 | 
 | ||||||
| 		transform.origin += translation; | 	const UnloadTexture = struct { | ||||||
| 
 | 		handle: Texture, | ||||||
| 		return transform; | 	}; | ||||||
| 	} |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub const colors = struct { | pub const colors = struct { | ||||||
| @ -546,6 +549,8 @@ pub const colors = struct { | |||||||
| 
 | 
 | ||||||
| 	pub const purple = rgb(0.5, 0, 0.5); | 	pub const purple = rgb(0.5, 0, 0.5); | ||||||
| 
 | 
 | ||||||
|  | 	pub const red = rgb(1, 0, 0); | ||||||
|  | 
 | ||||||
| 	pub fn rgb(r: f32, g: f32, b: f32) Color { | 	pub fn rgb(r: f32, g: f32, b: f32) Color { | ||||||
| 		return .{r, g, b, 1}; | 		return .{r, g, b, 1}; | ||||||
| 	} | 	} | ||||||
| @ -553,13 +558,13 @@ pub const colors = struct { | |||||||
| 	pub const white = greyscale(1); | 	pub const white = greyscale(1); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| pub fn poll(app: ona.Write(ona.App), events: ona.Send(hid.Event)) !void { | pub fn poll(loop: ona.Write(ona.Loop), events: ona.Send(hid.Event)) !void { | ||||||
| 	var event = @as(ext.SDL_Event, undefined); | 	var event = @as(ext.SDL_Event, undefined); | ||||||
| 
 | 
 | ||||||
| 	while (ext.SDL_PollEvent(&event) != 0) { | 	while (ext.SDL_PollEvent(&event) != 0) { | ||||||
| 		switch (event.type) { | 		switch (event.type) { | ||||||
| 			ext.SDL_QUIT => { | 			ext.SDL_QUIT => { | ||||||
| 				app.state.quit(); | 				loop.state.quit(); | ||||||
| 			}, | 			}, | ||||||
| 
 | 
 | ||||||
| 			ext.SDL_KEYUP => { | 			ext.SDL_KEYUP => { | ||||||
| @ -606,13 +611,15 @@ pub fn poll(app: ona.Write(ona.App), events: ona.Send(hid.Event)) !void { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || std.Thread.SpawnError || std.mem.Allocator.Error)!void { | pub fn setup(world: *ona.World) !void { | ||||||
| 	if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) { | 	if (ext.SDL_Init(ext.SDL_INIT_VIDEO | ext.SDL_INIT_EVENTS) != 0) { | ||||||
| 		return error.Unsupported; | 		return error.Unsupported; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	const display = try world.set_get_state(Display{}); | ||||||
|  | 
 | ||||||
| 	const assets = create: { | 	const assets = create: { | ||||||
| 		var assets = try Assets.init(); | 		var assets = try Assets.init(display.width, display.height); | ||||||
| 
 | 
 | ||||||
| 		errdefer { | 		errdefer { | ||||||
| 			assets.deinit(); | 			assets.deinit(); | ||||||
| @ -632,10 +639,9 @@ pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || | |||||||
| 		assets.window, | 		assets.window, | ||||||
| 	}); | 	}); | ||||||
| 
 | 
 | ||||||
| 	try world.set_state(Display{}); | 	try world.on_event(.pre_update, ona.system_fn(poll), .{.label = "poll gfx"}); | ||||||
| 	try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll gfx"}); | 	try world.on_event(.exit, ona.system_fn(stop), .{.label = "stop gfx"}); | ||||||
| 	try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop gfx"}); | 	try world.on_event(.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"}); | ||||||
| 	try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"}); |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn stop(assets: ona.Write(Assets)) void { | pub fn stop(assets: ona.Write(Assets)) void { | ||||||
| @ -648,8 +654,8 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void { | |||||||
| 	assets.frame_rendered.wait(); | 	assets.frame_rendered.wait(); | ||||||
| 	assets.frame_rendered.reset(); | 	assets.frame_rendered.reset(); | ||||||
| 
 | 
 | ||||||
| 	{ | 	inline for (Layer.values) |layer| { | ||||||
| 		var has_command_param = exclusive.world.get_params(Commands).has_head; | 		var has_command_param = exclusive.world.get_params(Commands(layer)).has_head; | ||||||
| 
 | 
 | ||||||
| 		while (has_command_param) |command_param| : (has_command_param = command_param.has_next) { | 		while (has_command_param) |command_param| : (has_command_param = command_param.has_next) { | ||||||
| 			command_param.param.rotate(); | 			command_param.param.rotate(); | ||||||
| @ -664,35 +670,27 @@ pub fn synchronize(exclusive: ona.Exclusive(&.{Assets, Display})) !void { | |||||||
| 		ext.SDL_SetWindowSize(assets.window, display.width, display.height); | 		ext.SDL_SetWindowSize(assets.window, display.width, display.height); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if (exclusive.world.get_params(Commands).has_head) |command_param| { | 	var commands_filled: usize = 0; | ||||||
|  | 	var commands_set = Work.RenderFrame.CommandsSet{null, null, null}; | ||||||
|  | 
 | ||||||
|  | 	inline for (0 .. Layer.values.len, Layer.values) |i, layer| { | ||||||
|  | 		if (exclusive.world.get_params(Commands(layer)).has_head) |command_param| { | ||||||
|  | 			commands_set[i] = command_param; | ||||||
|  | 			commands_filled += 1; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (commands_filled == 0) { | ||||||
|  | 		assets.frame_rendered.set(); | ||||||
|  | 	} else { | ||||||
| 		assets.pending_work.enqueue(.{ | 		assets.pending_work.enqueue(.{ | ||||||
| 			.render_frame = .{ | 			.render_frame = .{ | ||||||
| 				.has_command_params = command_param, | 				.commands_set = commands_set, | ||||||
| 				.width = display.width, | 				.width = display.width, | ||||||
| 				.height = display.height, | 				.height = display.height, | ||||||
| 				.clear_color = display.clear_color, | 				.clear_color = display.clear_color, | ||||||
| 				.finished = &assets.frame_rendered, | 				.finished = &assets.frame_rendered, | ||||||
| 			}, | 			}, | ||||||
| 		}); | 		}); | ||||||
| 	} else { |  | ||||||
| 		assets.frame_rendered.set(); |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 |  | ||||||
| pub fn transform_2d(simplified: Transform2D.Simplified) Transform2D { |  | ||||||
| 	const rotation_skew = simplified.rotation + simplified.skew; |  | ||||||
| 
 |  | ||||||
| 	return .{ |  | ||||||
| 		.xbasis = simplified.scale * Transform2D.Vector{ |  | ||||||
| 			std.math.cos(simplified.rotation), |  | ||||||
| 			std.math.sin(simplified.rotation), |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		.ybasis = simplified.scale * Transform2D.Vector{ |  | ||||||
| 			-std.math.sin(rotation_skew), |  | ||||||
| 			std.math.cos(rotation_skew), |  | ||||||
| 		}, |  | ||||||
| 
 |  | ||||||
| 		.origin = simplified.translation, |  | ||||||
| 	}; |  | ||||||
| } |  | ||||||
|  | |||||||
| @ -23,15 +23,23 @@ const Frame = struct { | |||||||
| 	current_source_texture: gfx.Texture = .default, | 	current_source_texture: gfx.Texture = .default, | ||||||
| 	current_target_texture: ?gfx.Texture = null, | 	current_target_texture: ?gfx.Texture = null, | ||||||
| 	current_effect: gfx.Effect = .default, | 	current_effect: gfx.Effect = .default, | ||||||
|  | 	transform_matrix: Matrix = identity_matrix, | ||||||
| 	width: u16 = 0, | 	width: u16 = 0, | ||||||
| 	height: u16 = 0, | 	height: u16 = 0, | ||||||
| 
 | 
 | ||||||
| 	const DrawTexture = extern struct { | 	const DrawTexture = extern struct { | ||||||
| 		transform: gfx.Transform2D, | 		rotation: f32, | ||||||
| 		tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), | 		depth: f32, | ||||||
| 		depth: f32 = 0, | 		position: gfx.Vector2, | ||||||
| 		texture_offset: @Vector(2, f32) = @splat(0), | 		size: gfx.Vector2, | ||||||
| 		texture_size: @Vector(2, f32) = @splat(1), | 		anchor: gfx.Vector2, | ||||||
|  | 		source_clip: gfx.Rect, | ||||||
|  | 		tint: gfx.Color, | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	const View = extern struct { | ||||||
|  | 		projection_matrix: Matrix, | ||||||
|  | 		transform_matrix: Matrix, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	const batches_per_buffer = 512; | 	const batches_per_buffer = 512; | ||||||
| @ -75,35 +83,13 @@ const Frame = struct { | |||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub fn draw_rect(self: *Frame, resources: *Resources, command: gfx.Commands.DrawRectCommand) !void { | 	pub fn draw_font(self: *Frame, resources: *Resources, command: gfx.Command.DrawFont) !void { | ||||||
| 		if (self.current_source_texture != .default) { | 		_ = self; | ||||||
| 			self.flush(resources); | 		_ = resources; | ||||||
| 		} | 		_ = command; | ||||||
| 
 |  | ||||||
| 		self.current_source_texture = .default; |  | ||||||
| 
 |  | ||||||
| 		const has_filled_current_buffer = (self.drawn_count % batches_per_buffer) == 0; |  | ||||||
| 		const buffer_count = self.drawn_count / batches_per_buffer; |  | ||||||
| 
 |  | ||||||
| 		if (has_filled_current_buffer and buffer_count == self.texture_batch_buffers.len()) { |  | ||||||
| 			const instance_buffer = sokol.gfx.makeBuffer(.{ |  | ||||||
| 				.size = @sizeOf(DrawTexture) * batches_per_buffer, |  | ||||||
| 				.usage = .STREAM, |  | ||||||
| 			}); |  | ||||||
| 
 |  | ||||||
| 			errdefer sokol.gfx.destroyBuffer(instance_buffer); |  | ||||||
| 
 |  | ||||||
| 			try self.texture_batch_buffers.push_grow(instance_buffer); |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{ |  | ||||||
| 			.transform = command.transform, |  | ||||||
| 		})); |  | ||||||
| 
 |  | ||||||
| 		self.drawn_count += 1; |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Commands.DrawTextureCommand) !void { | 	pub fn draw_texture(self: *Frame, resources: *Resources, command: gfx.Command.DrawTexture) !void { | ||||||
| 		if (self.current_source_texture != command.texture) { | 		if (self.current_source_texture != command.texture) { | ||||||
| 			self.flush(resources); | 			self.flush(resources); | ||||||
| 		} | 		} | ||||||
| @ -127,10 +113,13 @@ const Frame = struct { | |||||||
| 		const texture = resources.get_texture(command.texture).?; | 		const texture = resources.get_texture(command.texture).?; | ||||||
| 
 | 
 | ||||||
| 		_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{ | 		_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{ | ||||||
| 			.transform = command.transform.scaled(@floatFromInt(command.resolution orelse .{ | 			.size = command.size orelse .{@floatFromInt(texture.width), @floatFromInt(texture.height)}, | ||||||
| 				texture.width, | 			.anchor = command.anchor, | ||||||
| 				texture.height, | 			.rotation = command.rotation, | ||||||
| 			})), | 			.depth = command.depth, | ||||||
|  | 			.source_clip = command.source, | ||||||
|  | 			.tint = command.tint, | ||||||
|  | 			.position = command.position, | ||||||
| 		})); | 		})); | ||||||
| 
 | 
 | ||||||
| 		self.drawn_count += 1; | 		self.drawn_count += 1; | ||||||
| @ -178,19 +167,27 @@ const Frame = struct { | |||||||
| 		if (self.current_target_texture) |target_texture| { | 		if (self.current_target_texture) |target_texture| { | ||||||
| 			const texture = resources.get_texture(target_texture).?; | 			const texture = resources.get_texture(target_texture).?; | ||||||
| 
 | 
 | ||||||
| 			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{ | 			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{ | ||||||
| 				.left = 0, | 				.projection_matrix = orthographic_projection(-1.0, 1.0, .{ | ||||||
| 				.top = 0, | 					.left = 0, | ||||||
| 				.right = @floatFromInt(texture.width), | 					.top = 0, | ||||||
| 				.bottom = @floatFromInt(texture.height), | 					.right = @floatFromInt(texture.width), | ||||||
| 			}))); | 					.bottom = @floatFromInt(texture.height), | ||||||
|  | 				}), | ||||||
|  | 
 | ||||||
|  | 				.transform_matrix = self.transform_matrix, | ||||||
|  | 			})); | ||||||
| 		} else { | 		} else { | ||||||
| 			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{ | 			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{ | ||||||
| 				.left = 0, | 				.projection_matrix = orthographic_projection(-1.0, 1.0, .{ | ||||||
| 				.top = 0, | 					.left = 0, | ||||||
| 				.right = @floatFromInt(self.width), | 					.top = 0, | ||||||
| 				.bottom = @floatFromInt(self.height), | 					.right = @floatFromInt(self.width), | ||||||
| 			}))); | 					.bottom = @floatFromInt(self.height), | ||||||
|  | 				}), | ||||||
|  | 
 | ||||||
|  | 				.transform_matrix = self.transform_matrix, | ||||||
|  | 			})); | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		if (effect.properties.len != 0) { | 		if (effect.properties.len != 0) { | ||||||
| @ -215,7 +212,7 @@ const Frame = struct { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Commands.SetEffectCommand) void { | 	pub fn set_effect(self: *Frame, resources: *Resources, command: gfx.Command.SetEffect) void { | ||||||
| 		if (command.effect != self.current_effect) { | 		if (command.effect != self.current_effect) { | ||||||
| 			self.flush(resources); | 			self.flush(resources); | ||||||
| 		} | 		} | ||||||
| @ -227,7 +224,7 @@ const Frame = struct { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Commands.SetTargetCommand) void { | 	pub fn set_target(self: *Frame, resources: *Resources, command: gfx.Command.SetTarget) void { | ||||||
| 		sokol.gfx.endPass(); | 		sokol.gfx.endPass(); | ||||||
| 
 | 
 | ||||||
| 		var pass = sokol.gfx.Pass{ | 		var pass = sokol.gfx.Pass{ | ||||||
| @ -305,18 +302,23 @@ const Frame = struct { | |||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| fn Matrix(comptime n: usize, comptime Element: type) type { | const Matrix = [4]@Vector(4, f32); | ||||||
| 	return [n]@Vector(n, Element); |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| var default_sampler: sokol.gfx.Sampler = undefined; | var default_sampler: sokol.gfx.Sampler = undefined; | ||||||
| 
 | 
 | ||||||
|  | const identity_matrix = Matrix{ | ||||||
|  | 	.{1, 0, 0, 0}, | ||||||
|  | 	.{0, 1, 0, 0}, | ||||||
|  | 	.{0, 0, 1, 0}, | ||||||
|  | 	.{0, 0, 0, 1}, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| const vertex_indices = .{ | const vertex_indices = .{ | ||||||
| 	.mesh = 0, | 	.mesh = 0, | ||||||
| 	.instance = 1, | 	.instance = 1, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix(4, f32) { | fn orthographic_projection(near: f32, far: f32, viewport: gfx.Rect) Matrix { | ||||||
| 	const width = viewport.right - viewport.left; | 	const width = viewport.right - viewport.left; | ||||||
| 	const height = viewport.bottom - viewport.top; | 	const height = viewport.bottom - viewport.top; | ||||||
| 
 | 
 | ||||||
| @ -397,29 +399,31 @@ pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window | |||||||
| 				} | 				} | ||||||
| 			}, | 			}, | ||||||
| 
 | 
 | ||||||
| 			.render_frame => |render_frame| { | 			.render_frame => |*render_frame| { | ||||||
| 				frame.start(render_frame.width, render_frame.height); | 				frame.start(render_frame.width, render_frame.height); | ||||||
| 
 | 
 | ||||||
| 				var has_command_params = render_frame.has_command_params; | 				inline for (&render_frame.commands_set) |has_commands| { | ||||||
|  | 					var has_commands_currently = has_commands; | ||||||
| 
 | 
 | ||||||
| 				while (has_command_params) |command_params| : (has_command_params = command_params.has_next) { | 					while (has_commands_currently) |commands| : (has_commands_currently = commands.has_next) { | ||||||
| 					for (command_params.param.submitted_commands()) |command| { | 						for (commands.param.submitted_commands()) |command| { | ||||||
| 						try switch (command) { | 							try switch (command) { | ||||||
| 							.draw_rect => |draw_rect| frame.draw_rect(&resources, draw_rect), | 								.draw_font => |draw_font| frame.draw_font(&resources, draw_font), | ||||||
| 							.draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture), | 								.draw_texture => |draw_texture| frame.draw_texture(&resources, draw_texture), | ||||||
| 							.set_effect => |set_effect| frame.set_effect(&resources, set_effect), | 								.set_effect => |set_effect| frame.set_effect(&resources, set_effect), | ||||||
| 							.set_target => |set_target| frame.set_target(&resources, set_target), | 								.set_target => |set_target| frame.set_target(&resources, set_target), | ||||||
| 						}; | 							}; | ||||||
| 					} | 						} | ||||||
| 
 | 
 | ||||||
| 					frame.flush(&resources); | 						frame.flush(&resources); | ||||||
| 
 | 
 | ||||||
| 					if (frame.current_target_texture != null) { | 						if (frame.current_target_texture != null) { | ||||||
| 						frame.set_target(&resources, .{ | 							frame.set_target(&resources, .{ | ||||||
| 							.clear_color = null, | 								.clear_color = null, | ||||||
| 							.clear_depth = null, | 								.clear_depth = null, | ||||||
| 							.clear_stencil = null, | 								.clear_stencil = null, | ||||||
| 						}); | 							}); | ||||||
|  | 						} | ||||||
| 					} | 					} | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,29 +0,0 @@ | |||||||
| #version 430 |  | ||||||
| 
 |  | ||||||
| layout (location = 0) in vec2 mesh_xy; |  | ||||||
| layout (location = 1) in vec2 mesh_uv; |  | ||||||
| 
 |  | ||||||
| layout (location = 2) in vec2 instance_xbasis; |  | ||||||
| layout (location = 3) in vec2 instance_ybasis; |  | ||||||
| layout (location = 4) in vec2 instance_origin; |  | ||||||
| layout (location = 5) in vec4 instance_tint; |  | ||||||
| layout (location = 6) in float instance_depth; |  | ||||||
| layout (location = 7) in vec4 instance_clip; |  | ||||||
| 
 |  | ||||||
| layout (location = 0) out vec4 color; |  | ||||||
| layout (location = 1) out vec2 uv; |  | ||||||
| 
 |  | ||||||
| layout (binding = 0) uniform View { |  | ||||||
| 	mat4 projection_matrix; |  | ||||||
| }; |  | ||||||
| 
 |  | ||||||
| void main() { |  | ||||||
| 	const vec2 world_position = instance_origin + mesh_xy.x * instance_xbasis + mesh_xy.y * instance_ybasis; |  | ||||||
| 	const vec2 projected_position = (projection_matrix * vec4(world_position, 0, 1)).xy; |  | ||||||
| 	const vec2 rect_size = instance_clip.zw - instance_clip.xy; |  | ||||||
| 	const vec4 screen_position = vec4(projected_position, instance_depth, 1.0); |  | ||||||
| 
 |  | ||||||
| 	gl_Position = screen_position; |  | ||||||
| 	color = instance_tint; |  | ||||||
| 	uv = instance_clip.xy + (mesh_uv * rect_size); |  | ||||||
| } |  | ||||||
							
								
								
									
										42
									
								
								src/gfx/shaders/draw_texture.vert
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/gfx/shaders/draw_texture.vert
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | |||||||
|  | #version 430 | ||||||
|  | 
 | ||||||
|  | layout (location = 0) in vec2 batch_xy; | ||||||
|  | layout (location = 1) in vec2 batch_uv; | ||||||
|  | 
 | ||||||
|  | layout (location = 2) in float instance_rotation; | ||||||
|  | layout (location = 3) in float instance_depth; | ||||||
|  | layout (location = 4) in vec2 instance_position; | ||||||
|  | layout (location = 5) in vec2 instance_scale; | ||||||
|  | layout (location = 6) in vec2 instance_anchor; | ||||||
|  | layout (location = 7) in vec4 instance_clip; | ||||||
|  | layout (location = 8) in vec4 instance_tint; | ||||||
|  | 
 | ||||||
|  | layout (location = 0) out vec4 color; | ||||||
|  | layout (location = 1) out vec2 uv; | ||||||
|  | 
 | ||||||
|  | layout (binding = 0) uniform View { | ||||||
|  | 	mat4 projection_matrix; | ||||||
|  | 	mat4 transform_matrix; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | void main() { | ||||||
|  | 	const vec2 normalized_position = (batch_xy + 0.5) - instance_anchor; | ||||||
|  | 	const float cos_theta = cos(instance_rotation); | ||||||
|  | 	const float sin_theta = sin(instance_rotation); | ||||||
|  | 
 | ||||||
|  | 	const mat2 rotation_matrix = mat2( | ||||||
|  | 		cos_theta, -sin_theta, | ||||||
|  | 		sin_theta,  cos_theta | ||||||
|  | 	); | ||||||
|  | 
 | ||||||
|  | 	const vec2 rotated_position = rotation_matrix * (normalized_position * instance_scale); | ||||||
|  | 	const vec2 world_position = instance_position + rotated_position; | ||||||
|  | 	const vec4 clip_space_position = projection_matrix * transform_matrix * vec4(world_position, 0.0, 1.0); | ||||||
|  | 
 | ||||||
|  | 	gl_Position = vec4(clip_space_position.xy, instance_depth, 1.0); | ||||||
|  | 
 | ||||||
|  | 	const vec2 clip_size = instance_clip.zw - instance_clip.xy; | ||||||
|  | 
 | ||||||
|  | 	uv = instance_clip.xy + (batch_uv * clip_size); | ||||||
|  | 	color = instance_tint; | ||||||
|  | } | ||||||
| @ -232,9 +232,7 @@ pub const Stage = struct { | |||||||
| 						else => error.UnsupportedSPIRV, | 						else => error.UnsupportedSPIRV, | ||||||
| 					}, | 					}, | ||||||
| 
 | 
 | ||||||
| 					.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse { | 					.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse 0, | ||||||
| 						return error.UnsupportedSPIRV; |  | ||||||
| 					}, |  | ||||||
| 				}; | 				}; | ||||||
| 			} | 			} | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										234
									
								
								src/gui/gui.zig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										234
									
								
								src/gui/gui.zig
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,234 @@ | |||||||
|  | const gfx = @import("gfx"); | ||||||
|  | 
 | ||||||
|  | const ona = @import("ona"); | ||||||
|  | 
 | ||||||
|  | const std = @import("std"); | ||||||
|  | 
 | ||||||
|  | pub const Box = struct { | ||||||
|  | 	left: f32, | ||||||
|  | 	top: f32, | ||||||
|  | 	width: f32, | ||||||
|  | 	height: f32, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const Canvas = struct { | ||||||
|  | 	nodes: ona.stack.Parallel(Node) = .{}, | ||||||
|  | 	drawable_blocks: ona.stack.Sequential(DrawableBlock) = .{}, | ||||||
|  | 	drawable_labels: ona.stack.Sequential(DrawableLabel) = .{}, | ||||||
|  | 
 | ||||||
|  | 	fn Drawable(comptime State: type) type { | ||||||
|  | 		return struct { | ||||||
|  | 			state: State, | ||||||
|  | 			depth: f32, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const DrawableBlock = Drawable(struct { | ||||||
|  | 		background: gfx.Texture = .default, | ||||||
|  | 		color: gfx.Color = gfx.colors.white, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	const DrawableLabel = Drawable(struct { | ||||||
|  | 		text: ona.stack.Sequential(u8) = .{}, | ||||||
|  | 		color: gfx.Color = gfx.colors.black, | ||||||
|  | 		font: gfx.Font = .default, | ||||||
|  | 	}); | ||||||
|  | 
 | ||||||
|  | 	const Node = struct { | ||||||
|  | 		name: []u8, | ||||||
|  | 		box: Box, | ||||||
|  | 		parent_index: u32 = unused_index, | ||||||
|  | 		block_index: u32 = unused_index, | ||||||
|  | 		label_index: u32 = unused_index, | ||||||
|  | 		style_index: u32 = unused_index, | ||||||
|  | 
 | ||||||
|  | 		const unused_index = std.math.maxInt(u32); | ||||||
|  | 	}; | ||||||
|  | 
 | ||||||
|  | 	pub fn append(self: *Canvas, name: []const u8, box: Box) std.mem.Allocator.Error!Item { | ||||||
|  | 		const duped_name = try ona.heap.allocator.dupe(u8, name); | ||||||
|  | 
 | ||||||
|  | 		errdefer { | ||||||
|  | 			ona.heap.allocator.free(duped_name); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		const index = self.nodes.len(); | ||||||
|  | 
 | ||||||
|  | 		try self.nodes.push_grow(.{ | ||||||
|  | 			.name = duped_name, | ||||||
|  | 			.box = box, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		errdefer { | ||||||
|  | 			std.debug.assert(self.nodes.pop()); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		const node_count = self.nodes.len(); | ||||||
|  | 
 | ||||||
|  | 		try self.drawable_blocks.grow(ona.scalars.sub(node_count, self.drawable_blocks.cap) orelse 0); | ||||||
|  | 		try self.drawable_labels.grow(ona.scalars.sub(node_count, self.drawable_labels.cap) orelse 0); | ||||||
|  | 
 | ||||||
|  | 		return @enumFromInt(index); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fn calculate_depth(self: Canvas, item: Item) f32 { | ||||||
|  | 		const parent_indices = self.nodes.values.slice(.parent_index); | ||||||
|  | 		var depth: f32 = 0; | ||||||
|  | 		var index = @intFromEnum(item); | ||||||
|  | 
 | ||||||
|  | 		while (true) : (depth += 1) { | ||||||
|  | 			const parent_index = parent_indices[index]; | ||||||
|  | 
 | ||||||
|  | 			if (parent_index == Node.unused_index) { | ||||||
|  | 				break; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			index = parent_index; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return depth; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	fn deinit(self: *Canvas) void { | ||||||
|  | 		self.nodes.deinit(); | ||||||
|  | 		self.drawable_blocks.deinit(); | ||||||
|  | 
 | ||||||
|  | 		for (self.drawable_labels.values) |*drawable_label| { | ||||||
|  | 			drawable_label.state.text.deinit(); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		self.drawable_labels.deinit(); | ||||||
|  | 
 | ||||||
|  | 		self.* = undefined; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	const label_depth_offset = 0.5; | ||||||
|  | 
 | ||||||
|  | 	pub fn set_block(self: *Canvas, item: Item, set: SetBlock) void { | ||||||
|  | 		const block_index = self.nodes.values.get(.block_index, @intFromEnum(item)).?; | ||||||
|  | 
 | ||||||
|  | 		if (block_index.* == Node.unused_index) { | ||||||
|  | 			block_index.* = @intCast(self.drawable_blocks.len()); | ||||||
|  | 
 | ||||||
|  | 			std.debug.assert(self.drawable_blocks.push(.{ | ||||||
|  | 				.depth = self.calculate_depth(item), | ||||||
|  | 				.state = .{}, | ||||||
|  | 			})); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		const block = &self.drawable_blocks.values[block_index.*]; | ||||||
|  | 
 | ||||||
|  | 		if (set.has_background) |background| { | ||||||
|  | 			block.state.background = background; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (set.has_color) |color| { | ||||||
|  | 			block.state.color = color; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn set_label(self: *Canvas, item: Item, set: SetLabel) std.mem.Allocator.Error!void { | ||||||
|  | 		const label_index = self.nodes.values.get(.label_index, @intFromEnum(item)).?; | ||||||
|  | 
 | ||||||
|  | 		if (label_index.* == Node.unused_index) { | ||||||
|  | 			label_index.* = @intCast(self.drawable_labels.len()); | ||||||
|  | 
 | ||||||
|  | 			std.debug.assert(self.drawable_labels.push(.{ | ||||||
|  | 				.depth = self.calculate_depth(item) + label_depth_offset, | ||||||
|  | 				.state = .{}, | ||||||
|  | 			})); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		const label = &self.drawable_labels.values[label_index.*]; | ||||||
|  | 
 | ||||||
|  | 		if (set.has_text) |text| { | ||||||
|  | 			label.state.text.pop_all(); | ||||||
|  | 
 | ||||||
|  | 			try label.state.text.grow(ona.scalars.sub(text.len, label.state.text.cap) orelse 0); | ||||||
|  | 
 | ||||||
|  | 			std.debug.assert(label.state.text.push_all(text)); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (set.has_font) |font| { | ||||||
|  | 			label.state.font = font; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if (set.has_color) |color| { | ||||||
|  | 			label.state.color = color; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn set_parent(self: *Canvas, item: Item, has_parent: ?Item) void { | ||||||
|  | 		const item_index = @intFromEnum(item); | ||||||
|  | 		const parent_index = self.nodes.values.get(.parent_index, item_index).?; | ||||||
|  | 
 | ||||||
|  | 		parent_index.* = if (has_parent) |parent| @intFromEnum(parent) else Node.unused_index; | ||||||
|  | 
 | ||||||
|  | 		const label_index = self.nodes.values.get(.parent_index, item_index).?.*; | ||||||
|  | 		const block_index = self.nodes.values.get(.parent_index, item_index).?.*; | ||||||
|  | 		const label_index_exists = label_index != Node.unused_index; | ||||||
|  | 		const block_index_exists = block_index != Node.unused_index; | ||||||
|  | 
 | ||||||
|  | 		if (label_index_exists or block_index_exists) { | ||||||
|  | 			const depth = self.calculate_depth(item); | ||||||
|  | 
 | ||||||
|  | 			if (label_index_exists) { | ||||||
|  | 				self.drawable_labels[label_index].depth = depth + label_depth_offset; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			if (block_index_exists) { | ||||||
|  | 				self.drawable_labels[label_index].depth = depth; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const Item = enum (u32) { | ||||||
|  | 	_, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SetBlock = struct { | ||||||
|  | 	has_background: ?gfx.Texture = null, | ||||||
|  | 	has_color: ?gfx.Color = null, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const SetLabel = struct { | ||||||
|  | 	has_text: ?[]const u8 = null, | ||||||
|  | 	has_color: ?gfx.Color = null, | ||||||
|  | 	has_font: ?gfx.Font = null, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub fn exit(canvas: ona.Write(Canvas)) !void { | ||||||
|  | 	canvas.state.deinit(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn render(commands: gfx.Commands(.overlay), canvas: ona.Read(Canvas)) !void { | ||||||
|  | 	const boxes = canvas.state.nodes.values.slice(.box); | ||||||
|  | 
 | ||||||
|  | 	for (canvas.state.drawable_blocks.values, boxes) |block, box| { | ||||||
|  | 		try commands.draw_texture(.{ | ||||||
|  | 			.depth = block.depth, | ||||||
|  | 			.position = .{box.left, box.top}, | ||||||
|  | 			.size = .{box.width, box.height}, | ||||||
|  | 			.texture = block.state.background, | ||||||
|  | 			.tint = block.state.color, | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for (canvas.state.drawable_labels.values, boxes) |label, box| { | ||||||
|  | 		try commands.draw_font(.{ | ||||||
|  | 			.depth = label.depth, | ||||||
|  | 			.text = label.state.text.values, | ||||||
|  | 			.position = .{box.left, box.top}, | ||||||
|  | 			.size = .{box.width, box.height}, | ||||||
|  | 			.tint = label.state.color, | ||||||
|  | 			.font = label.state.font, | ||||||
|  | 		}); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | pub fn setup(world: *ona.World) !void { | ||||||
|  | 	try world.set_state(Canvas{}); | ||||||
|  | 	try world.on_event(.render, ona.system_fn(render), .{.label = "render gui"}); | ||||||
|  | 	try world.on_event(.exit, ona.system_fn(exit), .{.label = "cleanup gui"}); | ||||||
|  | } | ||||||
| @ -293,10 +293,10 @@ test "mapping values" { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn setup(world: *ona.World, events: ona.App.Events) std.mem.Allocator.Error!void { | pub fn setup(world: *ona.World) std.mem.Allocator.Error!void { | ||||||
| 	try world.set_state(Mapping{}); | 	try world.set_state(Mapping{}); | ||||||
| 
 | 
 | ||||||
| 	try world.on_event(events.pre_update, ona.system_fn(update), .{ | 	try world.on_event(.pre_update, ona.system_fn(update), .{ | ||||||
| 		.label = "update actions", | 		.label = "update actions", | ||||||
| 	}); | 	}); | ||||||
| } | } | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ const std = @import("std"); | |||||||
| 
 | 
 | ||||||
| thread_pool: ?*std.Thread.Pool = null, | thread_pool: ?*std.Thread.Pool = null, | ||||||
| thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable, | thread_restricted_resources: [std.enums.values(ona.ThreadRestriction).len]StateTable, | ||||||
| event_systems: ona.stack.Sequential(Schedule), | event_schedules: ona.map.Hashed([]const u8, *Schedule, ona.map.string_traits), | ||||||
| 
 | 
 | ||||||
| pub const BindContext = struct { | pub const BindContext = struct { | ||||||
| 	node: ona.dag.Node, | 	node: ona.dag.Node, | ||||||
| @ -110,8 +110,6 @@ const Graph = ona.dag.Graph(struct { | |||||||
| 	resource_accesses: ona.stack.Sequential(StateAccess), | 	resource_accesses: ona.stack.Sequential(StateAccess), | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| pub const Event = enum (usize) { _ }; |  | ||||||
| 
 |  | ||||||
| const ParallelNodeBundles = ona.stack.Sequential(NodeBundle); | const ParallelNodeBundles = ona.stack.Sequential(NodeBundle); | ||||||
| 
 | 
 | ||||||
| const NodeBundle = ona.stack.Sequential(ona.dag.Node); | const NodeBundle = ona.stack.Sequential(ona.dag.Node); | ||||||
| @ -442,22 +440,16 @@ const Schedule = struct { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	pub fn init(label: []const u8) std.mem.Allocator.Error!Schedule { | 	pub fn init(comptime label: [:0]const u8) std.mem.Allocator.Error!Schedule { | ||||||
| 		var arena = std.heap.ArenaAllocator.init(ona.heap.allocator); |  | ||||||
| 
 |  | ||||||
| 		errdefer arena.deinit(); |  | ||||||
| 
 |  | ||||||
| 		const duped_label = try arena.allocator().dupeZ(u8, label); |  | ||||||
| 
 |  | ||||||
| 		return .{ | 		return .{ | ||||||
| 			.graph = Graph.init(ona.heap.allocator), | 			.graph = Graph.init(ona.heap.allocator), | ||||||
| 			.label = duped_label, | 			.arena = std.heap.ArenaAllocator.init(ona.heap.allocator), | ||||||
| 			.arena = arena, | 			.label = label, | ||||||
| 			.system_id_nodes = .{.allocator = ona.heap.allocator}, | 			.system_id_nodes = .{}, | ||||||
| 			.read_write_resource_id_nodes = .{.allocator = ona.heap.allocator}, | 			.read_write_resource_id_nodes = .{}, | ||||||
| 			.read_only_resource_id_nodes = .{.allocator = ona.heap.allocator}, | 			.read_only_resource_id_nodes = .{}, | ||||||
| 			.parallel_work_bundles = .{.allocator = ona.heap.allocator}, | 			.parallel_work_bundles = .{}, | ||||||
| 			.blocking_work = .{.allocator = ona.heap.allocator}, | 			.blocking_work = .{}, | ||||||
| 		}; | 		}; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -544,21 +536,12 @@ pub const UnbindContext = struct { | |||||||
| 	world: *Self, | 	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 { | pub fn deinit(self: *Self) void { | ||||||
| 	for (self.event_systems.values) |*schedule| { | 	var event_schedules = self.event_schedules.entries(); | ||||||
| 		schedule.deinit(self); | 
 | ||||||
|  | 	while (event_schedules.next()) |schedule| { | ||||||
|  | 		schedule.value.deinit(self); | ||||||
|  | 		ona.heap.allocator.destroy(schedule.value); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	for (&self.thread_restricted_resources) |*resources| { | 	for (&self.thread_restricted_resources) |*resources| { | ||||||
| @ -570,7 +553,7 @@ pub fn deinit(self: *Self) void { | |||||||
| 		ona.heap.allocator.destroy(thread_pool); | 		ona.heap.allocator.destroy(thread_pool); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	self.event_systems.deinit(); | 	self.event_schedules.deinit(); | ||||||
| 
 | 
 | ||||||
| 	self.* = undefined; | 	self.* = undefined; | ||||||
| } | } | ||||||
| @ -594,7 +577,7 @@ pub fn set_get_state(self: *Self, value: anytype) std.mem.Allocator.Error!*@Type | |||||||
| pub fn init(thread_count: u32) std.Thread.SpawnError!Self { | pub fn init(thread_count: u32) std.Thread.SpawnError!Self { | ||||||
| 	var world = Self{ | 	var world = Self{ | ||||||
| 		.thread_restricted_resources = .{StateTable.init(), StateTable.init()}, | 		.thread_restricted_resources = .{StateTable.init(), StateTable.init()}, | ||||||
| 		.event_systems = .{.allocator = ona.heap.allocator}, | 		.event_schedules = .{.allocator = ona.heap.allocator}, | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	if (thread_count != 0 and !builtin.single_threaded) { | 	if (thread_count != 0 and !builtin.single_threaded) { | ||||||
| @ -611,12 +594,34 @@ pub fn init(thread_count: u32) std.Thread.SpawnError!Self { | |||||||
| 	return world; | 	return world; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn on_event(self: *Self, event: Event, action: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void { | pub fn on_event(self: *Self, comptime event: anytype, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void { | ||||||
| 	try self.event_systems.values[@intFromEnum(event)].insert(action, order); | 	const event_name = @tagName(event); | ||||||
|  | 
 | ||||||
|  | 	if (self.event_schedules.get(event_name)) |schedule| { | ||||||
|  | 		try schedule.*.insert(info, order); | ||||||
|  | 	} else { | ||||||
|  | 		const schedule = try ona.heap.allocator.create(Schedule); | ||||||
|  | 
 | ||||||
|  | 		errdefer { | ||||||
|  | 			ona.heap.allocator.destroy(schedule); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		schedule.* = try Schedule.init(event_name); | ||||||
|  | 
 | ||||||
|  | 		errdefer { | ||||||
|  | 			schedule.deinit(self); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		try schedule.insert(info, order); | ||||||
|  | 
 | ||||||
|  | 		std.debug.assert(try self.event_schedules.emplace(event_name, schedule)); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn run_event(self: *Self, event: Event) anyerror!void { | pub fn run_event(self: *Self, comptime event: anytype) anyerror!void { | ||||||
| 	try self.event_systems.values[@intFromEnum(event)].run(self); | 	if (self.event_schedules.get(@tagName(event))) |schedule| { | ||||||
|  | 		try schedule.*.run(self); | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void { | pub fn set_state(self: *Self, value: anytype) std.mem.Allocator.Error!void { | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( | |||||||
| 	const max_int = std.math.maxInt(usize); | 	const max_int = std.math.maxInt(usize); | ||||||
| 
 | 
 | ||||||
| 	return struct { | 	return struct { | ||||||
| 		allocator: std.mem.Allocator, | 		allocator: std.mem.Allocator = ona.heap.allocator, | ||||||
| 		entry_map: []?Entry = &.{}, | 		entry_map: []?Entry = &.{}, | ||||||
| 		len: usize = 0, | 		len: usize = 0, | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										332
									
								
								src/ona/ona.zig
									
									
									
									
									
								
							
							
						
						
									
										332
									
								
								src/ona/ona.zig
									
									
									
									
									
								
							| @ -29,185 +29,146 @@ pub const World = @import("./World.zig"); | |||||||
| pub const lina = @import("./lina.zig"); | pub const lina = @import("./lina.zig"); | ||||||
| 
 | 
 | ||||||
| pub const App = struct { | pub const App = struct { | ||||||
| 	events: *const Events, | 	step: fn (*World) anyerror!void = noop, | ||||||
|  | 
 | ||||||
|  | 	fn noop(_: *World) !void {} | ||||||
|  | 
 | ||||||
|  | 	pub fn build(self: App) fn () anyerror!void { | ||||||
|  | 		const Start = struct { | ||||||
|  | 			fn main() anyerror!void { | ||||||
|  | 				defer { | ||||||
|  | 					heap.trace_leaks(); | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				parse_args: for (std.os.argv[1 ..]) |arg| { | ||||||
|  | 					const arg_span = std.mem.span(arg); | ||||||
|  | 					const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len; | ||||||
|  | 					const arg_name = arg_span[0 .. arg_split_index]; | ||||||
|  | 
 | ||||||
|  | 					for (LaunchFlag.values) |value| { | ||||||
|  | 						const name = @tagName(value); | ||||||
|  | 
 | ||||||
|  | 						if (!std.mem.eql(u8, arg_name, name)) { | ||||||
|  | 							continue; | ||||||
|  | 						} | ||||||
|  | 
 | ||||||
|  | 						LaunchFlag.args[@intFromEnum(value)] = | ||||||
|  | 							if (arg_split_index == arg_span.len) | ||||||
|  | 								name | ||||||
|  | 							else | ||||||
|  | 								arg_span[arg_split_index ..]; | ||||||
|  | 
 | ||||||
|  | 						continue: parse_args; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 
 | ||||||
|  | 				const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| { | ||||||
|  | 					@panic(switch (cpu_count_error) { | ||||||
|  | 						error.PermissionDenied => "permission denied retrieving CPU count", | ||||||
|  | 						error.SystemResources => "system resources are preventing retrieval of the CPU count", | ||||||
|  | 						error.Unexpected => "unexpected failure retrieving CPU count", | ||||||
|  | 					}); | ||||||
|  | 				}, 0, std.math.maxInt(u32)))); | ||||||
|  | 
 | ||||||
|  | 				var world = try World.init(scalars.fractional(cpu_count, @as(f32, 0.75)) orelse 0); | ||||||
|  | 
 | ||||||
|  | 				defer world.deinit(); | ||||||
|  | 
 | ||||||
|  | 				try self.step(&world); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		return Start.main; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub const game = App{.step = run_game}; | ||||||
|  | 
 | ||||||
|  | 	fn run_game(world: *World) !void { | ||||||
|  | 		const loop = try world.set_get_state(Loop{ | ||||||
|  | 			.target_frame_time = 1.0 / 60.0, | ||||||
|  | 			.elapsed_time = 0, | ||||||
|  | 			.is_running = true, | ||||||
|  | 		}); | ||||||
|  | 
 | ||||||
|  | 		try world.run_event(.load); | ||||||
|  | 
 | ||||||
|  | 		const ticks_initial = std.time.milliTimestamp(); | ||||||
|  | 		var ticks_previous = ticks_initial; | ||||||
|  | 		var accumulated_time = @as(f64, 0); | ||||||
|  | 
 | ||||||
|  | 		while (loop.is_running) { | ||||||
|  | 			const ticks_current = std.time.milliTimestamp(); | ||||||
|  | 			const milliseconds_per_second = 1000.0; | ||||||
|  | 			const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; | ||||||
|  | 
 | ||||||
|  | 			loop.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second; | ||||||
|  | 			ticks_previous = ticks_current; | ||||||
|  | 			accumulated_time += delta_time; | ||||||
|  | 
 | ||||||
|  | 			try world.run_event(.pre_update); | ||||||
|  | 
 | ||||||
|  | 			while (accumulated_time >= loop.target_frame_time) : (accumulated_time -= loop.target_frame_time) { | ||||||
|  | 				try world.run_event(.update); | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			try world.run_event(.post_update); | ||||||
|  | 			try world.run_event(.render); | ||||||
|  | 			try world.run_event(.finish); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		try world.run_event(.exit); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn with_module(self: App, comptime Module: type) App { | ||||||
|  | 		if (!@hasDecl(Module, "setup")) { | ||||||
|  | 			@compileError("`Module` argument must have a setup fn declaration"); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		const WithState = struct { | ||||||
|  | 			fn step(world: *World) !void { | ||||||
|  | 				try Module.setup(world); | ||||||
|  | 				try self.step(world); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		return .{ | ||||||
|  | 			.step = WithState.step, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn with_state(self: App, comptime state: anytype) App { | ||||||
|  | 		const WithState = struct { | ||||||
|  | 			fn step(world: *World) !void { | ||||||
|  | 				try world.set_state(state); | ||||||
|  | 				try self.step(world); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		return .{ | ||||||
|  | 			.step = WithState.step, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pub fn with_system(self: App, comptime event: anytype, comptime info: *const SystemInfo, comptime order: SystemOrder) App { | ||||||
|  | 		const WithState = struct { | ||||||
|  | 			fn step(world: *World) !void { | ||||||
|  | 				try world.on_event(event, info, order); | ||||||
|  | 				try self.step(world); | ||||||
|  | 			} | ||||||
|  | 		}; | ||||||
|  | 
 | ||||||
|  | 		return .{ | ||||||
|  | 			.step = WithState.step, | ||||||
|  | 		}; | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | pub const Loop = struct { | ||||||
| 	target_frame_time: f64, | 	target_frame_time: f64, | ||||||
| 	elapsed_time: f64, | 	elapsed_time: f64, | ||||||
| 	is_running: bool, | 	is_running: bool, | ||||||
| 
 | 
 | ||||||
| 	pub const Events = struct { | 	pub fn quit(self: *Loop) void { | ||||||
| 		load: World.Event, |  | ||||||
| 		pre_update: World.Event, |  | ||||||
| 		update: World.Event, |  | ||||||
| 		post_update: World.Event, |  | ||||||
| 		render: World.Event, |  | ||||||
| 		finish: World.Event, |  | ||||||
| 		exit: World.Event, |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	pub const Setup = struct { |  | ||||||
| 		pub const Event = enum { |  | ||||||
| 			load, |  | ||||||
| 			pre_update, |  | ||||||
| 			update, |  | ||||||
| 			post_update, |  | ||||||
| 			render, |  | ||||||
| 			finish, |  | ||||||
| 			exit, |  | ||||||
| 		}; |  | ||||||
| 
 |  | ||||||
| 		step: fn (*World, Events) anyerror!void = noop, |  | ||||||
| 
 |  | ||||||
| 		fn noop(_: *World, _: Events) !void {} |  | ||||||
| 
 |  | ||||||
| 		pub fn build(self: Setup) fn () anyerror!void { |  | ||||||
| 			const Start = struct { |  | ||||||
| 				fn main() anyerror!void { |  | ||||||
| 					defer { |  | ||||||
| 						heap.trace_leaks(); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					parse_args: for (std.os.argv[1 ..]) |arg| { |  | ||||||
| 						const arg_span = std.mem.span(arg); |  | ||||||
| 						const arg_split_index = std.mem.indexOfScalar(u8, arg_span, '=') orelse arg_span.len; |  | ||||||
| 						const arg_name = arg_span[0 .. arg_split_index]; |  | ||||||
| 
 |  | ||||||
| 						for (LaunchFlag.values) |value| { |  | ||||||
| 							const name = @tagName(value); |  | ||||||
| 
 |  | ||||||
| 							if (!std.mem.eql(u8, arg_name, name)) { |  | ||||||
| 								continue; |  | ||||||
| 							} |  | ||||||
| 
 |  | ||||||
| 							LaunchFlag.args[@intFromEnum(value)] = |  | ||||||
| 								if (arg_split_index == arg_span.len) |  | ||||||
| 									name |  | ||||||
| 								else |  | ||||||
| 									arg_span[arg_split_index ..]; |  | ||||||
| 
 |  | ||||||
| 							continue: parse_args; |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					const cpu_count = @as(u32, @intCast(std.math.clamp(std.Thread.getCpuCount() catch |cpu_count_error| { |  | ||||||
| 						@panic(switch (cpu_count_error) { |  | ||||||
| 							error.PermissionDenied => "permission denied retrieving CPU count", |  | ||||||
| 							error.SystemResources => "system resources are preventing retrieval of the CPU count", |  | ||||||
| 							error.Unexpected => "unexpected failure retrieving CPU count", |  | ||||||
| 						}); |  | ||||||
| 					}, 0, std.math.maxInt(u32)))); |  | ||||||
| 
 |  | ||||||
| 					var world = try World.init(scalars.fractional(cpu_count, @as(f32, 0.75)) orelse 0); |  | ||||||
| 
 |  | ||||||
| 					defer world.deinit(); |  | ||||||
| 
 |  | ||||||
| 					const events = App.Events{ |  | ||||||
| 						.load = try world.create_event("load"), |  | ||||||
| 						.pre_update = try world.create_event("pre-update"), |  | ||||||
| 						.update = try world.create_event("update"), |  | ||||||
| 						.post_update = try world.create_event("post-update"), |  | ||||||
| 						.render = try world.create_event("render"), |  | ||||||
| 						.finish = try world.create_event("finish"), |  | ||||||
| 						.exit = try world.create_event("exit"), |  | ||||||
| 					}; |  | ||||||
| 
 |  | ||||||
| 					const app = try world.set_get_state(App{ |  | ||||||
| 						.events = &events, |  | ||||||
| 						.target_frame_time = 1.0 / 60.0, |  | ||||||
| 						.elapsed_time = 0, |  | ||||||
| 						.is_running = true, |  | ||||||
| 					}); |  | ||||||
| 
 |  | ||||||
| 					try self.step(&world, events); |  | ||||||
| 					try world.run_event(events.load); |  | ||||||
| 
 |  | ||||||
| 					const ticks_initial = std.time.milliTimestamp(); |  | ||||||
| 					var ticks_previous = ticks_initial; |  | ||||||
| 					var accumulated_time = @as(f64, 0); |  | ||||||
| 
 |  | ||||||
| 					while (app.is_running) { |  | ||||||
| 						const ticks_current = std.time.milliTimestamp(); |  | ||||||
| 						const milliseconds_per_second = 1000.0; |  | ||||||
| 						const delta_time = @as(f64, @floatFromInt(ticks_current - ticks_previous)) / milliseconds_per_second; |  | ||||||
| 
 |  | ||||||
| 						app.elapsed_time = @as(f64, @floatFromInt(ticks_current - ticks_initial)) / milliseconds_per_second; |  | ||||||
| 						ticks_previous = ticks_current; |  | ||||||
| 						accumulated_time += delta_time; |  | ||||||
| 
 |  | ||||||
| 						try world.run_event(events.pre_update); |  | ||||||
| 
 |  | ||||||
| 						while (accumulated_time >= app.target_frame_time) : (accumulated_time -= app.target_frame_time) { |  | ||||||
| 							try world.run_event(events.update); |  | ||||||
| 						} |  | ||||||
| 
 |  | ||||||
| 						try world.run_event(events.post_update); |  | ||||||
| 						try world.run_event(events.render); |  | ||||||
| 						try world.run_event(events.finish); |  | ||||||
| 					} |  | ||||||
| 
 |  | ||||||
| 					try world.run_event(events.exit); |  | ||||||
| 				} |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 			return Start.main; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		pub fn with_module(self: Setup, comptime Module: type) Setup { |  | ||||||
| 			if (!@hasDecl(Module, "setup")) { |  | ||||||
| 				@compileError("`Module` argument must have a setup fn declaration"); |  | ||||||
| 			} |  | ||||||
| 
 |  | ||||||
| 			const WithState = struct { |  | ||||||
| 				fn step(world: *World, events: App.Events) !void { |  | ||||||
| 					try Module.setup(world, events); |  | ||||||
| 					try self.step(world, events); |  | ||||||
| 				} |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 			return .{ |  | ||||||
| 				.step = WithState.step, |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		pub fn with_state(self: Setup, comptime state: anytype) Setup { |  | ||||||
| 			const WithState = struct { |  | ||||||
| 				fn step(world: *World, events: App.Events) !void { |  | ||||||
| 					try world.set_state(state); |  | ||||||
| 					try self.step(world, events); |  | ||||||
| 				} |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 			return .{ |  | ||||||
| 				.step = WithState.step, |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		pub fn with_system(self: Setup, comptime event: Event, comptime info: *const SystemInfo, comptime order: SystemOrder) Setup { |  | ||||||
| 			const WithState = struct { |  | ||||||
| 				fn step(world: *World, events: App.Events) !void { |  | ||||||
| 					const app_event = switch (event) { |  | ||||||
| 						.load => events.load, |  | ||||||
| 						.pre_update => events.pre_update, |  | ||||||
| 						.update => events.update, |  | ||||||
| 						.post_update => events.post_update, |  | ||||||
| 						.render => events.render, |  | ||||||
| 						.finish => events.finish, |  | ||||||
| 						.exit => events.exit, |  | ||||||
| 					}; |  | ||||||
| 
 |  | ||||||
| 					try world.on_event(app_event, info, order); |  | ||||||
| 					try self.step(world, events); |  | ||||||
| 				} |  | ||||||
| 			}; |  | ||||||
| 
 |  | ||||||
| 			return .{ |  | ||||||
| 				.step = WithState.step, |  | ||||||
| 			}; |  | ||||||
| 		} |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	pub const setup = Setup{}; |  | ||||||
| 
 |  | ||||||
| 	pub fn quit(self: *App) void { |  | ||||||
| 		self.is_running = false; | 		self.is_running = false; | ||||||
| 	} | 	} | ||||||
| }; | }; | ||||||
| @ -355,7 +316,10 @@ pub const LaunchFlag = enum { | |||||||
| 
 | 
 | ||||||
| pub fn Params(comptime Value: type) type { | pub fn Params(comptime Value: type) type { | ||||||
| 	if (!@hasDecl(Value, "Param")) { | 	if (!@hasDecl(Value, "Param")) { | ||||||
| 		@compileError("System parameters must have a Params type declaration"); | 		@compileError(@typeName(Value) ++ | ||||||
|  | 			\\ is missing a public `Params` type declaration. | ||||||
|  | 			\\This is required for a type to be used as a system parameter | ||||||
|  | 			); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return struct { | 	return struct { | ||||||
| @ -519,15 +483,11 @@ pub fn Receive(comptime Message: type) type { | |||||||
| 				.channel = (try context.register_readable_state_access(TypedChannel)) orelse set: { | 				.channel = (try context.register_readable_state_access(TypedChannel)) orelse set: { | ||||||
| 					try context.world.set_state(TypedChannel.init(heap.allocator)); | 					try context.world.set_state(TypedChannel.init(heap.allocator)); | ||||||
| 
 | 
 | ||||||
| 					const app = context.world.get_state(App) orelse { | 					try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{ | ||||||
| 						@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), | 						.label = "swap channel of " ++ @typeName(Message), | ||||||
| 					}); | 					}); | ||||||
| 
 | 
 | ||||||
| 					try context.world.on_event(app.events.exit, system_fn(TypedChannel.cleanup), .{ | 					try context.world.on_event(.exit, system_fn(TypedChannel.cleanup), .{ | ||||||
| 						.label = "clean up channel of " ++ @typeName(Message), | 						.label = "clean up channel of " ++ @typeName(Message), | ||||||
| 					}); | 					}); | ||||||
| 
 | 
 | ||||||
| @ -565,15 +525,11 @@ pub fn Send(comptime Message: type) type { | |||||||
| 				.channel = (try context.register_writable_state_access(TypedChannel)) orelse set: { | 				.channel = (try context.register_writable_state_access(TypedChannel)) orelse set: { | ||||||
| 					try context.world.set_state(TypedChannel.init(heap.allocator)); | 					try context.world.set_state(TypedChannel.init(heap.allocator)); | ||||||
| 
 | 
 | ||||||
| 					const app = context.world.get_state(App) orelse { | 					try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{ | ||||||
| 						@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), | 						.label = "swap channel of " ++ @typeName(Message), | ||||||
| 					}); | 					}); | ||||||
| 
 | 
 | ||||||
| 					try context.world.on_event(app.events.exit, system_fn(TypedChannel.cleanup), .{ | 					try context.world.on_event(.exit, system_fn(TypedChannel.cleanup), .{ | ||||||
| 						.label = "clean up channel of " ++ @typeName(Message), | 						.label = "clean up channel of " ++ @typeName(Message), | ||||||
| 					}); | 					}); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -69,6 +69,10 @@ pub fn Sequential(comptime Value: type) type { | |||||||
| 			return true; | 			return true; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		pub fn pop_all(self: *Self) void { | ||||||
|  | 			self.values = self.values[0 .. 0]; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		pub fn pop_many(self: *Self, n: usize) bool { | 		pub fn pop_many(self: *Self, n: usize) bool { | ||||||
| 			const new_length = ona.scalars.sub(self.values.len, n) orelse { | 			const new_length = ona.scalars.sub(self.values.len, n) orelse { | ||||||
| 				return false; | 				return false; | ||||||
| @ -247,6 +251,16 @@ pub fn Parallel(comptime Value: type) type { | |||||||
| 			return self.values.len; | 			return self.values.len; | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		pub fn pop(self: *Self) bool { | ||||||
|  | 			if (self.values.len == 0) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			self.values = self.values.slice_all(0, self.values.len - 1).?; | ||||||
|  | 
 | ||||||
|  | 			return true; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		pub fn pop_many(self: *Self, n: usize) bool { | 		pub fn pop_many(self: *Self, n: usize) bool { | ||||||
| 			const new_length = ona.scalars.sub(self.values.len, n) orelse { | 			const new_length = ona.scalars.sub(self.values.len, n) orelse { | ||||||
| 				return false; | 				return false; | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user