add spritesheets

This commit is contained in:
Rokas Puzonas 2026-07-11 17:04:17 +03:00
parent f685b69bd9
commit c09ff2aad0
11 changed files with 490 additions and 64 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

View File

@ -69,6 +69,7 @@ pub fn build(b: *std.Build) !void {
const dep_stb = b.dependency("stb", .{});
exe_mod.addImport("stb_image", dep_stb.module("stb_image"));
exe_mod.addImport("stb_vorbis", dep_stb.module("stb_vorbis"));
exe_mod.addImport("stb_rect_pack", dep_stb.module("stb_rect_pack"));
const dep_tracy = b.dependency("tracy", .{
.target = target,

View File

@ -22,17 +22,32 @@ pub fn build(b: *std.Build) void {
}
{
const mod_stb_image = b.addModule("stb_vorbis", .{
const mod_stb_vorbis = b.addModule("stb_vorbis", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("src/stb_vorbis.zig"),
.link_libc = true,
});
mod_stb_image.addIncludePath(stb_dependency.path("."));
mod_stb_image.addCSourceFile(.{
mod_stb_vorbis.addIncludePath(stb_dependency.path("."));
mod_stb_vorbis.addCSourceFile(.{
.file = b.path("src/stb_vorbis_impl.c"),
.flags = &.{}
});
}
{
const mod_stb_rect_pack = b.addModule("stb_rect_pack", .{
.target = target,
.optimize = optimize,
.root_source_file = b.path("src/stb_rect_pack.zig"),
.link_libc = true,
});
mod_stb_rect_pack.addIncludePath(stb_dependency.path("."));
mod_stb_rect_pack.addCSourceFile(.{
.file = b.path("src/stb_rect_pack_impl.c"),
.flags = &.{}
});
}
}

View File

@ -0,0 +1,36 @@
const std = @import("std");
const c = @cImport({
@cInclude("stb_rect_pack.h");
});
const STBRectPack = @This();
ctx: c.stbrp_context,
pub fn init(self: *STBRectPack, width: u32, height: u32, nodes: []Node) void {
c.stbrp_init_target(&self.ctx, @intCast(width), @intCast(height), nodes.ptr, @intCast(nodes.len));
}
pub fn setupAllowOutOfMem(self: *STBRectPack, enabled: bool) void {
c.stbrp_setup_heuristic(&self.ctx, if (enabled) 1 else 0);
}
pub fn setupHeuristic(self: *STBRectPack, heuristic: Heuristic) void {
c.stbrp_setup_heuristic(&self.ctx, heuristic);
}
pub fn packRects(self: *STBRectPack, rects: []Rect) bool {
return c.stbrp_pack_rects(&self.ctx, rects.ptr, @intCast(rects.len)) == 1;
}
pub const Heuristic = enum(i32) {
Skyline_BL_sortHeight = c.STBRP_HEURISTIC_Skyline_BL_sortHeight,
Skyline_BF_sortHeight = c.STBRP_HEURISTIC_Skyline_BF_sortHeight,
_,
pub const default = Heuristic.Skyline_BL_sortHeight;
};
pub const Node = c.stbrp_node;
pub const Rect = c.stbrp_rect;

View File

@ -0,0 +1,2 @@
#define STB_RECT_PACK_IMPLEMENTATION
#include "stb_rect_pack.h"

View File

@ -10,6 +10,7 @@ const STBImage = @import("stb_image");
const Platform = @import("./platform.zig");
const Gfx = Platform.Gfx;
const ImGUI = Platform.ImGUI;
const Rect = Platform.Rect;
const App = @This();
@ -17,13 +18,14 @@ bg: Platform.Color,
show_first_window: bool,
check: bool,
player_pos: Vec2,
tex: Gfx.TextureId,
sprite_sprite: Gfx.Sprite.Id,
wall_sprite: Gfx.Sprite.Id,
pub fn init(self: *App, plt: Platform.Init) !?u8 {
const cwd = std.Io.Dir.cwd();
const sokoban_spritesheet = try cwd.readFileAlloc(
plt.io,
"assets/sokoban/Spritesheet/sokoban_spritesheet.png",
"assets/sokoban/sokoban_tilesheet.png",
plt.gpa,
.unlimited
);
@ -32,15 +34,35 @@ pub fn init(self: *App, plt: Platform.Init) !?u8 {
var image = try STBImage.load(sokoban_spritesheet);
defer image.deinit();
const tex = try Gfx.initTexture();
try Gfx.setTexture(tex, image.rgba8_pixels, image.width, image.height);
const spritesheet = Gfx.ImageData{
.width = image.width,
.height = image.height,
.pixels = .{ .rgba8 = image.rgba8_pixels }
};
const tile_size = Vec2.init(64, 64);
const player_sprite = try Gfx.initSprite();
Gfx.setSprite(player_sprite, .{
.image = spritesheet,
.rect = Rect.init(0, 4, 1, 1).multiply(tile_size)
});
const wall_sprite = try Gfx.initSprite();
Gfx.setSprite(wall_sprite, .{
.image = spritesheet,
.rect = Rect.init(1, 0, 1, 1).multiply(tile_size)
});
try Gfx.packSprites();
self.* = App{
.bg = .black,
.player_pos = .init(100, 100),
.show_first_window = true,
.check = false,
.tex = tex
.sprite_sprite = player_sprite,
.wall_sprite = wall_sprite
};
return null;
@ -68,8 +90,8 @@ pub fn frame(self: *App, plt: Platform.Frame) !void {
self.player_pos = self.player_pos.add(dir.multiplyScalar(50 * dt));
Gfx.setClearColor(self.bg);
Gfx.drawTexture(self.tex, self.player_pos, .init(100, 100), .rgb(200, 0, 0));
Gfx.drawRectangle(self.player_pos.add(.init(0, 200)), .init(100, 100), .rgb(255, 255, 255));
Gfx.drawSprite(self.sprite_sprite, self.player_pos, .init(100, 100), .white);
Gfx.drawSprite(self.wall_sprite, self.player_pos.add(.init(0, 200)), .init(100, 100), .white);
if (ImGUI.beginWindow(.{
.name = "Hello",

View File

@ -1,6 +1,7 @@
const std = @import("std");
const log = std.log.scoped(.graphics);
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const sokol = @import("sokol");
const sg = sokol.gfx;
@ -11,7 +12,9 @@ const Math = @import("math");
const Vec2 = Math.Vec2;
const Vec4 = Math.Vec4;
const Mat4 = Math.Mat4;
const Rect = Math.Rect;
const STBRectPack = @import("stb_rect_pack");
const Color = @import("./color.zig");
const shd = @import("shader");
const SlotMapType = @import("slot_map.zig").SlotMapType;
@ -23,8 +26,8 @@ const State = struct {
pipeline: sg.Pipeline,
bindings: sg.Bindings,
default_texture: TextureId,
error_texture: TextureId,
default_sprite: Sprite.Id,
error_sprite: Sprite.Id,
default_sampler: sg.Sampler,
quads_buffer: [2048]Quad,
@ -32,13 +35,15 @@ const State = struct {
clear_color: Color,
textures_buffer: [max_textures]Texture,
textures_slotmap: TextureSlotMap,
};
textures_buffer: [Texture.max_textures]Texture.SlotMap.Slot,
textures: Texture.SlotMap,
const max_textures = 64;
const TextureSlotMap = SlotMapType(std.math.IntFittingRange(0, max_textures-1), u8);
pub const TextureId = TextureSlotMap.Id;
spritesheet_texture: Texture.Id,
spritesheet_size: Vec2,
spritesheet_stale: bool,
sprites_buffer: [Sprite.max_sprites]Sprite.SlotMap.Slot,
sprites: Sprite.SlotMap,
};
var g_state: State = undefined;
@ -129,9 +134,14 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
.quads = .initBuffer(&self.quads_buffer),
.clear_color = .black,
.textures_buffer = undefined,
.textures_slotmap = .init(try .initCapacity(gpa, max_textures)),
.default_texture = undefined,
.error_texture = undefined
.textures = .init(.initBuffer(&self.textures_buffer)),
.default_sprite = undefined,
.error_sprite = undefined,
.spritesheet_stale = false,
.sprites_buffer = undefined,
.sprites = .init(.initBuffer(&self.sprites_buffer)),
.spritesheet_texture = undefined,
.spritesheet_size = .init(0, 0)
};
{
@ -141,8 +151,14 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
var pixels: [width * height * 4]u8 = undefined;
@memset(&pixels, 0xFF);
self.default_texture = try initTexture();
try setTexture(self.default_texture, &pixels, width, height);
self.default_sprite = try initSprite();
setSprite(self.default_sprite, .{
.image = ImageData{
.width = width,
.height = height,
.pixels = .{ .rgba8 = &pixels }
}
});
}
{
@ -169,15 +185,29 @@ pub fn init(gpa: std.mem.Allocator, logger: sg.Logger) !void {
}
}
self.error_texture = try initTexture();
try setTexture(self.error_texture, &pixels, width, height);
self.error_sprite = try initSprite();
setSprite(self.error_sprite, .{
.image = .{
.width = width,
.height = height,
.pixels = .{ .rgba8 = &pixels }
}
});
}
self.spritesheet_texture = try initTexture();
}
pub fn deinit() void {
const self = &g_state;
self.textures_slotmap.deinit(self.gpa);
var sprite_iter = self.sprites.iterator();
while (sprite_iter.next()) |sprite_id| {
const sprite = self.sprites.getAssumeExists(sprite_id);
if (sprite.data) |data| {
data.deinit(self.gpa);
}
}
sg.destroyPipeline(self.pipeline);
sg.destroyShader(self.shader);
@ -227,9 +257,8 @@ pub fn beginFrame() void {
.swapchain = sglue.swapchain()
});
assert(self.textures_slotmap.exists(self.default_texture));
const default_texture = self.textures_buffer[self.default_texture.index];
self.bindings.views[shd.VIEW_tex] = default_texture.view;
const spritesheet_texture = self.textures.getAssumeExists(self.spritesheet_texture);
self.bindings.views[shd.VIEW_tex] = spritesheet_texture.view;
self.bindings.samplers[shd.SMP_smp] = self.default_sampler;
}
@ -285,7 +314,7 @@ pub fn endFrame() void {
pub fn drawRectangle(pos: Vec2, size: Vec2, color: Color) void {
const self = &g_state;
drawTexture(self.default_texture, pos, size, color);
drawSprite(self.default_sprite, pos, size, color);
}
pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
@ -300,21 +329,21 @@ pub fn drawLine(from: Vec2, to: Vec2, color: Vec4, width: f32) void {
var quad: Quad = undefined;
quad.setColor(color);
quad.setUVRect(.init(0, 0), .init(1, 1));
quad.setUVRect(.init(0, 0, 1, 1));
quad.vertices[0] = top_right;
quad.vertices[1] = top_left;
quad.vertices[2] = bottom_right;
quad.vertices[3] = bottom_left;
draw(.{
.texture = self.default_texture,
.texture = self.default_sprite,
.quad = quad
});
}
pub fn initTexture() !TextureId {
pub fn initTexture() !Texture.Id {
const self = &g_state;
const id = try self.textures_slotmap.insertBounded();
const id = try self.textures.insertBounded();
const image = sg.allocImage();
assert(image.id != sg.invalid_id);
@ -322,7 +351,7 @@ pub fn initTexture() !TextureId {
const view = sg.allocView();
assert(view.id != sg.invalid_id);
const texture = &self.textures_buffer[id.index];
const texture = self.textures.getAssumeExists(id);
texture.* = .{
.image = image,
.view = view
@ -330,14 +359,14 @@ pub fn initTexture() !TextureId {
return id;
}
pub fn deinitTexture(id: TextureId) void {
pub fn deinitTexture(id: Texture.Id) void {
const self = &g_state;
if (self.textures_slotmap.remove(id)) {
if (self.textures.remove(id)) {
const texture = self.textures_buffer[id.index];
const bound_view = &self.bindings.views[shd.VIEW_tex];
if (texture.view.id == bound_view.id) {
assert(self.textures_slotmap.exists(self.default_texture));
bound_view.* = self.textures_buffer[self.default_texture.index].view;
assert(self.textures.exists(self.default_sprite));
bound_view.* = self.textures_buffer[self.default_sprite.index].view;
}
sg.destroyView(texture.view);
@ -345,25 +374,26 @@ pub fn deinitTexture(id: TextureId) void {
}
}
pub fn setTexture(id: TextureId, rgba8_pixels: [*]const u8, width: u32, height: u32) !void {
pub fn setTexture(id: Texture.Id, texture_data: ImageData) void {
const self = &g_state;
if (!self.textures_slotmap.exists(id)) {
return error.NotFound;
if (!self.textures.exists(id)) {
log.warn("Attempt to set data for texture that doesn't exist: {}", .{id});
return;
}
const texture = &self.textures_buffer[id.index];
const texture = self.textures.getAssumeExists(id);
if (sg.queryImageState(texture.image) == .VALID) {
sg.uninitImage(texture.image);
}
const pixel_count = width*height;
const pixel_count = texture_data.width*texture_data.height;
var image_data: sg.ImageData = .{};
image_data.mip_levels[0] = sg.asRange(rgba8_pixels[0..(pixel_count*4)]);
image_data.mip_levels[0] = sg.asRange(texture_data.pixels.rgba8[0..(pixel_count*4)]);
sg.initImage(texture.image, .{
.type = ._2D,
.width = @intCast(width),
.height = @intCast(height),
.width = @intCast(texture_data.width),
.height = @intCast(texture_data.height),
.num_mipmaps = 1,
.pixel_format = .RGBA8,
.data = image_data,
@ -378,11 +408,11 @@ pub fn setTexture(id: TextureId, rgba8_pixels: [*]const u8, width: u32, height:
}
}
pub fn drawTexture(id: TextureId, pos: Vec2, size: Vec2, color: Color) void {
pub fn drawTexture(id: Texture.Id, pos: Vec2, size: Vec2, color: Color) void {
var quad: Quad = undefined;
quad.setColor(color);
quad.setRect(pos, size);
quad.setUVRect(.init(0, 0), .init(1, 1));
quad.setRect(.{ .pos = pos, .size = size });
quad.setUVRect(.init(0, 0, 1, 1));
draw(.{
.texture = id,
@ -391,7 +421,7 @@ pub fn drawTexture(id: TextureId, pos: Vec2, size: Vec2, color: Color) void {
}
const DrawOptions = struct {
texture: TextureId,
texture: Texture.Id,
quad: Quad
};
@ -400,12 +430,18 @@ pub fn draw(opts: DrawOptions) void {
var quad = opts.quad;
var texture = opts.texture;
if (!self.textures_slotmap.exists(texture)) {
texture = self.error_texture;
quad.setColor(.white);
if (!self.textures.exists(texture)) {
if (getSpriteUVRect(self.error_sprite)) |error_uv| {
texture = self.spritesheet_texture;
quad.setUVRect(error_uv);
quad.setColor(.white);
} else {
log.err("Failed to draw error texture", .{});
return;
}
}
const view = self.textures_buffer[texture.index].view;
const view = self.textures.getAssumeExists(texture).view;
const bound_view = &self.bindings.views[shd.VIEW_tex];
if (bound_view.id != view.id) {
@ -420,9 +456,311 @@ pub fn draw(opts: DrawOptions) void {
self.quads.appendAssumeCapacity(quad);
}
const Texture = struct {
pub fn initSprite() !Sprite.Id {
const self = &g_state;
const id = try self.sprites.insertBounded();
const sprite = self.sprites.getAssumeExists(id);
sprite.* = .{
.data = null,
.position = null
};
return id;
}
pub fn deinitSprite(id: Sprite.Id) void {
const self = &g_state;
if (self.sprites.exists(id)) {
const sprite = self.sprites.getAssumeExists(id);
if (sprite.data) |data| {
data.deinit(self.gpa);
}
self.sprites.removeAssumeExists(id);
self.spritesheet_stale = true;
}
}
const SetSpriteOptions = struct {
image: ImageData,
rect: ?Rect = null
};
pub fn setSprite(id: Sprite.Id, opts: SetSpriteOptions) void {
const self = &g_state;
if (!self.sprites.exists(id)) {
log.warn("Attempt to set data for sprite that doesn't exist: {}", .{id});
return;
}
var sprite_data: ImageData = .empty;
errdefer sprite_data.deinit(self.gpa);
if (opts.rect) |rect| {
sprite_data = ImageData.initRGBA8(self.gpa, @trunc(rect.size.x), @trunc(rect.size.y)) catch |e| {
log.warn("Failed to create texture data: {}", .{e});
return;
};
sprite_data.copyFrom(
opts.image,
0, 0,
@trunc(rect.pos.x), @trunc(rect.pos.y),
@trunc(rect.size.x), @trunc(rect.size.y)
);
} else {
sprite_data = opts.image.clone(self.gpa) catch |e| {
log.warn("Failed to clone image: {}", .{e});
return;
};
}
const sprite = self.sprites.getAssumeExists(id);
if (sprite.data) |old_sprite_data| {
old_sprite_data.deinit(self.gpa);
}
sprite.data = sprite_data;
sprite.position = null;
self.spritesheet_stale = true;
}
pub fn packSprites() !void {
const self = &g_state;
// TODO: Add a smarter startegy for picking the initial texture size.
// One idea is to calculate the sum area of all sprites and pick a atlas size based on that.
const texture_sizes = [_]u32{
64, 128, 256, 512, 1024, 2048, 4096
};
var nodes: [32]STBRectPack.Node = undefined;
var rects: [Sprite.max_sprites]STBRectPack.Rect = undefined;
for (&texture_sizes) |texture_size| {
var ctx: STBRectPack = undefined;
ctx.init(texture_size, texture_size, &nodes);
ctx.setupAllowOutOfMem(true);
@memset(&rects, .{});
{
var i: usize = 0;
var sprite_iter = self.sprites.iterator();
while (sprite_iter.next()) |sprite_id| : (i += 1) {
const sprite = self.sprites.getAssumeExists(sprite_id);
const sprite_data = sprite.data orelse continue;
rects[i] = .{
.w = @intCast(sprite_data.width),
.h = @intCast(sprite_data.height),
};
}
}
const success = ctx.packRects(&rects);
std.debug.print("success {} {}\n", .{texture_size, success});
if (!success) {
continue;
}
const spritesheet = try ImageData.initRGBA8(self.gpa, texture_size, texture_size);
defer spritesheet.deinit(self.gpa);
{
var i: usize = 0;
var sprite_iter = self.sprites.iterator();
while (sprite_iter.next()) |sprite_id| : (i += 1) {
const sprite = self.sprites.getAssumeExists(sprite_id);
const sprite_data = sprite.data orelse continue;
assert(rects[i].was_packed == 1);
const x: u32 = @intCast(rects[i].x);
const y: u32 = @intCast(rects[i].y);
sprite.position = .initFromInt(u32, x, y);
spritesheet.copyFrom(
sprite_data,
x,
y,
0,
0,
sprite_data.width,
sprite_data.height
);
}
}
setTexture(self.spritesheet_texture, spritesheet);
self.spritesheet_size = .initFromInt(u32, texture_size, texture_size);
self.spritesheet_stale = false;
return;
}
const max_texture_size = texture_sizes[texture_sizes.len-1];
log.warn("Failed to pack sprites, couldn't create a texture that is large enough. Max size tried: {}x{}", .{max_texture_size, max_texture_size});
}
pub fn getSpriteUVRect(id: Sprite.Id) ?Rect {
const self = &g_state;
if (!self.sprites.exists(id)) {
return null;
}
const sprite = self.sprites.getAssumeExists(id);
if (sprite.data == null) {
return null;
}
if (self.spritesheet_stale) {
packSprites() catch |e| {
log.warn("Failed to pack sprites: {}", .{e});
};
}
assert(sprite.position != null);
const sprite_size = Vec2.initFromInt(u32, sprite.data.?.width, sprite.data.?.height);
return Rect{
.pos = sprite.position.?.divide(self.spritesheet_size),
.size = sprite_size.divide(self.spritesheet_size),
};
}
pub fn drawSprite(id: Sprite.Id, pos: Vec2, size: Vec2, color: Color) void {
const self = &g_state;
var quad: Quad = undefined;
quad.setColor(color);
quad.setRect(.{ .pos = pos, .size = size });
if (getSpriteUVRect(id)) |sprite_uv| {
quad.setUVRect(sprite_uv);
} else if (getSpriteUVRect(self.error_sprite)) |error_uv| {
quad.setUVRect(error_uv);
quad.setColor(.white);
}
draw(.{
.texture = self.spritesheet_texture,
.quad = quad
});
}
pub const ImageData = struct {
const Pixels = union(enum) {
none,
rgba8: [*]u8
};
pixels: Pixels,
width: u32,
height: u32,
pub const empty = ImageData{
.pixels = .none,
.width = 0,
.height = 0,
};
pub fn initRGBA8(gpa: Allocator, width: u32, height: u32) !ImageData {
const pixel_count = width * height;
const pixels = try gpa.alloc(u8, pixel_count * 4);
return ImageData{
.pixels = .{ .rgba8 = pixels.ptr },
.width = width,
.height = height
};
}
pub fn clone(self: ImageData, gpa: Allocator) !ImageData {
const pixel_count = self.width * self.height;
const pixels = switch (self.pixels) {
.none => Pixels{ .none = {} },
.rgba8 => |pixels| Pixels{
.rgba8 = (try gpa.dupe(u8, pixels[0..(pixel_count * 4)])).ptr
}
};
return ImageData{
.pixels = pixels,
.width = self.width,
.height = self.height
};
}
pub fn deinit(self: ImageData, gpa: Allocator) void {
const pixel_count = self.width * self.height;
switch (self.pixels) {
.none => {},
.rgba8 => |pixels| gpa.free(pixels[0..(pixel_count*4)])
}
}
pub fn copyFrom(
self: ImageData,
source: ImageData,
cx: u32,
cy: u32,
source_cx: u32,
source_cy: u32,
source_width: u32,
source_height: u32,
) void {
if (source.pixels == .none or self.pixels == .none) {
return;
}
for (0..source_height) |oy| {
const source_y = source_cy+oy;
const dest_y = cy+oy;
if (dest_y >= self.height) {
continue;
}
if (source_y >= source.height) {
continue;
}
for (0..source_width) |ox| {
const source_x = source_cx+ox;
const dest_x = cx+ox;
if (dest_x >= self.width) {
continue;
}
if (source_x >= source.width) {
continue;
}
const source_pixel_index = source_y * source.width + source_x;
const source_pixel = source.pixels.rgba8[(4*source_pixel_index)..][0..4];
const dest_pixel_index = dest_y * self.width + dest_x;
const dest_pixel = self.pixels.rgba8[(4*dest_pixel_index)..][0..4];
@memcpy(dest_pixel, source_pixel);
}
}
}
};
pub const Texture = struct {
image: sg.Image,
view: sg.View,
const max_textures = 64;
const SlotMap = SlotMapType(std.math.IntFittingRange(0, max_textures-1), u8, Texture);
pub const Id = SlotMap.Id;
};
pub const Sprite = struct {
data: ?ImageData,
position: ?Vec2,
const max_sprites = 128;
const SlotMap = SlotMapType(std.math.IntFittingRange(0, max_sprites-1), u8, Sprite);
pub const Id = SlotMap.Id;
};
pub const Vertex = extern struct {
@ -451,14 +789,18 @@ pub const Quad = extern struct {
}
}
pub fn setUVRect(self: *Quad, pos: Vec2, size: Vec2) void {
pub fn setUVRect(self: *Quad, rect: Rect) void {
const pos = rect.pos;
const size = rect.size;
self.vertices[0].texcoord = pos;
self.vertices[1].texcoord = pos.add(.init(size.x, 0));
self.vertices[2].texcoord = pos.add(.init(0, size.y));
self.vertices[3].texcoord = pos.add(size);
}
pub fn setRect(self: *Quad, pos: Vec2, size: Vec2) void {
pub fn setRect(self: *Quad, rect: Rect) void {
const pos = rect.pos;
const size = rect.size;
self.vertices[0].position = pos;
self.vertices[1].position = pos.add(.init(size.x, 0));
self.vertices[2].position = pos.add(.init(0, size.y));

View File

@ -12,10 +12,11 @@ const sg = sokol.gfx;
const sglue = sokol.glue;
const sgl = sokol.gl;
const Math = @import("math");
const Vec2 = Math.Vec2;
const Vec4 = Math.Vec4;
const Mat4 = Math.Mat4;
pub const Math = @import("math");
pub const Vec2 = Math.Vec2;
pub const Vec4 = Math.Vec4;
pub const Mat4 = Math.Mat4;
pub const Rect = Math.Rect;
const tracy = @import("tracy");

View File

@ -13,7 +13,7 @@ const Allocator = std.mem.Allocator;
///
/// For more details about this data structure checkout this podcast episode from Wookash:
/// * https://www.youtube.com/watch?v=ShSGHb65f3M
pub fn SlotMapType(Index: type, Generation: type) type {
pub fn SlotMapType(Index: type, Generation: type, Value: type) type {
assert(@typeInfo(Generation) == .int);
assert(@typeInfo(Index) == .int);
@ -28,6 +28,7 @@ pub fn SlotMapType(Index: type, Generation: type) type {
//
// So in the end idk if it's worth it.
pub const Slot = struct {
value: Value,
generation: Generation,
next_hole: ?Index,
used: bool
@ -109,6 +110,7 @@ pub fn SlotMapType(Index: type, Generation: type) type {
const index: Index = @intCast(self.slots.items.len);
self.slots.items.len += 1;
self.slots.items[index] = Slot{
.value = undefined,
.generation = 0,
.next_hole = null,
.used = false
@ -171,6 +173,11 @@ pub fn SlotMapType(Index: type, Generation: type) type {
return slot.used and slot.generation == id.generation;
}
pub fn getAssumeExists(self: *Self, id: Id) *Value {
assert(self.exists(id));
return &self.slots.items[id.index].value;
}
pub fn removeAssumeExists(self: *Self, id: Id) void {
assert(self.exists(id));
@ -209,7 +216,7 @@ pub fn SlotMapType(Index: type, Generation: type) type {
};
}
const TestMap = SlotMapType(u24, u8);
const TestMap = SlotMapType(u24, u8, void);
// TODO: Add more rigorous test for check if generation usage is nicely distributed.