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", | ||||
| 			"type": "gdb", | ||||
| 			"request": "launch", | ||||
| 			"target": "${workspaceRoot}/demos/effects.out", | ||||
| 			"target": "${workspaceRoot}/demos/canvas.out", | ||||
| 			"cwd": "${workspaceRoot}/demos/", | ||||
| 			"valuesFormatting": "prettyPrinters", | ||||
| 			"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; | ||||
| 
 | ||||
| 	_ = 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_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. | ||||
| 	with_module(gfx). | ||||
| 	with_state(Effects{}). | ||||
| 	with_system(.load, ona.system_fn(load), .{.label = "load effects"}). | ||||
| 	with_system(.update, ona.system_fn(update), .{.label = "update effects"}). | ||||
| 	with_system(.render, ona.system_fn(render), .{.label = "render effects"}).build(); | ||||
| pub const main = ona.App.game | ||||
| 	.with_module(gfx) | ||||
| 	.with_state(Effects{}) | ||||
| 	.with_system(.load, ona.system_fn(load), .{.label = "load effects"}) | ||||
| 	.with_system(.update, ona.system_fn(update), .{.label = "update effects"}) | ||||
| 	.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; | ||||
| 
 | ||||
| 	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.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(.{ | ||||
| 		.texture = effects.state.render_texture, | ||||
| 		.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, | ||||
| 	}); | ||||
| 
 | ||||
| 	const display_width: f32 = @floatFromInt(display.state.width); | ||||
| 	const display_height: f32 = @floatFromInt(display.state.height); | ||||
| 
 | ||||
| 	const display_transform = gfx.transform_2d(.{ | ||||
| 		.translation = .{display_width / 2, display_height / 2}, | ||||
| 	}); | ||||
| 	const width: f32 = @floatFromInt(display.state.width); | ||||
| 	const height: f32 = @floatFromInt(display.state.height); | ||||
| 
 | ||||
| 	try commands.draw_texture(.{ | ||||
| 		.texture = effects.state.image_textures[effects.state.image_index], | ||||
| 		.transform = display_transform, | ||||
| 		.resolution = .{display.state.width, display.state.height}, | ||||
| 		.size = .{width, height}, | ||||
| 	}); | ||||
| 
 | ||||
| 	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), | ||||
| 			.width = width, | ||||
| 			.height = height, | ||||
| 			.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(.{ | ||||
| 		.texture = effects.state.render_texture, | ||||
| 		.transform = display_transform, | ||||
| 		.size = .{width, height}, | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -9,7 +9,8 @@ const std = @import("std"); | ||||
| const Spawned = struct { | ||||
| 	visual: struct { | ||||
| 		color: gfx.Color, | ||||
| 		transform: gfx.Transform2D, | ||||
| 		position: gfx.Vector2, | ||||
| 		rotation: f32, | ||||
| 	}, | ||||
| 
 | ||||
| 	lifetime_seconds: f32, | ||||
| @ -23,7 +24,6 @@ const Visuals = struct { | ||||
| 
 | ||||
| fn cleanup(visuals: ona.Write(Visuals)) !void { | ||||
| 	visuals.state.spawned.deinit(); | ||||
| 	visuals.state.spawned.deinit(); | ||||
| } | ||||
| 
 | ||||
| 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); | ||||
| } | ||||
| 
 | ||||
| pub const main = ona.App.setup. | ||||
| 	with_module(gfx). | ||||
| 	with_module(hid). | ||||
| 	with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)}). | ||||
| 	with_system(.load, ona.system_fn(load), .{.label = "load visuals"}). | ||||
| 	with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"}). | ||||
| 	with_system(.render, ona.system_fn(render), .{.label = "render visuals"}). | ||||
| 	with_system(.exit, ona.system_fn(cleanup), .{.label = "clean up visuals"}).build(); | ||||
| pub const main = ona.App.game | ||||
| 	.with_module(gfx) | ||||
| 	.with_module(hid) | ||||
| 	.with_state(Visuals{.random = std.Random.Xoroshiro128.init(47342563891212)}) | ||||
| 	.with_system(.load, ona.system_fn(load), .{.label = "load visuals"}) | ||||
| 	.with_system(.update, ona.system_fn(update), .{.label = "spawn visuals"}) | ||||
| 	.with_system(.render, ona.system_fn(render), .{.label = "render visuals"}) | ||||
| 	.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 { | ||||
| 	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 width: f32 = @floatFromInt(display.state.width); | ||||
| 	const height: f32 = @floatFromInt(display.state.height); | ||||
| 	const icon_scale = .{8, 8}; | ||||
| 
 | ||||
| 	for (events.messages()) |event| { | ||||
| 		switch (event) { | ||||
| @ -57,12 +56,8 @@ fn update(visuals: ona.Write(Visuals), events: ona.Receive(hid.Event), display: | ||||
| 
 | ||||
| 					.visual = .{ | ||||
| 						.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, | ||||
| 
 | ||||
| 						.transform = gfx.transform_2d(.{ | ||||
| 							.translation = .{width * random.float(f32), height}, | ||||
| 						.position = .{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 = .{ | ||||
| 						.color = .{random.float(f32), random.float(f32), random.float(f32), random.float(f32)}, | ||||
| 
 | ||||
| 						.transform = gfx.transform_2d(.{ | ||||
| 							.translation = visuals.state.mouse_position, | ||||
| 						.position = 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; | ||||
| 
 | ||||
| 	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| { | ||||
| 		try commands.draw_rect(.{ | ||||
| 			.transform = visual.transform, | ||||
| 			.color = visual.color, | ||||
| 		try commands.draw_texture(.{ | ||||
| 			.anchor = @splat(0.5), | ||||
| 			.position = visual.position, | ||||
| 			.tint = visual.color, | ||||
| 			.size = @as(gfx.Vector2, @splat(64)), | ||||
| 		}); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -38,7 +38,7 @@ pub const Effect = struct { | ||||
| 			}, | ||||
| 
 | ||||
| 			.vertex_source = .{ | ||||
| 				.ops = &spirv.to_ops(@embedFile("./shaders/2d_default.vert.spv")), | ||||
| 				.ops = &spirv.to_ops(@embedFile("./shaders/draw_texture.vert.spv")), | ||||
| 			}, | ||||
| 
 | ||||
| 			.fragment_source = .{ | ||||
| @ -197,12 +197,12 @@ pub const Effect = struct { | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[2] = .{ | ||||
| 				.format = .FLOAT2, | ||||
| 				.format = .FLOAT, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[3] = .{ | ||||
| 				.format = .FLOAT2, | ||||
| 				.format = .FLOAT, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| @ -212,12 +212,12 @@ pub const Effect = struct { | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[5] = .{ | ||||
| 				.format = .UBYTE4N, | ||||
| 				.format = .FLOAT2, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[6] = .{ | ||||
| 				.format = .FLOAT, | ||||
| 				.format = .FLOAT2, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| @ -226,6 +226,11 @@ pub const Effect = struct { | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			attrs[8] = .{ | ||||
| 				.format = .FLOAT4, | ||||
| 				.buffer_index = 1, | ||||
| 			}; | ||||
| 
 | ||||
| 			break: get attrs; | ||||
| 		}, | ||||
| 
 | ||||
| @ -466,7 +471,7 @@ pub fn init() !Self { | ||||
| 	} | ||||
| 
 | ||||
| 	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(.{ | ||||
|  | ||||
							
								
								
									
										278
									
								
								src/gfx/gfx.zig
									
									
									
									
									
								
							
							
						
						
									
										278
									
								
								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, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const WorkQueue = ona.asyncio.BlockingQueue(1024, union (enum) { | ||||
| 		load_effect: LoadEffectWork, | ||||
| 		load_texture: LoadTextureWork, | ||||
| 		render_frame: RenderFrameWork, | ||||
| 		shutdown, | ||||
| 		unload_effect: UnloadEffectWork, | ||||
| 		unload_texture: UnloadTextureWork, | ||||
| 
 | ||||
| 		const LoadEffectWork = struct { | ||||
| 			desc: Effect.Desc, | ||||
| 			loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Effect), | ||||
| 		}; | ||||
| 
 | ||||
| 		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, | ||||
| 		}; | ||||
| 	}); | ||||
| 	pub const WorkQueue = ona.asyncio.BlockingQueue(1024, Work); | ||||
| 
 | ||||
| 	fn deinit(self: *Assets) void { | ||||
| 		self.pending_work.enqueue(.shutdown); | ||||
| @ -72,12 +39,10 @@ pub const Assets = struct { | ||||
| 		self.* = undefined; | ||||
| 	} | ||||
| 
 | ||||
| 	fn init() !Assets { | ||||
| 	fn init(width: u16, height: u16) !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; | ||||
| @ -138,42 +103,60 @@ pub const Assets = struct { | ||||
| 	} | ||||
| 
 | ||||
| 	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 Commands = struct { | ||||
| 	pending: *List, | ||||
| pub const Command = union (enum) { | ||||
| 	draw_font: DrawFont, | ||||
| 	draw_texture: DrawTexture, | ||||
| 	set_effect: SetEffect, | ||||
| 	set_target: SetTarget, | ||||
| 
 | ||||
| 	pub const Command = union (enum) { | ||||
| 		draw_rect: DrawRectCommand, | ||||
| 		draw_texture: DrawTextureCommand, | ||||
| 		set_effect: SetEffectCommand, | ||||
| 		set_target: SetTargetCommand, | ||||
| 	pub const DrawFont = struct { | ||||
| 		position: Vector2 = @splat(0), | ||||
| 		size: Vector2, | ||||
| 		text: []const u8, | ||||
| 		font: Font, | ||||
| 		tint: Color, | ||||
| 		depth: f32, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const DrawRectCommand = struct { | ||||
| 		color: Color, | ||||
| 		transform: Transform2D, | ||||
| 	pub const DrawTexture = struct { | ||||
| 		anchor: Vector2 = @splat(0), | ||||
| 		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 { | ||||
| 		texture: Texture, | ||||
| 		transform: Transform2D, | ||||
| 		resolution: ?@Vector(2, u16) = null, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const SetEffectCommand = struct { | ||||
| 	pub const SetEffect = struct { | ||||
| 		effect: Effect, | ||||
| 		properties: []const u8, | ||||
| 	}; | ||||
| 
 | ||||
| 	pub const SetTargetCommand = struct { | ||||
| 	pub const SetTarget = struct { | ||||
| 		texture: ?Texture = null, | ||||
| 		clear_color: ?Color, | ||||
| 		clear_depth: ?f32, | ||||
| 		clear_stencil: ?u8, | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| pub fn Commands(comptime layer: Layer) type { | ||||
| 	return struct { | ||||
| 		pending: *List, | ||||
| 
 | ||||
| 		pub const List = struct { | ||||
| 			arena: std.heap.ArenaAllocator, | ||||
| @ -231,6 +214,8 @@ pub const Commands = struct { | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		const Self = @This(); | ||||
| 
 | ||||
| 		pub fn bind(_: ona.World.BindContext) std.mem.Allocator.Error!Param { | ||||
| 			return .{ | ||||
| 				.swap_lists = .{ | ||||
| @ -240,21 +225,30 @@ pub const Commands = struct { | ||||
| 			}; | ||||
| 		} | ||||
| 
 | ||||
| 	pub fn init(param: *Param) Commands { | ||||
| 		pub fn init(param: *Param) Self { | ||||
| 			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_font(self: Self, command: Command.DrawFont) std.mem.Allocator.Error!void { | ||||
| 			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, | ||||
| 				}, | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 	pub fn draw_texture(self: Commands, command: DrawTextureCommand) std.mem.Allocator.Error!void { | ||||
| 		pub fn draw_texture(self: Self, command: Command.DrawTexture) 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 { | ||||
| 		pub fn set_effect(self: Self, command: Command.SetEffect) std.mem.Allocator.Error!void { | ||||
| 			try self.pending.stack.push_grow(.{ | ||||
| 				.set_effect = .{ | ||||
| 					.properties = try self.pending.arena.allocator().dupe(u8, command.properties), | ||||
| @ -263,14 +257,17 @@ pub const Commands = struct { | ||||
| 			}); | ||||
| 		} | ||||
| 
 | ||||
| 		pub const _ = layer; | ||||
| 
 | ||||
| 		pub fn unbind(param: *Param, _: ona.World.UnbindContext) void { | ||||
| 			param.deinit(); | ||||
| 		} | ||||
| 
 | ||||
| 	pub fn set_target(self: Commands, command: SetTargetCommand) std.mem.Allocator.Error!void { | ||||
| 		pub fn set_target(self: Self, command: Command.SetTarget) std.mem.Allocator.Error!void { | ||||
| 			try self.pending.stack.push_grow(.{.set_target = command}); | ||||
| 		} | ||||
| }; | ||||
| 	}; | ||||
| } | ||||
| 
 | ||||
| pub const Descs = struct { | ||||
| 	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, | ||||
| 	top: f32, | ||||
| 	right: f32, | ||||
| @ -474,58 +488,47 @@ pub const Texture = enum (u32) { | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| pub const Transform2D = extern struct { | ||||
| 	xbasis: Vector = .{1, 0}, | ||||
| 	ybasis: Vector = .{0, 1}, | ||||
| 	origin: Vector = @splat(0), | ||||
| pub const Vector2 = @Vector(2, f32); | ||||
| 
 | ||||
| 	pub const Simplified = struct { | ||||
| 		translation: Vector = @splat(0), | ||||
| 		rotation: f32 = 0, | ||||
| 		scale: Vector = @splat(1), | ||||
| 		skew: f32 = 0, | ||||
| const Work = union (enum) { | ||||
| 	load_effect: LoadEffect, | ||||
| 	load_texture: LoadTexture, | ||||
| 	render_frame: RenderFrame, | ||||
| 	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); | ||||
| 
 | ||||
| 	pub fn scaled(self: Transform2D, scale: Vector) Transform2D { | ||||
| 		var transform = self; | ||||
| 		const scale_x, const scale_y = scale; | ||||
| 
 | ||||
| 		transform.xbasis *= @splat(scale_x); | ||||
|         transform.ybasis *= @splat(scale_y); | ||||
| 
 | ||||
| 		return transform; | ||||
| 	} | ||||
| 
 | ||||
| 	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, | ||||
| 	const LoadTexture = struct { | ||||
| 		desc: Texture.Desc, | ||||
| 		loaded: *ona.asyncio.Future(std.mem.Allocator.Error!Texture), | ||||
| 	}; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn translated(self: Transform2D, translation: Vector) Transform2D { | ||||
| 		var transform = self; | ||||
| 	const RenderFrame = struct { | ||||
| 		clear_color: Color, | ||||
| 		width: u16, | ||||
| 		height: u16, | ||||
| 		finished: *std.Thread.ResetEvent, | ||||
| 		commands_set: CommandsSet, | ||||
| 
 | ||||
| 		transform.origin += translation; | ||||
| 		const CommandsSet = struct { | ||||
| 			?*ona.Params(Commands(.background)).Node, | ||||
| 			?*ona.Params(Commands(.foreground)).Node, | ||||
| 			?*ona.Params(Commands(.overlay)).Node, | ||||
| 		}; | ||||
| 	}; | ||||
| 
 | ||||
| 		return transform; | ||||
| 	} | ||||
| 	const UnloadEffect = struct { | ||||
| 		handle: Effect, | ||||
| 	}; | ||||
| 
 | ||||
| 	const UnloadTexture = struct { | ||||
| 		handle: Texture, | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| pub const colors = struct { | ||||
| @ -546,6 +549,8 @@ pub const colors = struct { | ||||
| 
 | ||||
| 	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 { | ||||
| 		return .{r, g, b, 1}; | ||||
| 	} | ||||
| @ -553,13 +558,13 @@ pub const colors = struct { | ||||
| 	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); | ||||
| 
 | ||||
| 	while (ext.SDL_PollEvent(&event) != 0) { | ||||
| 		switch (event.type) { | ||||
| 			ext.SDL_QUIT => { | ||||
| 				app.state.quit(); | ||||
| 				loop.state.quit(); | ||||
| 			}, | ||||
| 
 | ||||
| 			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) { | ||||
| 		return error.Unsupported; | ||||
| 	} | ||||
| 
 | ||||
| 	const display = try world.set_get_state(Display{}); | ||||
| 
 | ||||
| 	const assets = create: { | ||||
| 		var assets = try Assets.init(); | ||||
| 		var assets = try Assets.init(display.width, display.height); | ||||
| 
 | ||||
| 		errdefer { | ||||
| 			assets.deinit(); | ||||
| @ -632,10 +639,9 @@ pub fn setup(world: *ona.World, events: ona.App.Events) (error {Unsupported} || | ||||
| 		assets.window, | ||||
| 	}); | ||||
| 
 | ||||
| 	try world.set_state(Display{}); | ||||
| 	try world.on_event(events.pre_update, ona.system_fn(poll), .{.label = "poll gfx"}); | ||||
| 	try world.on_event(events.exit, ona.system_fn(stop), .{.label = "stop gfx"}); | ||||
| 	try world.on_event(events.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"}); | ||||
| 	try world.on_event(.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(.finish, ona.system_fn(synchronize), .{.label = "synchronize gfx"}); | ||||
| } | ||||
| 
 | ||||
| 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.reset(); | ||||
| 
 | ||||
| 	{ | ||||
| 		var has_command_param = exclusive.world.get_params(Commands).has_head; | ||||
| 	inline for (Layer.values) |layer| { | ||||
| 		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) { | ||||
| 			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); | ||||
| 	} | ||||
| 
 | ||||
| 	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(.{ | ||||
| 			.render_frame = .{ | ||||
| 				.has_command_params = command_param, | ||||
| 				.commands_set = commands_set, | ||||
| 				.width = display.width, | ||||
| 				.height = display.height, | ||||
| 				.clear_color = display.clear_color, | ||||
| 				.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_target_texture: ?gfx.Texture = null, | ||||
| 	current_effect: gfx.Effect = .default, | ||||
| 	transform_matrix: Matrix = identity_matrix, | ||||
| 	width: u16 = 0, | ||||
| 	height: u16 = 0, | ||||
| 
 | ||||
| 	const DrawTexture = extern struct { | ||||
| 		transform: gfx.Transform2D, | ||||
| 		tint: @Vector(4, u8) = @splat(std.math.maxInt(u8)), | ||||
| 		depth: f32 = 0, | ||||
| 		texture_offset: @Vector(2, f32) = @splat(0), | ||||
| 		texture_size: @Vector(2, f32) = @splat(1), | ||||
| 		rotation: f32, | ||||
| 		depth: f32, | ||||
| 		position: gfx.Vector2, | ||||
| 		size: gfx.Vector2, | ||||
| 		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; | ||||
| @ -75,35 +83,13 @@ const Frame = struct { | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn draw_rect(self: *Frame, resources: *Resources, command: gfx.Commands.DrawRectCommand) !void { | ||||
| 		if (self.current_source_texture != .default) { | ||||
| 			self.flush(resources); | ||||
| 	pub fn draw_font(self: *Frame, resources: *Resources, command: gfx.Command.DrawFont) !void { | ||||
| 		_ = self; | ||||
| 		_ = 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) { | ||||
| 			self.flush(resources); | ||||
| 		} | ||||
| @ -127,10 +113,13 @@ const Frame = struct { | ||||
| 		const texture = resources.get_texture(command.texture).?; | ||||
| 
 | ||||
| 		_ = sokol.gfx.appendBuffer(self.texture_batch_buffers.get().?.*, sokol.gfx.asRange(&DrawTexture{ | ||||
| 			.transform = command.transform.scaled(@floatFromInt(command.resolution orelse .{ | ||||
| 				texture.width, | ||||
| 				texture.height, | ||||
| 			})), | ||||
| 			.size = command.size orelse .{@floatFromInt(texture.width), @floatFromInt(texture.height)}, | ||||
| 			.anchor = command.anchor, | ||||
| 			.rotation = command.rotation, | ||||
| 			.depth = command.depth, | ||||
| 			.source_clip = command.source, | ||||
| 			.tint = command.tint, | ||||
| 			.position = command.position, | ||||
| 		})); | ||||
| 
 | ||||
| 		self.drawn_count += 1; | ||||
| @ -178,19 +167,27 @@ const Frame = struct { | ||||
| 		if (self.current_target_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{ | ||||
| 				.projection_matrix = orthographic_projection(-1.0, 1.0, .{ | ||||
| 					.left = 0, | ||||
| 					.top = 0, | ||||
| 					.right = @floatFromInt(texture.width), | ||||
| 					.bottom = @floatFromInt(texture.height), | ||||
| 			}))); | ||||
| 				}), | ||||
| 
 | ||||
| 				.transform_matrix = self.transform_matrix, | ||||
| 			})); | ||||
| 		} else { | ||||
| 			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&orthographic_projection(-1.0, 1.0, .{ | ||||
| 			sokol.gfx.applyUniforms(.VS, 0, sokol.gfx.asRange(&View{ | ||||
| 				.projection_matrix = orthographic_projection(-1.0, 1.0, .{ | ||||
| 					.left = 0, | ||||
| 					.top = 0, | ||||
| 					.right = @floatFromInt(self.width), | ||||
| 					.bottom = @floatFromInt(self.height), | ||||
| 			}))); | ||||
| 				}), | ||||
| 
 | ||||
| 				.transform_matrix = self.transform_matrix, | ||||
| 			})); | ||||
| 		} | ||||
| 
 | ||||
| 		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) { | ||||
| 			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(); | ||||
| 
 | ||||
| 		var pass = sokol.gfx.Pass{ | ||||
| @ -305,18 +302,23 @@ const Frame = struct { | ||||
| 	} | ||||
| }; | ||||
| 
 | ||||
| fn Matrix(comptime n: usize, comptime Element: type) type { | ||||
| 	return [n]@Vector(n, Element); | ||||
| } | ||||
| const Matrix = [4]@Vector(4, f32); | ||||
| 
 | ||||
| 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 = .{ | ||||
| 	.mesh = 0, | ||||
| 	.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 height = viewport.bottom - viewport.top; | ||||
| 
 | ||||
| @ -397,15 +399,16 @@ 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); | ||||
| 
 | ||||
| 				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) { | ||||
| 					for (command_params.param.submitted_commands()) |command| { | ||||
| 					while (has_commands_currently) |commands| : (has_commands_currently = commands.has_next) { | ||||
| 						for (commands.param.submitted_commands()) |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), | ||||
| 								.set_effect => |set_effect| frame.set_effect(&resources, set_effect), | ||||
| 								.set_target => |set_target| frame.set_target(&resources, set_target), | ||||
| @ -422,6 +425,7 @@ pub fn process_work(pending_work: *gfx.Assets.WorkQueue, window: *ext.SDL_Window | ||||
| 							}); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				frame.finish(&resources); | ||||
| 				ext.SDL_GL_SwapWindow(window); | ||||
|  | ||||
| @ -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, | ||||
| 					}, | ||||
| 
 | ||||
| 					.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse { | ||||
| 						return error.UnsupportedSPIRV; | ||||
| 					}, | ||||
| 					.len = std.math.cast(u16, ext.spvc_type_get_array_dimension(member_type_handle, 0)) orelse 0, | ||||
| 				}; | ||||
| 			} | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										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.on_event(events.pre_update, ona.system_fn(update), .{ | ||||
| 	try world.on_event(.pre_update, ona.system_fn(update), .{ | ||||
| 		.label = "update actions", | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| @ -6,7 +6,7 @@ const std = @import("std"); | ||||
| 
 | ||||
| thread_pool: ?*std.Thread.Pool = null, | ||||
| 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 { | ||||
| 	node: ona.dag.Node, | ||||
| @ -110,8 +110,6 @@ const Graph = ona.dag.Graph(struct { | ||||
| 	resource_accesses: ona.stack.Sequential(StateAccess), | ||||
| }); | ||||
| 
 | ||||
| pub const Event = enum (usize) { _ }; | ||||
| 
 | ||||
| const ParallelNodeBundles = ona.stack.Sequential(NodeBundle); | ||||
| 
 | ||||
| const NodeBundle = ona.stack.Sequential(ona.dag.Node); | ||||
| @ -442,22 +440,16 @@ const Schedule = struct { | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	pub fn init(label: []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); | ||||
| 
 | ||||
| 	pub fn init(comptime label: [:0]const u8) std.mem.Allocator.Error!Schedule { | ||||
| 		return .{ | ||||
| 			.graph = Graph.init(ona.heap.allocator), | ||||
| 			.label = duped_label, | ||||
| 			.arena = arena, | ||||
| 			.system_id_nodes = .{.allocator = ona.heap.allocator}, | ||||
| 			.read_write_resource_id_nodes = .{.allocator = ona.heap.allocator}, | ||||
| 			.read_only_resource_id_nodes = .{.allocator = ona.heap.allocator}, | ||||
| 			.parallel_work_bundles = .{.allocator = ona.heap.allocator}, | ||||
| 			.blocking_work = .{.allocator = ona.heap.allocator}, | ||||
| 			.arena = std.heap.ArenaAllocator.init(ona.heap.allocator), | ||||
| 			.label = label, | ||||
| 			.system_id_nodes = .{}, | ||||
| 			.read_write_resource_id_nodes = .{}, | ||||
| 			.read_only_resource_id_nodes = .{}, | ||||
| 			.parallel_work_bundles = .{}, | ||||
| 			.blocking_work = .{}, | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| @ -544,21 +536,12 @@ 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); | ||||
| 	var event_schedules = self.event_schedules.entries(); | ||||
| 
 | ||||
| 	while (event_schedules.next()) |schedule| { | ||||
| 		schedule.value.deinit(self); | ||||
| 		ona.heap.allocator.destroy(schedule.value); | ||||
| 	} | ||||
| 
 | ||||
| 	for (&self.thread_restricted_resources) |*resources| { | ||||
| @ -570,7 +553,7 @@ pub fn deinit(self: *Self) void { | ||||
| 		ona.heap.allocator.destroy(thread_pool); | ||||
| 	} | ||||
| 
 | ||||
| 	self.event_systems.deinit(); | ||||
| 	self.event_schedules.deinit(); | ||||
| 
 | ||||
| 	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 { | ||||
| 	var world = Self{ | ||||
| 		.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) { | ||||
| @ -611,12 +594,34 @@ pub fn init(thread_count: u32) std.Thread.SpawnError!Self { | ||||
| 	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)].insert(action, order); | ||||
| pub fn on_event(self: *Self, comptime event: anytype, info: *const ona.SystemInfo, order: ona.SystemOrder) std.mem.Allocator.Error!void { | ||||
| 	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 { | ||||
| 	try self.event_systems.values[@intFromEnum(event)].run(self); | ||||
| pub fn run_event(self: *Self, comptime event: anytype) anyerror!void { | ||||
| 	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 { | ||||
|  | ||||
| @ -7,7 +7,7 @@ pub fn Hashed(comptime Key: type, comptime Value: type, comptime traits: Traits( | ||||
| 	const max_int = std.math.maxInt(usize); | ||||
| 
 | ||||
| 	return struct { | ||||
| 		allocator: std.mem.Allocator, | ||||
| 		allocator: std.mem.Allocator = ona.heap.allocator, | ||||
| 		entry_map: []?Entry = &.{}, | ||||
| 		len: usize = 0, | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										178
									
								
								src/ona/ona.zig
									
									
									
									
									
								
							
							
						
						
									
										178
									
								
								src/ona/ona.zig
									
									
									
									
									
								
							| @ -29,37 +29,11 @@ pub const World = @import("./World.zig"); | ||||
| pub const lina = @import("./lina.zig"); | ||||
| 
 | ||||
| pub const App = struct { | ||||
| 	events: *const Events, | ||||
| 	target_frame_time: f64, | ||||
| 	elapsed_time: f64, | ||||
| 	is_running: bool, | ||||
| 	step: fn (*World) anyerror!void = noop, | ||||
| 
 | ||||
| 	pub const Events = struct { | ||||
| 		load: World.Event, | ||||
| 		pre_update: World.Event, | ||||
| 		update: World.Event, | ||||
| 		post_update: World.Event, | ||||
| 		render: World.Event, | ||||
| 		finish: World.Event, | ||||
| 		exit: World.Event, | ||||
| 	}; | ||||
| 	fn noop(_: *World) !void {} | ||||
| 
 | ||||
| 	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 { | ||||
| 	pub fn build(self: App) fn () anyerror!void { | ||||
| 		const Start = struct { | ||||
| 			fn main() anyerror!void { | ||||
| 				defer { | ||||
| @ -100,66 +74,60 @@ pub const App = struct { | ||||
| 
 | ||||
| 				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); | ||||
| 				try self.step(&world); | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| 		return Start.main; | ||||
| 	} | ||||
| 
 | ||||
| 		pub fn with_module(self: Setup, comptime Module: type) Setup { | ||||
| 	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, events: App.Events) !void { | ||||
| 					try Module.setup(world, events); | ||||
| 					try self.step(world, events); | ||||
| 			fn step(world: *World) !void { | ||||
| 				try Module.setup(world); | ||||
| 				try self.step(world); | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| @ -168,11 +136,11 @@ pub const App = struct { | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 		pub fn with_state(self: Setup, comptime state: anytype) Setup { | ||||
| 	pub fn with_state(self: App, comptime state: anytype) App { | ||||
| 		const WithState = struct { | ||||
| 				fn step(world: *World, events: App.Events) !void { | ||||
| 			fn step(world: *World) !void { | ||||
| 				try world.set_state(state); | ||||
| 					try self.step(world, events); | ||||
| 				try self.step(world); | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| @ -181,21 +149,11 @@ pub const App = struct { | ||||
| 		}; | ||||
| 	} | ||||
| 
 | ||||
| 		pub fn with_system(self: Setup, comptime event: Event, comptime info: *const SystemInfo, comptime order: SystemOrder) Setup { | ||||
| 	pub fn with_system(self: App, comptime event: anytype, comptime info: *const SystemInfo, comptime order: SystemOrder) App { | ||||
| 		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); | ||||
| 			fn step(world: *World) !void { | ||||
| 				try world.on_event(event, info, order); | ||||
| 				try self.step(world); | ||||
| 			} | ||||
| 		}; | ||||
| 
 | ||||
| @ -203,11 +161,14 @@ pub const App = struct { | ||||
| 			.step = WithState.step, | ||||
| 		}; | ||||
| 	} | ||||
| 	}; | ||||
| }; | ||||
| 
 | ||||
| 	pub const setup = Setup{}; | ||||
| pub const Loop = struct { | ||||
| 	target_frame_time: f64, | ||||
| 	elapsed_time: f64, | ||||
| 	is_running: bool, | ||||
| 
 | ||||
| 	pub fn quit(self: *App) void { | ||||
| 	pub fn quit(self: *Loop) void { | ||||
| 		self.is_running = false; | ||||
| 	} | ||||
| }; | ||||
| @ -355,7 +316,10 @@ pub const LaunchFlag = enum { | ||||
| 
 | ||||
| pub fn Params(comptime Value: type) type { | ||||
| 	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 { | ||||
| @ -519,15 +483,11 @@ pub fn Receive(comptime Message: type) type { | ||||
| 				.channel = (try context.register_readable_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), .{ | ||||
| 					try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{ | ||||
| 						.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), | ||||
| 					}); | ||||
| 
 | ||||
| @ -565,15 +525,11 @@ pub fn Send(comptime Message: type) type { | ||||
| 				.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), .{ | ||||
| 					try context.world.on_event(.post_update, system_fn(TypedChannel.swap), .{ | ||||
| 						.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), | ||||
| 					}); | ||||
| 
 | ||||
|  | ||||
| @ -69,6 +69,10 @@ pub fn Sequential(comptime Value: type) type { | ||||
| 			return true; | ||||
| 		} | ||||
| 
 | ||||
| 		pub fn pop_all(self: *Self) void { | ||||
| 			self.values = self.values[0 .. 0]; | ||||
| 		} | ||||
| 
 | ||||
| 		pub fn pop_many(self: *Self, n: usize) bool { | ||||
| 			const new_length = ona.scalars.sub(self.values.len, n) orelse { | ||||
| 				return false; | ||||
| @ -247,6 +251,16 @@ pub fn Parallel(comptime Value: type) type { | ||||
| 			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 { | ||||
| 			const new_length = ona.scalars.sub(self.values.len, n) orelse { | ||||
| 				return false; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user