const std = @import("std");
const assert = std.debug.assert;

const zcs = @import("root.zig");
const typeId = zcs.typeId;
const Entity = zcs.Entity;
const Any = zcs.Any;
const TypeId = zcs.TypeId;
const CmdBuf = zcs.CmdBuf;
const Entities = zcs.Entities;

/// An unencoded representation of command buffer commands.
pub const Subcmd = union(enum) {
    /// Binds an existing entity.
    bind_entity: Entity,
    /// Destroys the bound entity.
    destroy: void,
    /// Queues a component to be added by value. The type ID is passed as an argument, component
    /// data is passed via any bytes.
    add_val: Any,
    /// Queues a component to be added by pointer. The type ID and a pointer to the component data
    /// are passed as arguments.
    add_ptr: Any,
    /// Queues an extension command to be added by value. The type ID is passed as an argument, the
    /// payload is passed via any bytes.
    ext_val: Any,
    /// Queues an extension command to be added by pointer. The type ID and a pointer to the
    /// component data are passed as arguments.
    ext_ptr: Any,
    /// Queues a component to be removed.
    remove: TypeId,

    /// If a new worst case command is introduced, also update the tests!
    pub const rename_when_changing_encoding = {};

    pub const Tag = @typeInfo(@This()).@"union".tag_type.?;

    /// Decodes encoded commands.
    pub const Decoder = struct {
        cb: *const CmdBuf,
        tag_index: usize = 0,
        arg_index: usize = 0,
        comp_bytes_index: usize = 0,

        pub inline fn next(self: *@This()) ?Subcmd {
            _ = rename_when_changing_encoding;

            // Decode the next command
            if (self.nextTag()) |tag| {
                switch (tag) {
                    .bind_entity => {
                        const entity: Entity = @bitCast(self.nextArg().?);
                        return .{ .bind_entity = entity };
                    },
                    inline .add_val, .ext_val => |add| {
                        @setEvalBranchQuota(2000);
                        const id: TypeId = @ptrFromInt(self.nextArg().?);
                        const ptr = self.nextAny(id);
                        const any: Any = .{
                            .id = id,
                            .ptr = ptr,
                        };
                        return switch (add) {
                            .add_val => .{ .add_val = any },
                            .ext_val => .{ .ext_val = any },
                            else => comptime unreachable,
                        };
                    },
                    inline .add_ptr, .ext_ptr => |add| {
                        const id: TypeId = @ptrFromInt(self.nextArg().?);
                        const ptr: *const anyopaque = @ptrFromInt(self.nextArg().?);
                        const any: Any = .{
                            .id = id,
                            .ptr = ptr,
                        };
                        switch (add) {
                            .add_ptr => return .{ .add_ptr = any },
                            .ext_ptr => return .{ .ext_ptr = any },
                            else => comptime unreachable,
                        }
                    },
                    .remove => {
                        const id: TypeId = @ptrFromInt(self.nextArg().?);
                        return .{ .remove = id };
                    },
                    .destroy => return .destroy,
                }
            }

            // Assert that we're fully empty, and return null
            assert(self.tag_index == self.cb.tags.items.len);
            assert(self.arg_index == self.cb.args.items.len);
            assert(self.comp_bytes_index == self.cb.data.items.len);
            return null;
        }

        pub inline fn peekTag(self: *@This()) ?Subcmd.Tag {
            if (self.tag_index < self.cb.tags.items.len) {
                return self.cb.tags.items[self.tag_index];
            } else {
                @branchHint(.unlikely);
                return null;
            }
        }

        pub inline fn nextTag(self: *@This()) ?Subcmd.Tag {
            const tag = self.peekTag() orelse return null;
            self.tag_index += 1;
            return tag;
        }

        pub inline fn nextArg(self: *@This()) ?u64 {
            if (self.arg_index < self.cb.args.items.len) {
                const arg = self.cb.args.items[self.arg_index];
                self.arg_index += 1;
                return arg;
            } else {
                return null;
            }
        }

        pub

    };

    /// Encode adding a component to an entity by value.
    pub fn encodeAddVal(cb: *CmdBuf, entity: Entity, T: type, comp: T) error{ZcsCmdBufOverflow}!void {
        try Subcmd.encodeBind(cb, entity);
        try Subcmd.encodeVal(cb, .add_val, T, comp);
    }

    /// Encode adding a component to an entity by pointer.
    pub fn encodeAddPtr(cb: *CmdBuf, entity: Entity, T: type, comp: *const T) error{ZcsCmdBufOverflow}!void {
        try Subcmd.encodeBind(cb, entity);
        try Subcmd.encodePtr(cb, .add_ptr, T, comp);
    }

    /// Encode an extension command by value.
    pub fn encodeExtVal(cb: *CmdBuf, T: type, payload: T) error{ZcsCmdBufOverflow}!void {
        // Clear the binding. Archetype changes must start with a bind so we don't want it to be
        // cached across other commands.
        cb.binding = .none;
        try Subcmd.encodeVal(cb, .ext_val, T, payload);
    }

    /// Encode an extension command by pointer.
    pub fn encodeExtPtr(cb: *CmdBuf, T: type, payload: *const T) error{ZcsCmdBufOverflow}!void {
        // Clear the binding. Archetype changes must start with a bind so we don't want it to be
        // cached across other commands.
        cb.binding = .none;
        try Subcmd.encodePtr(cb, .ext_ptr, T, payload);
    }

    /// Encode removing a component from an entity.
    pub fn encodeRemove(cb: *CmdBuf, entity: Entity, id: TypeId) error{ZcsCmdBufOverflow}!void {
        errdefer if (std.debug.runtime_safety) {
            cb.invalid = true;
        };
        try Subcmd.encodeBind(cb, entity);
        if (cb.binding.destroyed) return;
        if (cb.tags.items.len >= cb.tags.capacity) return error.ZcsCmdBufOverflow;
        if (cb.args.items.len >= cb.args.capacity) return error.ZcsCmdBufOverflow;
        cb.tags.appendAssumeCapacity(.remove);
        cb.args.appendAssumeCapacity(@intFromPtr(id));
    }

    /// Encode committing an entity.
    pub fn encodeCommit(cb: *CmdBuf, entity: Entity) error{ZcsCmdBufOverflow}!void {
        try encodeBind(cb, entity);
    }

    /// Encode destroying an entity.
    pub fn encodeDestroy(cb: *CmdBuf, entity: Entity) error{ZcsCmdBufOverflow}!void {
        errdefer if (std.debug.runtime_safety) {
            cb.invalid = true;
        };
        try Subcmd.encodeBind(cb, entity);
        if (cb.binding.destroyed) return;
        if (cb.tags.items.len >= cb.tags.capacity) return error.ZcsCmdBufOverflow;
        cb.tags.appendAssumeCapacity(.destroy);
        cb.binding.destroyed = true;
    }

    /// Encode binding an entity as part of a subcommand.
    fn encodeBind(cb: *CmdBuf, entity: Entity) error{ZcsCmdBufOverflow}!void {
        errdefer if (std.debug.runtime_safety) {
            cb.invalid = true;
        };
        if (cb.binding.entity != entity.toOptional()) {
            if (cb.tags.items.len >= cb.tags.capacity) return error.ZcsCmdBufOverflow;
            cb.binding = .{ .entity = entity.toOptional() };
            cb.tags.appendAssumeCapacity(.bind_entity);
            cb.args.appendAssumeCapacity(@bitCast(entity));
        }
    }

    /// Encode a value as part of a subcommand.
    fn encodeVal(cb: *CmdBuf, tag: Tag, T: type, val: T) error{ZcsCmdBufOverflow}!void {
        errdefer if (std.debug.runtime_safety) {
            cb.invalid = true;
        };

        if (cb.binding.destroyed) return;

        const aligned = std.mem.alignForward(usize, cb.data.items.len, @alignOf(T));
        if (cb.tags.items.len >= cb.tags.capacity) return error.ZcsCmdBufOverflow;
        if (aligned + @sizeOf(T) > cb.data.capacity) return error.ZcsCmdBufOverflow;
        cb.tags.appendAssumeCapacity(tag);
        cb.args.appendAssumeCapacity(@intFromPtr(typeId(T)));

        cb.data.items.len = aligned;
        cb.data.appendSliceAssumeCapacity(std.mem.asBytes(&val));
    }

    /// Encode a pointer as part of a subcommand.
    fn encodePtr(cb: *CmdBuf, tag: Tag, T: type, ptr: *const T) error{ZcsCmdBufOverflow}!void {
        errdefer if (std.debug.runtime_safety) {
            cb.invalid = true;
        };

        if (cb.binding.destroyed) return;

        if (cb.tags.items.len >= cb.tags.capacity) return error.ZcsCmdBufOverflow;
        if (cb.args.items.len + 2 > cb.args.capacity) return error.ZcsCmdBufOverflow;

        cb.tags.appendAssumeCapacity(tag);
        cb.args.appendAssumeCapacity(@intFromPtr(typeId(T)));
        cb.args.appendAssumeCapacity(@intFromPtr(ptr));
    }
};

inline fn nextAny(self: *@This(), id: TypeId) *const anyopaque {
            self.comp_bytes_index = std.mem.alignForward(
                usize,
                self.comp_bytes_index,
                id.alignment,
            );
            const bytes = &self.cb.data.items[self.comp_bytes_index..][0..id.size];
            self.comp_bytes_index += id.size;
            return bytes.ptr;
        }

//! Buffers ECS commands for later execution.
//!
//! This allows queuing destructive operations while iterating, or from multiple threads safely by
//! assigning each thread its own command buffer. All commands are noops if the entity in question
//! is destroyed before the time of execution.
//!
//! `CmdBuf` allocates at init time, and then never again. It should be cleared and reused when
//! possible.
//!
//! All exec methods may invalidate iterators, but by convention only the high level
//! `execImmediate*` explicitly calls `es.pointer_generation.increment()`. If you are writing your
//! own exec function, you should call this yourself.
//!
//! Some command buffer modifications leave the command buffer in an undefined state on failure, this
//! is documented on the relevant functions. Trying to execute a command buffer invalidated in this
//! way results in safety checked illegal behavior. This design means that these functions are
//! typically not useful in real applications since you can't recover the CB after the error, rather
//! they're intended for tests. While it would be possible to preserve valid state by restoring the
//! lengths, this doesn't tend to be useful for real applications and substantially affects
//! performance in benchmarks.

const std = @import("std");
const tracy = @import("tracy");
const zcs = @import("root.zig");

const log = std.log;

const meta = zcs.meta;

const assert = std.debug.assert;
const Allocator = std.mem.Allocator;

const Zone = tracy.Zone;

const Entities = zcs.Entities;
const Entity = zcs.Entity;
const Any = zcs.Any;
const TypeId = zcs.TypeId;
const CompFlag = zcs.CompFlag;

const CmdBuf = @This();
const Subcmd = @import("subcmd.zig").Subcmd;

const Binding = struct {
    pub const none: @This() = .{ .entity = .none };
    entity: Entity.Optional = .none,
    destroyed: bool = false,
};

name: ?[:0]const u8,
tags: std.ArrayListUnmanaged(Subcmd.Tag),
args: std.ArrayListUnmanaged(u64),
data: std.ArrayListAlignedUnmanaged(u8, zcs.TypeInfo.max_align),
binding: Binding = .{ .entity = .none },
reserved: std.ArrayListUnmanaged(Entity),
invalid: if (std.debug.runtime_safety) bool else void,
warn_ratio: f32,

/// Options for `init`.
pub const InitOptions = struct {
    /// The debug name for this command buffer.
    ///
    /// Used in warnings and plots, see `updateStats`.
    name: ?[:0]const u8,
    /// Used to allocate the command buffer.
    gpa: Allocator,
    /// Entities are reserved from here.
    es: *Entities,
    /// The capacity of the buffer.
    cap: Capacity = .{},
    /// If more than this ratio of the command buffer is used as measured by `worstCaseUsage`, a
    /// warning will be emitted by `Exec.checkStats`.
    warn_ratio: f32 = 0.2,
};

/// The capacity of a command buffer.
pub const Capacity = struct {
    /// The default number of commands to reserve.
    pub const default_cmds = 100000;
    /// By default, `cmds / entities_ratio` commands are reserved.
    pub const entities_ratio = 4;

    /// Space for at least this many commands will be reserved. If `null`, allocates `default_cmds`.
    ///
    /// Being optional allows `CmdPool` to override this default with a lower value.
    cmds: ?usize = null,
    /// Space for at least this much command data will be reserved. Keep in mind that padding may
    /// vary per platform.
    data: union(enum) {
        bytes: usize,
        bytes_per_cmd: u32,
    } = .{ .bytes_per_cmd = 2 * 16 * @sizeOf(f32) },
    /// Sets the number of entities to reserve up front. See `entities_ratio` for the `null` case.
    reserved_entities: ?usize = null,

    /// Returns the number of reserved commands requested.
    fn getCmds(self: @This()) usize {
        return self.cmds orelse default_cmds;
    }

    /// Returns the number of data bytes requested.
    fn dataBytes(self: @This()) usize {
        return switch (self.data) {
            .bytes => |bytes| bytes,
            .bytes_per_cmd => |bytes_per_cmd| self.getCmds() * bytes_per_cmd,
        };
    }

    /// Returns the number of reserved entities requested.
    fn reservedEntities(self: @This()) usize {
        return self.reserved_entities orelse self.getCmds() / entities_ratio;
    }
};

/// Initializes a command buffer.
pub fn init(options: InitOptions) error{ OutOfMemory, ZcsEntityOverflow }!@This() {
    const zone = Zone.begin(.{ .src = @src() });
    defer zone.end();
    comptime assert(CompFlag.max < std.math.maxInt(u64));

    // Each command can have at most two tags
    _ = Subcmd.rename_when_changing_encoding;
    const cmds_cap = options.cap.getCmds() * 2;
    const tags_zone = Zone.begin(.{ .name = "tags", .src = @src() });
    var tags: std.ArrayListUnmanaged(Subcmd.Tag) = try .initCapacity(options.gpa, cmds_cap);
    errdefer tags.deinit(options.gpa);
    tags_zone.end();

    // Each command can have at most 3 args (the add ptr subcommand does a bind which has one
    // arg if it's not skipped, and then it also passes the component ID and pointer as args as
    // well.
    _ = Subcmd.rename_when_changing_encoding;
    const args_zone = Zone.begin(.{ .name = "args", .src = @src() });
    const args_cap = cmds_cap * 3;
    var args: std.ArrayListUnmanaged(u64) = try .initCapacity(options.gpa, args_cap);
    errdefer args.deinit(options.gpa);
    args_zone.end();

    const any_bytes_zone = Zone.begin(.{ .name = "any bytes", .src = @src() });
    var data: std.ArrayListAlignedUnmanaged(u8, zcs.TypeInfo.max_align) = try .initCapacity(
        options.gpa,
        options.cap.dataBytes(),
    );
    errdefer data.deinit(options.gpa);
    any_bytes_zone.end();

    const reserved_zone = Zone.begin(.{ .name = "reserved", .src = @src() });
    var reserved: std.ArrayListUnmanaged(Entity) = try .initCapacity(
        options.gpa,
        options.cap.reservedEntities(),
    );
    errdefer reserved.deinit(options.gpa);
    for (0..reserved.capacity) |_| {
        reserved.appendAssumeCapacity(try Entity.reserveImmediateOrErr(options.es));
    }
    reserved_zone.end();

    if (tracy.enabled) {
        if (options.name) |name| {
            tracy.plotConfig(.{
                .name = name,
                .format = .percentage,
                .mode = .line,
                .fill = true,
            });
        }
    }

    return .{
        .name = options.name,
        .reserved = reserved,
        .tags = tags,
        .args = args,
        .data = data,
        .invalid = if (std.debug.runtime_safety) false else {},
        .warn_ratio = options.warn_ratio,
    };
}

/// Destroys the command buffer.
pub fn deinit(self: *@This(), gpa: Allocator, es: *Entities) void {
    for (self.reserved.items) |entity| assert(entity.destroyImmediate(es));
    self.reserved.deinit(gpa);
    self.data.deinit(gpa);
    self.args.deinit(gpa);
    self.tags.deinit(gpa);
    self.* = undefined;
}

/// Appends an extension command to the buffer.
///
/// See notes on `Entity.add` with regards to performance and pass by value vs pointer.
pub inline fn ext(self: *@This(), T: type, payload: T) void {
    // Don't get tempted to remove inline from here! It's required for `isComptimeKnown`.
    comptime assert(@typeInfo(@TypeOf(ext)).@"fn".calling_convention == .@"inline");
    self.extOrErr(T, payload) catch |err|
        @panic(@errorName(err));
}

/// Similar to `ext`, but returns an error on failure instead of panicking. The command buffer is
/// left in an undefined state on error, see the top level documentation for more detail.
pub inline fn extOrErr(self: *@This(), T: type, payload: T) error{ZcsCmdBufOverflow}!void {
    // Don't get tempted to remove inline from here! It's required for `isComptimeKnown`.
    comptime assert(@typeInfo(@TypeOf(extOrErr)).@"fn".calling_convention == .@"inline");
    if (@sizeOf(T) > @sizeOf(*T) and meta.isComptimeKnown(payload)) {
        const Interned = struct {
            const value = payload;
        };
        try self.extPtr(T, comptime &Interned.value);
    } else {
        try self.extVal(T, payload);
    }
}

/// Similar to `extOrErr`, but forces the command to be copied by value to the command buffer.
/// Prefer `ext`.
pub fn extVal(self: *@This(), T: type, payload: T) error{ZcsCmdBufOverflow}!void {
    try Subcmd.encodeExtVal(self, T, payload);
}

/// Similar to `extOrErr`, but forces the command to be copied by pointer to the command buffer.
/// Prefer `ext`.
pub fn extPtr(self: *@This(), T: type, payload: *const T) error{ZcsCmdBufOverflow}!void {
    try Subcmd.encodeExtPtr(self, T, payload);
}

/// Clears the command buffer for reuse and then refills the reserved entity list to capacity.
/// Called automatically from `Exec.immediate`.
pub fn clear(self: *@This(), es: *Entities) void {
    self.clearOrErr(es) catch |err|
        @panic(@errorName(err));
}

/// Similar to `clear`, but returns `error.ZcsEntityOverflow` when failing to refill the reserved
/// entity list instead of panicking.
pub fn clearOrErr(self: *@This(), es: *Entities) error{ZcsEntityOverflow}!void {
    const zone = Zone.begin(.{ .src = @src() });
    defer zone.end();
    self.data.clearRetainingCapacity();
    self.args.clearRetainingCapacity();
    self.tags.clearRetainingCapacity();
    self.binding = .none;
    while (self.reserved.items.len < self.reserved.capacity) {
        self.reserved.appendAssumeCapacity(try Entity.reserveImmediateOrErr(es));
    }
}

/// Returns true if this command buffer is empty.
pub fn isEmpty(self: @This()) bool {
    return self.tags.items.len == 0 and self.reserved.items.len == self.reserved.capacity;
}

/// Returns the ratio of length to capacity for the internal buffer that is the nearest to being
/// full. Ignores buffers explicitly initialized with 0 capacity.
pub fn worstCaseUsage(self: @This()) f32 {
    const reserved_used: f32 = @floatFromInt(self.reserved.capacity - self.reserved.items.len);
    const reserved_usage = if (self.reserved.capacity == 0)
        0.0
    else
        reserved_used / @as(f32, @floatFromInt(self.reserved.capacity));
    return @max(
        usage(self.data),
        usage(self.args),
        usage(self.tags),
        reserved_usage,
    );
}

/// Calculates the usage of a list as a ratio.
fn usage(list: anytype) f32 {
    if (list.capacity == 0) return 0.0;
    return @as(f32, @floatFromInt(list.items.len)) / @as(f32, @floatFromInt(list.capacity));
}

/// Returns an iterator over the encoded commands.
pub fn iterator(self: *const @This()) Iterator {
    if (std.debug.runtime_safety) assert(!self.invalid);
    return .{ .decoder = .{ .cb = self } };
}

/// Emits a warning if over `warn_ratio`. If Tracy is enabled and this command buffer has a name,
/// update its plots. Called automatically by `Exec`.
pub fn updateStats(self: *const CmdBuf) void {
    const currentUsage = self.worstCaseUsage();

    if (tracy.enabled) {
        if (self.name) |name| {
            tracy.plot(.{
                .name = name,
                .value = .{ .f32 = currentUsage * 100.0 },
            });
        }
    }

    if (currentUsage > self.warn_ratio) {
        log.warn("command buffer past 50% capacity (buffer name = {?s})", .{self.name});
    }
}

pub const Exec = struct {
    zone: Zone,
    zone_cmd_exec: zcs.ext.ZoneCmd.Exec = .{},

    pub fn init() @This() {
        return .{
            .zone = Zone.begin(.{
                .src = @src(),
            }),
        };
    }

    /// Executes the command buffer and then clears it.
    ///
    /// Invalidates pointers.
    pub fn immediate(es: *Entities, cb: *CmdBuf) void {
        immediateOrErr(es, cb) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `execImmediate`, but returns an error on failure instead of panicking. On error, the
    /// command buffer will be partially executed.
    pub fn immediateOrErr(
        es: *Entities,
        cb: *CmdBuf,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow, ZcsEntityOverflow }!void {
        var self: @This() = .init();

        es.pointer_generation.increment();
        var iter = cb.iterator();
        while (iter.next()) |batch| {
            switch (batch) {
                .arch_change => |arch_change| {
                    const delta = arch_change.deltaImmediate();
                    _ = try arch_change.execImmediateOrErr(es, delta);
                },
                .ext => |payload| self.extImmediateOrErr(payload),
            }
        }

        try self.finish(cb, es);
    }

    pub fn extImmediateOrErr(self: *@This(), payload: Any) void {
        self.zone_cmd_exec.extImmediate(payload);
    }

    pub fn finish(self: *@This(), cb: *CmdBuf, es: *Entities) error{ZcsEntityOverflow}!void {
        cb.updateStats();
        cb.clear(es);
        self.zone_cmd_exec.finish();
        self.zone.end();
    }
};

/// A batch of sequential commands that all have the same entity bound.
pub const Batch = union(enum) {
    ext: Any,
    arch_change: ArchChange,

    pub const ArchChange = struct {
        /// A description of delta over all archetype change operations.
        pub const Delta = struct {
            add: CompFlag.Set = .{},
            remove: CompFlag.Set = .{},
            destroy: bool = false,

            /// Updates the delta for a given operation.
            pub inline fn updateImmediate(self: *@This(), op: Op) void {
                switch (op) {
                    .add => |comp| {
                        const flag = CompFlag.registerImmediate(comp.id);
                        self.add.insert(flag);
                        self.remove.remove(flag);
                    },
                    .remove => |id| {
                        const flag = CompFlag.registerImmediate(id);
                        self.add.remove(flag);
                        self.remove.insert(flag);
                    },
                    .destroy => self.destroy = true,
                }
            }
        };

        /// The bound entity.
        entity: Entity,
        decoder: Subcmd.Decoder,

        /// Returns the delta over all archetype change operations.
        pub inline fn deltaImmediate(self: @This()) Delta {
            var delta: Delta = .{};
            var ops = self.iterator();
            while (ops.next()) |op| delta.updateImmediate(op);
            return delta;
        }

        /// An iterator over this batch's commands.
        pub inline fn iterator(self: @This()) @This().Iterator {
            return .{
                .decoder = self.decoder,
            };
        }

        /// Executes the batch. Returns true if the entity exists before the command is run, false
        /// otherwise.
        ///
        /// See `getArchChangeImmediate` to get the default archetype change argument.
        pub fn execImmediate(self: @This(), es: *Entities, delta: Delta) bool {
            return self.execImmediateOrErr(es, delta) catch |err|
                @panic(@errorName(err));
        }

        /// Similar to `execImmediate`, but returns an error on overflow instead of panicking.
        pub inline fn execImmediateOrErr(
            self: @This(),
            es: *Entities,
            delta: Delta,
        ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow }!bool {
            if (delta.destroy) return self.entity.destroyImmediate(es);

            // Issue the change archetype command.  If no changes were requested, this will
            // still commit the entity. If the entity doesn't exist, early out.
            if (!try self.entity.changeArchUninitImmediateOrErr(es, .{
                .add = delta.add,
                .remove = delta.remove,
            })) {
                return false;
            }

            // Initialize any new components. Note that we check for existence on each because they
            // could have been subsequently removed.
            {
                // Unwrapping these is fine since we just committed the entity if it wasn't
                // committed, or earlied out if it doesn't exist
                const entity_loc = es.handle_tab.get(self.entity.key).?;
                const chunk = entity_loc.chunk.get(&es.chunk_pool).?;
                var ops = self.iterator();
                while (ops.next()) |op| {
                    switch (op) {
                        .add => |comp| if (comp.id.comp_flag) |flag| {
                            const offset = chunk.header()
                                .comp_buf_offsets.values[@intFromEnum(flag)];
                            if (offset != 0) {
                                assert(offset != 0);
                                const dest_unsized: [*]u8 = @ptrFromInt(@intFromPtr(chunk) +
                                    offset +
                                    @intFromEnum(entity_loc.index_in_chunk) * comp.id.size);
                                const dest = dest_unsized[0..comp.id.size];
                                @memcpy(dest, comp.bytes());
                            }
                        },
                        .remove,
                        .destroy,
                        => {},
                    }
                }
            }

            return true;
        }

        /// An archetype change operation.
        pub const Op = union(enum) {
            add: Any,
            remove: TypeId,
            destroy: void,
        };

        /// An iterator over this batch's commands.
        ///
        /// Note that the encoder will elide operations immediately following a destroy. This is
        /// intended to simplify writing extensions.
        pub const Iterator = struct {
            decoder: Subcmd.Decoder,

            /// Returns the next operation, or `null` if there are none.
            pub inline fn next(self: *@This()) ?Op {
                while (self.decoder.peekTag()) |tag| {
                    const op: Op = switch (tag) {
                        .add_val => .{ .add = self.decoder.next().?.add_val },
                        .add_ptr => .{ .add = self.decoder.next().?.add_ptr },
                        .remove => .{ .remove = self.decoder.next().?.remove },
                        .destroy => b: {
                            _ = self.decoder.next().?.destroy;
                            break :b .destroy;
                        },
                        .bind_entity, .ext_val, .ext_ptr => break,
                    };

                    // Return the operation.
                    return op;
                }
                return null;
            }
        };
    };
};

/// An iterator over batches of encoded commands.
pub const Iterator = struct {
    decoder: Subcmd.Decoder,

    /// Returns the next command batch, or `null` if there is none.
    pub inline fn next(self: *@This()) ?Batch {
        _ = Subcmd.rename_when_changing_encoding;

        // We just return bind operations here, `Subcmd` handles the add/remove commands.
        while (self.decoder.next()) |cmd| {
            switch (cmd) {
                // We buffer all ops on a given entity into a single command
                .bind_entity => |entity| return .{ .arch_change = .{
                    .entity = entity,
                    .decoder = self.decoder,
                } },
                .ext_val, .ext_ptr => |payload| return .{ .ext = payload },
                // These are handled by archetype change. We always start archetype change with a
                // bind so this will never miss ops.
                .add_ptr,
                .add_val,
                .remove,
                .destroy,
                => {},
            }
        }

        return null;
    }
};

const std = @import("std");
const assert = std.debug.assert;

const zcs = @import("root.zig");

const typeId = zcs.typeId;

const Subcmd = @import("subcmd.zig").Subcmd;

const Entities = zcs.Entities;
const PointerLock = zcs.PointerLock;
const Any = zcs.Any;
const CompFlag = zcs.CompFlag;
const CmdBuf = zcs.CmdBuf;
const TypeId = zcs.TypeId;
const HandleTab = zcs.HandleTab;
const Chunk = zcs.Chunk;
const ChunkPool = zcs.ChunkPool;
const ChunkList = zcs.storage.ChunkList;
const meta = zcs.meta;

/// An entity.
///
/// Entity handles are persistent, you can check if an entity has been destroyed via
/// `Entity.exists`. This is useful for dynamic systems like games where object lifetime may depend
/// on user input.
///
/// Methods that take a command buffer append the command to a command buffer for execution at a
/// later time. Methods with `immediate` in the name are executed immediately, usage of these is
/// discouraged as they are not valid while iterating unless otherwise noted and are not thread
/// safe.
pub const Entity = packed struct {
    /// An entity without its generation. Used by some lower level APIs to save space where the
    /// safety of the generation is not needed, should not be stored long term outside of the ECS.
    pub const Index = enum(@FieldType(HandleTab.Key, "index")) {
        _,

        /// Converts the entity index to an `Entity` with the corresponding generation. Assumes that
        /// the index is not dangling, this cannot be reliably enforced without the generation.
        pub fn toEntity(self: @This(), es: *const Entities) Entity {
            const result: Entity = .{ .key = .{
                .index = @intFromEnum(self),
                .generation = es.handle_tab.slots[@intFromEnum(self)].generation,
            } };
            assert(result.key.generation != .invalid);
            assert(result.key.index < es.handle_tab.next_index);
            return result;
        }
    };

    /// The location an entity is stored.
    ///
    /// This indirection allows entities to be relocated without invalidating their handles.
    pub const Location = struct {
        /// The index of an entity in a chunk.
        pub const IndexInChunk = enum(u32) { _ };

        /// A handle that's been reserved but not committed.
        pub const reserved: @This() = .{
            .chunk = .none,
            .index_in_chunk = if (std.debug.runtime_safety)
                @enumFromInt(std.math.maxInt(@typeInfo(IndexInChunk).@"enum".tag_type))
            else
                undefined,
        };

        /// The chunk where this entity is stored, or `null` if it hasn't been committed.
        chunk: Chunk.Index = .none,
        /// The entity's index in the chunk, value is unspecified if not committed.
        index_in_chunk: IndexInChunk,

        /// Returns the archetype for this entity, or the empty archetype if it hasn't been
        /// committed.
        pub fn arch(self: @This(), es: *const Entities) CompFlag.Set {
            const chunk = self.chunk.get(&es.chunk_pool) orelse return .{};
            const header = chunk.header();
            return header.arch(&es.arches);
        }
    };

    pub const Optional = packed struct {
        pub const none: @This() = .{ .key = .none };

        key: HandleTab.Key.Optional,

        /// Unwraps the optional entity into `Entity`, or returns `null` if it is `.none`.
        pub fn unwrap(self: @This()) ?Entity {
            if (self.key.unwrap()) |key| return .{ .key = key };
            return null;
        }

        /// Default formatting.
        pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
            return self.key.format(writer);
        }
    };

    key: HandleTab.Key,

    /// Pops a reserved entity.
    ///
    /// A reserved entity is given a persistent key, but no storage. As such, it will behave like
    /// an empty entity, but not show up in iteration.
    ///
    /// You can commit a reserved entity explicitly with `commit`, but this isn't usually
    /// necessary as adding or attempting to remove a component implicitly commits the entity.
    pub fn reserve(cb: *CmdBuf) Entity {
        return reserveOrErr(cb) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `reserve`, but returns an error on failure instead of panicking.
    pub fn reserveOrErr(cb: *CmdBuf) error{ZcsReservedEntityUnderflow}!Entity {
        return cb.reserved.pop() orelse error.ZcsReservedEntityUnderflow;
    }

    /// Similar to `reserve`, but reserves a new entity instead of popping one from a command
    /// buffers reserve. Prefer `reserve`.
    ///
    /// This does not invalidate pointers, but it's not thread safe.
    pub fn reserveImmediate(es: *Entities) Entity {
        return reserveImmediateOrErr(es) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `reserveImmediate`, but returns `error.ZcsEntityOverflow` on failure instead of
    /// panicking.
    ///
    /// This does not invalidate pointers, but it's not thread safe.
    pub fn reserveImmediateOrErr(es: *Entities) error{ZcsEntityOverflow}!Entity {
        const pointer_lock = es.pointer_generation.lock();
        defer pointer_lock.check(es.pointer_generation);

        const key = es.handle_tab.put(.reserved) catch |err| switch (err) {
            error.Overflow => return error.ZcsEntityOverflow,
        };
        es.reserved_entities += 1;
        return .{ .key = key };
    }

    /// Queues an entity for destruction.
    ///
    /// Destroying an entity that no longer exists has no effect.
    pub fn destroy(self: @This(), cb: *CmdBuf) void {
        self.destroyOrErr(cb) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `destroy`, but returns `error.ZcsCmdBufOverflow` on failure instead of
    /// panicking. The command buffer is left in an undefined state on error, see the top level
    /// documentation on `CmdBuf` for more info.
    pub fn destroyOrErr(self: @This(), cb: *CmdBuf) error{ZcsCmdBufOverflow}!void {
        try Subcmd.encodeDestroy(cb, self);
    }

    /// Similar to `destroy`, but destroys the entity immediately. Prefer `destroy`.
    ///
    /// Invalidates pointers.
    pub fn destroyImmediate(self: @This(), es: *Entities) bool {
        es.pointer_generation.increment();
        if (es.handle_tab.get(self.key)) |entity_loc| {
            if (entity_loc.chunk.get(&es.chunk_pool)) |chunk| {
                chunk.swapRemove(es, entity_loc.index_in_chunk);
            } else {
                es.reserved_entities -= 1;
            }
            es.handle_tab.remove(self.key);
            entity_loc.* = undefined;
            return true;
        } else {
            return false;
        }
    }

    /// Returns true if the entity has not been destroyed.
    pub fn exists(self: @This(), es: *const Entities) bool {
        return es.handle_tab.containsKey(self.key);
    }

    /// Returns true if the entity exists and has been committed, otherwise returns false.
    pub fn committed(self: @This(), es: *const Entities) bool {
        const entity_loc = es.handle_tab.get(self.key) orelse return false;
        return entity_loc.chunk != .none;
    }

    /// Returns true if the entity has the given component type, false otherwise or if the entity
    /// has been destroyed.
    pub fn has(self: @This(), es: *const Entities, T: type) bool {
        return self.hasId(es, typeId(T));
    }

    /// Similar to `has`, but operates on component IDs instead of types.
    pub fn hasId(self: @This(), es: *const Entities, id: TypeId) bool {
        const flag = id.comp_flag orelse return false;
        return self.arch(es).contains(flag);
    }

    /// Retrieves the given component type. Returns null if the entity does not have this component
    /// or has been destroyed. See also `Entities.getComp`.
    pub fn get(self: @This(), es: *const Entities, T: type) ?*T {
        // We could use `compsFromId` here, but we a measurable performance improvement in
        // benchmarks by calculating the result directly
        const entity_loc = es.handle_tab.get(self.key) orelse return null;
        const chunk = entity_loc.chunk.get(&es.chunk_pool) orelse return null;
        const flag = typeId(T).comp_flag orelse return null;
        // https://github.com/Games-by-Mason/ZCS/issues/24
        const offset = chunk.header().comp_buf_offsets.values[@intFromEnum(flag)];
        if (offset == 0) return null;
        const comps_addr = @intFromPtr(chunk) + offset;
        const comp_addr = comps_addr + @sizeOf(T) * @intFromEnum(entity_loc.index_in_chunk);
        return @ptrFromInt(comp_addr);
    }

    /// Similar to `get`, but operates on component IDs instead of types.
    pub fn getId(self: @This(), es: *const Entities, id: TypeId) ?[]u8 {
        const entity_loc = es.handle_tab.get(self.key) orelse return null;
        const chunk = entity_loc.chunk.get(&es.chunk_pool) orelse return null;
        const comps = chunk.compsFromId(id) orelse return null;
        return comps[@intFromEnum(entity_loc.index_in_chunk) * id.size ..][0..id.size];
    }

    /// Queues a component to be added.
    ///
    /// Batching add/removes on the same entity in sequence is more efficient than alternating
    /// between operations on different entities.
    ///
    /// Will automatically pass the data by pointer if it's comptime known, and larger than pointer
    /// sized.
    ///
    /// Adding components to an entity that no longer exists has no effect.
    pub inline fn add(self: @This(), cb: *CmdBuf, T: type, comp: T) void {
        // Don't get tempted to remove inline from here! It's required for `isComptimeKnown`.
        comptime assert(@typeInfo(@TypeOf(add)).@"fn".calling_convention == .@"inline");
        self.addOrErr(cb, T, comp) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `add`, but returns an error on failure instead of panicking. The command buffer
    /// is left in an undefined state on error, see the top level documentation on `CmdBuf` for more
    /// info.
    pub inline fn addOrErr(
        self: @This(),
        cb: *CmdBuf,
        T: type,
        comp: T,
    ) error{ZcsCmdBufOverflow}!void {
        // Don't get tempted to remove inline from here! It's required for `isComptimeKnown`.
        comptime assert(@typeInfo(@TypeOf(addOrErr)).@"fn".calling_convention == .@"inline");
        if (@sizeOf(T) > @sizeOf(*T) and meta.isComptimeKnown(comp)) {
            const Interned = struct {
                const value = comp;
            };
            try self.addPtr(cb, T, comptime &Interned.value);
        } else {
            try self.addVal(cb, T, comp);
        }
    }

    /// Similar to `addOrErr`, but forces the component to be copied by value to the command buffer.
    /// Prefer `add`.
    pub fn addVal(self: @This(), cb: *CmdBuf, T: type, comp: T) error{ZcsCmdBufOverflow}!void {
        try Subcmd.encodeAddVal(cb, self, T, comp);
    }

    /// Similar to `addOrErr`, forces the component to be copied by pointer to the command buffer.
    /// Prefer `add`.
    pub fn addPtr(
        self: @This(),
        cb: *CmdBuf,
        T: type,
        comp: *const T,
    ) error{ZcsCmdBufOverflow}!void {
        try Subcmd.encodeAddPtr(cb, self, T, comp);
    }

    /// Queues the given component to be removed. Has no effect if the component is not present, or
    /// the entity no longer exists.
    ///
    /// See note on `add` with regards to performance.
    pub fn remove(self: @This(), cb: *CmdBuf, T: type) void {
        self.removeId(cb, typeId(T)) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `remove`, but doesn't require compile time types and returns an error on failure
    /// instead of panicking on failure. The command buffer is left in an undefined state on error,
    /// see the top level documentation on `CmdBuf` for more info.
    pub fn removeId(self: @This(), cb: *CmdBuf, id: TypeId) error{ZcsCmdBufOverflow}!void {
        try Subcmd.encodeRemove(cb, self, id);
    }

    /// Queues the entity to be committed. Has no effect if it has already been committed, called
    /// implicitly on add/remove/cmd. In practice only necessary when creating an empty entity.
    pub fn commit(self: @This(), cb: *CmdBuf) void {
        self.commitOrErr(cb) catch |err|
            @panic(@errorName(err));
    }
    /// Similar to `commit`, but returns `error.ZcsCmdBufOverflow` on failure instead of
    /// panicking. The command buffer is left in an undefined state on error, see the top level
    /// documentation on `CmdBuf` for more info.
    pub fn commitOrErr(self: @This(), cb: *CmdBuf) error{ZcsCmdBufOverflow}!void {
        try Subcmd.encodeCommit(cb, self);
    }

    /// Options for `changeArchImmediate`.
    pub fn ChangeArchImmediateOptions(Add: type) type {
        comptime var has_defaults = true;
        for (@typeInfo(Add).@"struct".fields) |field| {
            if (field.default_value_ptr == null) {
                has_defaults = false;
                break;
            }
        }
        if (has_defaults) {
            return struct {
                add: Add = .{},
                remove: CompFlag.Set = .{},
            };
        } else {
            return struct {
                add: Add,
                remove: CompFlag.Set = .{},
            };
        }
    }

    /// Adds the listed components and then removes the listed component IDs.
    ///
    /// `Add` is a tuple or struct of components that may be added by `changes`. They may be
    /// optional types to allow deciding whether or not to add them at runtime.
    ///
    /// Returns `true` if the change was made, returns `false` if it couldn't be made because the
    /// entity doesn't exist.
    ///
    /// Invalidates pointers.
    pub fn changeArchImmediate(
        self: @This(),
        es: *Entities,
        Add: type,
        changes: ChangeArchImmediateOptions(Add),
    ) bool {
        return self.changeArchImmediateOrErr(es, Add, changes) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `changeArchImmediate`, but returns an error on failure instead of panicking.
    pub fn changeArchImmediateOrErr(
        self: @This(),
        es: *Entities,
        Add: type,
        changes: ChangeArchImmediateOptions(Add),
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow }!bool {
        es.pointer_generation.increment();

        // Build a set of component types to add/remove
        var add_comps: CompFlag.Set = .{};
        inline for (@typeInfo(Add).@"struct".fields) |field| {
            const Comp = zcs.view.Unwrap(field.type);
            if (@typeInfo(field.type) != .optional or @field(changes.add, field.name) != null) {
                add_comps.insert(CompFlag.registerImmediate(typeId(Comp)));
            }
        }

        // Apply the archetype change, early out if the entity doesn't exist
        if (!try self.changeArchUninitImmediateOrErr(es, .{
            .add = add_comps,
            .remove = changes.remove,
        })) return false;

        const entity_loc = es.handle_tab.get(self.key).?;
        const new_chunk = entity_loc.chunk.get(&es.chunk_pool).?;
        inline for (@typeInfo(Add).@"struct".fields) |field| {
            const Comp = zcs.view.Unwrap(field.type);
            if (@typeInfo(field.type) != .optional or @field(changes.add, field.name) != null) {
                // Unwrapping the flag is safe because we already registered it above
                const flag = typeId(Comp).comp_flag.?;
                if (!changes.remove.contains(flag)) {
                    // https://github.com/Games-by-Mason/ZCS/issues/24
                    const offset = new_chunk.header().comp_buf_offsets.values[@intFromEnum(flag)];
                    // Safe because we added it above
                    assert(offset != 0);
                    const comp: *Comp = @ptrFromInt(@intFromPtr(new_chunk) +
                        offset +
                        @intFromEnum(entity_loc.index_in_chunk) * @sizeOf(Comp));
                    comp.* = @as(?Comp, @field(changes.add, field.name)).?;
                }
            }
        }

        return true;
    }

    /// Options for `changeArchAnyImmediate`.
    pub const ChangeArchAnyImmediateOptions = struct {
        add: []const Any = &.{},
        remove: CompFlag.Set = .initEmpty(),
    };

    /// Similar to `changeArchImmediateOrErr`, but doesn't require comptime types.
    pub fn changeArchAnyImmediate(
        self: @This(),
        es: *Entities,
        changes: ChangeArchAnyImmediateOptions,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow }!bool {
        es.pointer_generation.increment();

        // Build a set of component types to add/remove
        var add_comps: CompFlag.Set = .{};
        for (changes.add) |comp| {
            add_comps.insert(CompFlag.registerImmediate(comp.id));
        }

        // Apply the archetype change, early out if the entity doesn't exist
        if (!try self.changeArchUninitImmediateOrErr(es, .{
            .add = add_comps,
            .remove = changes.remove,
        })) return false;

        // Initialize the components
        const entity_loc = es.handle_tab.get(self.key).?;
        const chunk = entity_loc.chunk.get(&es.chunk_pool).?;
        for (changes.add) |comp| {
            // Unwrapping the flag is safe because we already registered it above
            const flag = comp.id.comp_flag.?;
            if (!changes.remove.contains(flag)) {
                // Unwrap okay because registered above
                // https://github.com/Games-by-Mason/ZCS/issues/24
                const offset = chunk.header().comp_buf_offsets.values[@intFromEnum(flag)];
                assert(offset != 0);
                const dest_unsized: [*]u8 = @ptrFromInt(@intFromPtr(chunk) +
                    offset +
                    @intFromEnum(entity_loc.index_in_chunk) * comp.id.size);
                const dest = dest_unsized[0..comp.id.size];
                @memcpy(dest, comp.bytes());
            }
        }

        return true;
    }

    /// Options for the uninitialized variants of change archetype.
    pub const ChangeArchUninitImmediateOptions = struct {
        /// Component types to remove.
        remove: CompFlag.Set = .{},
        /// Component types to add.
        add: CompFlag.Set = .{},
    };

    /// Similar to `changeArchetypeOrErr`, but does not initialize the components. Furthermore, any
    /// added component's values are considered undefined after this call, even if they were
    /// previously initialized.
    ///
    /// May change internal allocator state even on failure, chunk lists are not destroyed even if
    /// no chunks could be allocated for them at this time.
    ///
    /// Technically we could go slightly faster with compile time known types, similar to
    /// `changeArchImmediate` vs `changeArchAnyImmediate`. This works because `@memcpy` with a
    /// comptime known length tends to be a bit faster. However, in practice, these sorts of
    /// immediate arch changes are only done (or only done in bulk) when loading a level, which
    /// means this function is essentially a noop anyway since there won't be any data to move.
    pub fn changeArchUninitImmediateOrErr(
        self: @This(),
        es: *Entities,
        options: ChangeArchUninitImmediateOptions,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow }!bool {
        es.pointer_generation.increment();

        // Get the handle value and figure out the new arch
        const entity_loc = es.handle_tab.get(self.key) orelse return false;
        const old_chunk = entity_loc.chunk.get(&es.chunk_pool);
        const prev_arch = entity_loc.arch(es);
        var new_arch = prev_arch;
        new_arch = new_arch.unionWith(options.add);
        new_arch = new_arch.differenceWith(options.remove);

        // If the entity is committed and the arch hasn't changed, early out
        if (old_chunk) |chunk| {
            const chunk_header = chunk.header();
            if (chunk_header.arch(&es.arches).eql(new_arch)) {
                @branchHint(.unlikely);
                return true;
            }
        }

        // Get the new location. As mentioned in the doc comment, it's possible that we'll end up
        // creating a new chunk list but not be able to allocate any chunks for it.
        const chunk_list = try es.arches.getOrPut(&es.chunk_pool, new_arch);
        const new_loc = try chunk_list.append(es, self);
        const new_chunk = new_loc.chunk.get(&es.chunk_pool).?;
        errdefer comptime unreachable;

        // Initialize the new components to undefined
        if (std.debug.runtime_safety) {
            var added = options.add.differenceWith(options.remove).iterator();
            while (added.next()) |flag| {
                const id = flag.getId();
                const comp_buffer = new_chunk.compsFromId(id).?;
                const comp_offset = @intFromEnum(new_loc.index_in_chunk) * id.size;
                const comp = comp_buffer[comp_offset..][0..id.size];
                @memset(comp, undefined);
            }
        }

        // Copy components that aren't being overwritten from the old arch to the new one
        if (old_chunk) |prev_chunk| {
            var move = prev_arch.differenceWith(options.remove)
                .differenceWith(options.add).iterator();
            while (move.next()) |flag| {
                const id = flag.getId();

                const new_comp_buffer = new_chunk.compsFromId(id).?;
                const new_comp_offset = @intFromEnum(new_loc.index_in_chunk) * id.size;
                const new_comp = new_comp_buffer[new_comp_offset..][0..id.size];

                const prev_comp_buffer = prev_chunk.compsFromId(id).?;
                const prev_comp_offset = @intFromEnum(entity_loc.index_in_chunk) * id.size;
                const prev_comp = prev_comp_buffer[prev_comp_offset..][0..id.size];

                @memcpy(new_comp, prev_comp);
            }
        }

        // Commit the entity to the new location
        if (old_chunk) |chunk| {
            chunk.swapRemove(es, entity_loc.index_in_chunk);
        } else {
            es.reserved_entities -= 1;
        }
        entity_loc.* = new_loc;

        return true;
    }

    /// Returns this entity as an optional.
    pub fn toOptional(self: @This()) Optional {
        return .{ .key = self.key.toOptional() };
    }

    /// Initializes a `view`, returning `null` if this entity does not exist or is missing any
    /// required components.
    pub fn view(self: @This(), es: *const Entities, View: type) ?View {
        // Check if the entity has the requested components. If the number of components in the view
        // is very large, this measurably improves performance. The cost of the check when not
        // necessary doesn't appear to be measurable.
        const entity_loc = es.handle_tab.get(self.key) orelse return null;
        const chunk = entity_loc.chunk.get(&es.chunk_pool) orelse return null;
        const view_arch = zcs.view.comps(View, .{ .size = .one }) orelse return null;
        const entity_arch = chunk.header().list.arch(&es.arches);
        if (!entity_arch.supersetOf(view_arch)) return null;

        // Fill in the view and return it
        var result: View = undefined;
        inline for (@typeInfo(View).@"struct".fields) |field| {
            const Unwrapped = zcs.view.UnwrapField(field.type, .{ .size = .one });
            if (Unwrapped == Entity) {
                @field(result, field.name) = self;
            } else {
                // https://github.com/Games-by-Mason/ZCS/issues/24
                const offset = if (typeId(Unwrapped).comp_flag) |flag|
                    chunk.header().comp_buf_offsets.values[@intFromEnum(flag)]
                else
                    0;
                if (@typeInfo(field.type) == .optional and offset == 0) {
                    @field(result, field.name) = null;
                } else {
                    assert(offset != 0); // Archetype already checked
                    const comps_addr = @intFromPtr(chunk) + offset;
                    const comp_addr = comps_addr +
                        @intFromEnum(entity_loc.index_in_chunk) * @sizeOf(Unwrapped);
                    @field(result, field.name) = @ptrFromInt(comp_addr);
                }
            }
        }
        return result;
    }

    /// Similar to `viewOrAddImmediate`, but for a single component.
    pub fn getOrAddImmediate(
        self: @This(),
        es: *Entities,
        T: type,
        default: T,
    ) ?T {
        return self.getOrAddImmediateOrErr(es, T, default) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `getOrAddImmediate`, but for a single component.
    pub fn getOrAddImmediateOrErr(
        self: @This(),
        es: *Entities,
        T: type,
        default: T,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow }!?*T {
        const result = try self.viewOrAddImmediateOrErr(es, struct { *T }, .{&default}) orelse
            return null;
        return result[0];
    }

    /// Similar to `view`, but will attempt to fill in any non-optional missing components with
    /// the defaults from the `comps` view if present.
    ///
    /// Invalidates pointers.
    pub fn viewOrAddImmediate(
        self: @This(),
        es: *Entities,
        View: type,
        comps: anytype,
    ) ?View {
        return self.viewOrAddImmediateOrErr(es, View, comps) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `viewOrAddImmediate` but returns, but returns an error on failure instead of
    /// panicking.
    pub fn viewOrAddImmediateOrErr(
        self: @This(),
        es: *Entities,
        View: type,
        comps: anytype,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow }!?View {
        // Create the view, possibly leaving some components uninitialized
        const result = (try self.viewOrAddUninitImmediateOrErr(es, View)) orelse return null;

        // Fill in any uninitialized components and return the view
        inline for (@typeInfo(View).@"struct".fields) |field| {
            const Unwrapped = zcs.view.UnwrapField(field.type, .{ .size = .one });
            if (@hasField(@TypeOf(comps), field.name) and
                result.uninitialized.contains(typeId(Unwrapped).comp_flag.?))
            {
                @field(result.view, field.name).* = @field(comps, field.name).*;
            }
        }
        return result.view;
    }

    /// The result of a `viewOrAddUninit*` call.
    pub fn VoaUninitResult(View: type) type {
        return struct {
            uninitialized: CompFlag.Set,
            view: View,
        };
    }

    /// Similar to `viewOrAddImmediate`, but leaves the added components uninitialized.
    pub fn viewOrAddUninitImmediate(
        self: @This(),
        es: *Entities,
        View: type,
    ) ?VoaUninitResult(View) {
        return self.viewOrAddUninitImmediate(es, View) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `viewOrAddImmediate`, but returns an error on failure instead of panicking.
    pub fn viewOrAddUninitImmediateOrErr(
        self: @This(),
        es: *Entities,
        View: type,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow }!?VoaUninitResult(View) {
        es.pointer_generation.increment();

        // Figure out which components are missing
        const entity_loc = es.handle_tab.get(self.key) orelse return null;
        var view_arch: CompFlag.Set = .{};
        inline for (@typeInfo(View).@"struct".fields) |field| {
            if (field.type != Entity and @typeInfo(field.type) != .optional) {
                const Unwrapped = zcs.view.UnwrapField(field.type, .{ .size = .one });
                const flag = CompFlag.registerImmediate(typeId(Unwrapped));
                view_arch.insert(flag);
            }
        }
        const curr_arch = entity_loc.arch(es);
        const uninitialized = view_arch.differenceWith(curr_arch);
        if (!curr_arch.supersetOf(view_arch)) {
            assert(try self.changeArchUninitImmediateOrErr(es, .{ .add = uninitialized }));
        }

        // Create and return the view
        return .{
            .view = self.view(es, View).?,
            .uninitialized = uninitialized,
        };
    }

    /// Default formatting.
    pub fn format(self: @This(), writer: *std.Io.Writer) std.Io.Writer.Error!void {
        return self.key.format(writer);
    }

    /// Returns the archetype of the entity. If it has been destroyed or is not yet committed, the
    /// empty archetype will be returned.
    pub fn arch(self: @This(), es: *const Entities) CompFlag.Set {
        const entity_loc = es.handle_tab.get(self.key) orelse return .{};
        return entity_loc.arch(es);
    }
};

//! A component that tracks parent child relationships.
//!
//! Node fields must be kept logically consistent. The recommended approach is to only modify nodes
//! through command buffers, and then use the provided command buffer processing to synchronize
//! your changes.

const std = @import("std");
const tracy = @import("tracy");

const assert = std.debug.assert;

const zcs = @import("../root.zig");
const typeId = zcs.typeId;
const TypeId = zcs.TypeId;
const Entities = zcs.Entities;
const Entity = zcs.Entity;
const CmdBuf = zcs.CmdBuf;
const CompFlag = zcs.CompFlag;
const Any = zcs.Any;

const Zone = tracy.Zone;

const Node = @This();

parent: Entity.Optional = .none,
first_child: Entity.Optional = .none,
prev_sib: Entity.Optional = .none,
next_sib: Entity.Optional = .none,

/// Returns the parent node, or null if none exists.
pub fn getParent(self: *const @This(), es: *const Entities) ?*Node {
    const parent = self.parent.unwrap() orelse return null;
    return parent.get(es, Node).?;
}

/// Returns the first child, or null if none exists.
pub fn getFirstChild(self: *const @This(), es: *const Entities) ?*Node {
    const first_child = self.first_child.unwrap() orelse return null;
    return first_child.get(es, Node).?;
}

/// Returns the previous sibling, or null if none exists.
pub fn getPrevSib(self: *const @This(), es: *const Entities) ?*Node {
    const prev_sib = self.prev_sib.unwrap() orelse return null;
    return prev_sib.get(es, Node).?;
}

/// Returns the next sibling, or null if none exists.
pub fn getNextSib(self: *const @This(), es: *const Entities) ?*Node {
    const next_sib = self.next_sib.unwrap() orelse return null;
    return next_sib.get(es, Node).?;
}

/// Similar to the `SetParent` command, but sets the parent immediately.
pub fn setParentImmediate(self: *Node, es: *Entities, parent_opt: ?*Node) void {
    const pointer_lock = es.pointer_generation.lock();
    defer pointer_lock.check(es.pointer_generation);

    // If the relationship would result in a cycle, or parent and child are equal, early out.
    if (parent_opt) |parent| {
        if (self == parent) return;
        if (self.isAncestorOf(es, parent)) return;
    }

    // Unparent the child
    if (self.getParent(es)) |curr_parent| {
        if (self.getPrevSib(es)) |prev_sib| {
            prev_sib.next_sib = self.next_sib;
        } else {
            curr_parent.first_child = self.next_sib;
        }
        if (self.next_sib.unwrap()) |next_sib| {
            next_sib.get(es, Node).?.prev_sib = self.prev_sib;
            self.next_sib = .none;
        }
        self.prev_sib = .none;
        self.parent = .none;
    }

    // Set the new parent
    if (parent_opt) |parent| {
        self.parent = es.getEntity(parent).toOptional();
        self.next_sib = parent.first_child;
        const child_entity = es.getEntity(self);
        if (parent.first_child.unwrap()) |first_child| {
            first_child.get(es, Node).?.prev_sib = child_entity.toOptional();
        }
        parent.first_child = child_entity.toOptional();
    }
}

/// Destroys a node, its entity, and all of its children. This behavior occurs automatically via
/// `exec` when an entity with an entity with a node is destroyed.
///
/// Invalidates pointers.
pub fn destroyImmediate(unstable_ptr: *@This(), es: *Entities) void {
    // Cache the entity handle since we're about to invalidate pointers
    const e = es.getEntity(unstable_ptr);

    // Destroy the children and unparent this node
    unstable_ptr.destroyChildrenAndUnparentImmediate(es);

    // Destroy the entity
    assert(e.destroyImmediate(es));
}

/// Destroys a node's children and then unparents it. This behavior occurs automatically via `exec`
/// when a node is removed from an entity.
///
/// Invalidates pointers.
pub fn destroyChildrenAndUnparentImmediate(unstable_ptr: *@This(), es: *Entities) void {
    const pointer_lock = es.pointer_generation.lock();

    // Get an iterator over the node's children and then destroy it
    var children = b: {
        defer pointer_lock.check(es.pointer_generation);
        const self = unstable_ptr; // Not yet disturbed

        const iter = self.postOrderIterator(es);
        self.setParentImmediate(es, null);
        self.first_child = .none;

        break :b iter;
    };

    // Iterate over the children and destroy them, this will invalidate `unstable_ptr`
    es.pointer_generation.increment();
    while (children.next(es)) |curr| {
        assert(es.getEntity(curr).destroyImmediate(es));
    }
}

/// Returns true if an ancestor of `descendant`, false otherwise. Entities are not ancestors of
/// themselves.
pub fn isAncestorOf(self: *const @This(), es: *const Entities, descendant: *const Node) bool {
    var curr = descendant.getParent(es) orelse return false;
    while (true) {
        if (curr == self) return true;
        curr = curr.getParent(es) orelse return false;
    }
}

/// Returns an iterator over the node's immediate children.
pub fn childIterator(self: *const @This()) ChildIterator {
    return .{ .curr = self.first_child };
}

/// An iterator over a node's immediate children.
const ChildIterator = struct {
    curr: Entity.Optional,

    /// Returns the next child, or `null` if there are none.
    pub fn next(self: *@This(), es: *const Entities) ?*Node {
        const entity = self.curr.unwrap() orelse return null;
        const node = entity.get(es, Node).?;
        self.curr = node.next_sib;
        return node;
    }
};

/// Returns an iterator over the node's ancestors. The iterator starts at the parent, if any, and
/// then, follows the parent chain until it hits a node with no parent.
pub fn ancestorIterator(self: *const @This()) AncestorIterator {
    return .{ .curr = self.parent };
}

/// An iterator over a node's ancestors.
const AncestorIterator = struct {
    curr: Entity.Optional,

    /// Returns the next ancestor, or `null` if there are none.
    pub fn next(self: *@This(), es: *const Entities) ?*Node {
        const entity = self.curr.unwrap() orelse return null;
        const node = entity.get(es, Node).?;
        self.curr = node.parent;
        return node;
    }
};

/// Returns a pre-order iterator over a node's children. Pre-order traversal visits parents before
/// children.
pub fn preOrderIterator(self: *const @This(), es: *const Entities) PreOrderIterator {
    return .{
        .start = es.getEntity(self).toOptional(),
        .curr = self.first_child,
    };
}

/// A pre-order iterator over `(start, ...]`.
pub const PreOrderIterator = struct {
    start: Entity.Optional,
    curr: Entity.Optional,

    /// An empty pre-order iterator.
    pub const empty: @This() = .{
        .start = .none,
        .curr = .none,
    };

    /// Returns the next child, or `null` if there are none.
    pub fn next(self: *@This(), es: *const Entities) ?*Node {
        const pre_entity = self.curr.unwrap() orelse return null;
        const pre = pre_entity.get(es, Node).?;
        if (pre.first_child != Entity.Optional.none) {
            self.curr = pre.first_child;
        } else {
            var has_next_sib = pre;
            while (has_next_sib.next_sib.unwrap() == null) {
                if (has_next_sib.parent.unwrap().? == self.start.unwrap().?) {
                    self.curr = .none;
                    return pre;
                }
                has_next_sib = has_next_sib.getParent(es).?;
            }
            self.curr = has_next_sib.next_sib.unwrap().?.toOptional();
        }
        return pre;
    }

    /// Fast forward the iterator to just after the given subtree.
    ///
    /// Asserts that `subtree` is a subtree of this iterator.
    pub fn skipSubtree(self: *@This(), es: *const Entities, subtree: *const Node) void {
        // Assert that subtree is contained by this iterator. If it isn't, we'd end up with an
        // infinite loop.
        if (self.start.unwrap()) |start| {
            assert(start.get(es, Node).?.isAncestorOf(es, subtree));
        }

        var has_next_sib = subtree;
        while (has_next_sib.next_sib.unwrap() == null) {
            if (has_next_sib.parent.unwrap().? == self.start.unwrap().?) {
                self.curr = .none;
                return;
            }
            has_next_sib = has_next_sib.getParent(es).?;
        }
        self.curr = has_next_sib.next_sib.unwrap().?.toOptional();
    }
};

/// Returns a post-order iterator over a node's children. Post-order traversal visits parents after
/// children.
pub fn postOrderIterator(self: *const @This(), es: *const Entities) PostOrderIterator {
    return .{
        // We start on the leftmost leaf
        .curr = b: {
            var curr = self.first_child.unwrap() orelse break :b .none;
            while (curr.get(es, Node).?.first_child.unwrap()) |fc| curr = fc;
            break :b curr.toOptional();
        },
        // And we end when we reach the given entity
        .end = es.getEntity(self),
    };
}

/// A post-order iterator over `[curr, end)`.
pub const PostOrderIterator = struct {
    curr: Entity.Optional,
    end: Entity,

    /// Returns the next child, or `null` if there are none.
    pub fn next(self: *@This(), es: *const Entities) ?*Node {
        const post_entity = self.curr.unwrap() orelse return null;
        if (post_entity == self.end) return null;
        const post = post_entity.get(es, Node).?;
        if (post.next_sib.unwrap()) |next_sib| {
            var curr = next_sib;
            while (curr.get(es, Node).?.first_child.unwrap()) |fc| curr = fc;
            self.curr = curr.toOptional();
        } else {
            self.curr = self.curr.unwrap().?.get(es, Node).?.parent.unwrap().?.toOptional();
        }
        return post;
    }
};

/// Encodes a command that requests to parent `child` and `parent`.
///
/// * If the relationship would result in a cycle, parent and child are equal, or child no longer
///   exists, then no change is made.
/// * If parent is `.none`, child is unparented.
/// * If parent no longer exists, child is destroyed.
pub const SetParent = struct {
    child: Entity,
    parent: Entity.Optional,
};

/// `Exec` provides helpers for processing hierarchy changes via the command buffer.
///
/// By convention, `exec` only calls into the stable public interface of the types it's working
/// with. As such, documentation is sparse. You are welcome to call these methods directly, or
/// use them as reference for implementing your own command buffer iterator.
pub const Exec = struct {
    /// Provided as reference. Executes a command buffer, maintaining the hierarchy and reacting to
    /// related events along the way. In practice, you likely want to call the finer grained
    /// functions provided directly, so that other libraries you use can also hook into the command
    /// buffer iterator.
    pub fn immediate(es: *Entities, cb: *CmdBuf) void {
        immediateOrErr(es, cb) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `immediate`, but returns an error on failure instead of panicking. On error the
    /// commands are left partially evaluated.
    ///
    /// Invalidates pointers.
    pub fn immediateOrErr(
        es: *Entities,
        cb: *CmdBuf,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow, ZcsEntityOverflow }!void {
        var default_exec: CmdBuf.Exec = .init();

        es.pointer_generation.increment();

        var batches = cb.iterator();
        while (batches.next()) |batch| {
            switch (batch) {
                .arch_change => |arch_change| {
                    var delta: CmdBuf.Batch.ArchChange.Delta = .{};
                    var ops = arch_change.iterator();
                    while (ops.next()) |op| {
                        beforeArchChangeImmediate(es, arch_change, op);
                        delta.updateImmediate(op);
                    }

                    _ = try arch_change.execImmediateOrErr(es, delta);
                },
                .ext => |ext| {
                    try extImmediateOrErr(es, ext);
                    default_exec.extImmediateOrErr(ext);
                },
            }
        }

        try default_exec.finish(cb, es);
    }

    /// Executes an extension command.
    pub inline fn extImmediateOrErr(
        es: *Entities,
        payload: Any,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow }!void {
        if (payload.as(SetParent)) |set_parent| {
            const child = set_parent.child;
            if (set_parent.parent.unwrap()) |parent| {
                if (try child.getOrAddImmediateOrErr(es, Node, .{})) |child_node| {
                    if (try parent.getOrAddImmediateOrErr(es, Node, .{})) |parent_node| {
                        // If a parent that exists is assigned, set it as the parent
                        child_node.setParentImmediate(es, parent_node);
                    } else {
                        // Otherwise destroy the child
                        child_node.destroyImmediate(es);
                    }
                }
            } else {
                // If our parent is being cleared and we have a node, clear it. If not leave
                // it alone since it's implicitly clear.
                if (child.get(es, Node)) |node| {
                    node.setParentImmediate(es, null);
                }
            }
        }
    }

    /// Call this before executing a command.
    pub inline fn beforeArchChangeImmediate(
        es: *Entities,
        arch_change: CmdBuf.Batch.ArchChange,
        op: CmdBuf.Batch.ArchChange.Op,
    ) void {
        switch (op) {
            .destroy => if (arch_change.entity.get(es, Node)) |node| {
                _ = node.destroyChildrenAndUnparentImmediate(es);
            },
            .remove => |id| if (id == typeId(Node)) {
                if (arch_change.entity.get(es, Node)) |node| {
                    _ = node.destroyChildrenAndUnparentImmediate(es);
                }
            },
            .add => {},
        }
    }
};

//! A pool of command buffers. Intended for multithreaded use cases where each thread needs its own
//! command buffer.
//!
//! # Problem Statement
//!
//! The key issue this abstraction solves is how much space to allocate per command buffer.
//!
//! The naive approach of dividing the capacity evenly across the threads will fail if the workload
//! is lopsided or the users machine has more cores than expected.
//!
//! The alternative naive approach of allocating a fixed command buffer size per thread will
//! increase the likelihood of overflowing command buffers on machines with low core counts, or
//! overflowing entities on machines with high core counts.
//!
//! # Solution
//!
//! `CmdPool` allocates a fixed number of command buffers of fixed sizes. Once per chunk, you call
//! `acquire` to get a command buffer with more than `headroom` capacity`. When done processing a
//! chunk, you call `release`.
//!
//! This distribution of load results in comparable command buffer usage regardless of core counts,
//! lessening the Q&A burden when tuning your game.

const std = @import("std");
const zcs = @import("root.zig");
const tracy = @import("tracy");

const log = std.log;
const assert = std.debug.assert;

const Mutex = std.Thread.Mutex;
const Condition = std.Thread.Condition;

const CmdBuf = zcs.CmdBuf;
const Entities = zcs.Entities;

const Allocator = std.mem.Allocator;
const ArrayListUnmanaged = std.ArrayListUnmanaged;

/// The debug name for this command pool. See `InitOptions.name`.
name: ?[:0]const u8,
/// Reserved command buffers that have not yet been acquired.
buffers: ArrayListUnmanaged(CmdBuf),
/// Locked when modifying internal state.
mutex: Mutex,
/// Signaled when command buffers are returned, broadcasted when all command buffers have been
/// retired.
condition: Condition,
/// Command buffers that have been released.
released: ArrayListUnmanaged(*CmdBuf),
/// The number of retired command buffers. Command buffers are retired when they are returned with
/// with less than the requested headroom available.
retired: usize,
/// Measured in units of `CmdBuf.worstCastUsage`, ranges from `0` to `1`.
headroom: f32,
/// See `InitOptions.warn_ratio`.
warn_ratio: f32,

/// The capacity of a command pool.
pub const Capacity = struct {
    /// The default number of commands to reserve.
    pub const default_cmds = CmdBuf.Capacity.default_cmds / 256;

    /// The capacity of a single command buffer. Note that `default_cmds` is overridden to a lower
    /// value than when creating a `CmdBuf` directly.
    buffer: CmdBuf.Capacity = .{},
    /// The total number of command buffers to reserve.
    buffers: usize = 256,
};

/// Options for `init`.
pub const InitOptions = struct {
    /// The debug name for this command pool.
    ///
    /// If non-null and Tracy is enabled, this command pool's usage is graphed under this name on
    /// reset.
    name: ?[:0]const u8,
    /// Used to allocate the command pool.
    gpa: Allocator,
    /// Entities are reserved from here.
    es: *Entities,
    /// The capacity.
    cap: Capacity = .{},
    /// `acquire` only returns command buffers with a `worstCaseUsage` greater than this value.
    headroom: f32 = 0.5,
    /// Warn if a single acquire exceeds this ratio of the headroom, or if more than this ratio of the
    /// total command buffers are written.
    warn_ratio: f32 = 0.2,
};

/// Initializes a command pool.
pub fn init(options: InitOptions) error{ OutOfMemory, ZcsEntityOverflow }!@This() {
    // Reserve the command buffers
    var buffers: ArrayListUnmanaged(CmdBuf) = try .initCapacity(options.gpa, options.cap.buffers);
    errdefer buffers.deinit(options.gpa);
    errdefer for (buffers.items) |*cb| cb.deinit(options.gpa, options.es);
    var buffer_cap = options.cap.buffer;
    buffer_cap.cmds = buffer_cap.cmds orelse Capacity.default_cmds;
    for (0..options.cap.buffers) |_| {
        const cb: CmdBuf = try .init(.{
            .name = null,
            .gpa = options.gpa,
            .es = options.es,
            .cap = buffer_cap,
            // We handle the warnings ourselves
            .warn_ratio = 1.0,
        });
        buffers.appendAssumeCapacity(cb);
    }

    // Reserve the released list
    var released: ArrayListUnmanaged(*CmdBuf) = try .initCapacity(options.gpa, options.cap.buffers);
    errdefer released.deinit(options.gpa);

    // If Tracy is enabled and we have a name, configure our plot.
    if (tracy.enabled) {
        if (options.name) |name| {
            tracy.plotConfig(.{
                .name = name,
                .format = .percentage,
                .mode = .line,
                .fill = true,
            });
        }
    }

    return .{
        .buffers = buffers,
        .mutex = .{},
        .condition = .{},
        .released = released,
        .retired = 0,
        .headroom = options.headroom,
        .name = options.name,
        .warn_ratio = options.warn_ratio,
    };
}

/// Destroys the command pool.
pub fn deinit(self: *@This(), gpa: Allocator, es: *Entities) void {
    self.checkAssertions();
    self.buffers.items.len = self.buffers.capacity;
    for (self.buffers.items) |*cb| cb.deinit(gpa, es);
    self.buffers.deinit(gpa);
    self.released.deinit(gpa);
}

/// The result of `acquire`.
pub const AcquireResult = struct {
    /// The initial usage ratio.
    usage: f32,
    /// The command buffer.
    cb: *CmdBuf,

    /// Initializes an acquire result with the starting usage.
    fn init(cb: *CmdBuf) @This() {
        return .{
            .usage = cb.worstCaseUsage(),
            .cb = cb,
        };
    }
};

/// Acquire a command buffer with at least `Capacity.headroom` capacity remaining. Call `release`
/// when done. Thread safe.
///
/// This function may block if all command buffers are currently in use. You can mitigate this by
/// reserving more command buffers up front.
pub fn acquire(self: *@This()) AcquireResult {
    return self.acquireOrErr() catch |err|
        @panic(@errorName(err));
}

/// Similar to `acquire`, but returns an error when out of command buffers.
pub fn acquireOrErr(self: *@This()) error{ZcsCmdPoolUnderflow}!AcquireResult {
    self.mutex.lock();
    defer self.mutex.unlock();

    // Try to get a recently released command buffer
    if (self.released.pop()) |popped| return .init(popped);

    // Try to get a reserved command buffer
    if (self.buffers.items.len > 0) {
        const new_len = self.buffers.items.len - 1;
        const result = &self.buffers.items[new_len];
        self.buffers.items.len = new_len;
        return .init(result);
    }

    // There are no command buffers available. Wait until one becomes available, or all are
    // retired.
    while (self.released.items.len == 0 and self.retired < self.buffers.capacity) {
        self.condition.wait(&self.mutex);
    }

    // Try to reacquire the released command buffer
    if (self.released.pop()) |cb| {
        assert(self.retired < self.buffers.capacity);
        return .init(cb);
    }

    // If no command buffer was released, we're out of command buffers, return an error
    assert(self.retired == self.buffers.capacity);
    return error.ZcsCmdPoolUnderflow;
}

/// Releases a previously acquired command buffer. Thread safe.
pub fn release(self: *@This(), ar: AcquireResult) void {
    self.mutex.lock();
    defer self.mutex.unlock();

    // Optionally warn if a single acquire used too much headroom, this is a sign that the command
    // buffers are too small
    const usage = ar.cb.worstCaseUsage();
    if (ar.cb.worstCaseUsage() - ar.usage > (1.0 - self.headroom) * self.warn_ratio) {
        log.warn(
            "acquire used > {d}% headroom, consider increasing command buffer size",
            .{self.warn_ratio * 100.0},
        );
    }

    // Release the command buffer if it has enough headroom left, otherwise retire it
    if (usage < self.headroom) {
        self.released.appendAssumeCapacity(ar.cb);
    } else {
        self.retired += 1;
    }

    // If all command buffers are retired, broadcast our condition so all pending acquires return an
    // error. Otherwise signal at least one pending acquire to continue.
    if (self.retired == self.buffers.capacity) {
        self.condition.broadcast();
    } else {
        self.condition.signal();
    }
}

/// Gets a slice of all command buffers that may have been written to since the last reset.
pub fn written(self: *@This()) []CmdBuf {
    self.checkAssertions();
    return self.buffers.items.ptr[self.buffers.items.len..self.buffers.capacity];
}

/// Resets the command pool. Asserts that all command buffers have already been reset.
pub fn reset(self: *@This()) void {
    // Check assertions
    self.checkAssertions();
    if (std.debug.runtime_safety) for (self.written()) |cb| assert(cb.isEmpty());

    // Warn if we used too many command buffers
    const cap: f32 = @floatFromInt(self.buffers.capacity);
    const used: f32 = @floatFromInt(self.written().len);
    const usage = used / cap;
    if (usage > self.warn_ratio) {
        log.warn(
            "more than {d}% command pool buffers used, consider incresing reserved count",
            .{self.warn_ratio},
        );
    }

    // If a name is configured, emit a Tracy plot
    if (self.name) |name| {
        tracy.plot(.{
            .name = name,
            .value = .{ .f32 = usage * 100.0 },
        });
    }

    // Reset the command pool
    self.buffers.items.len = self.buffers.capacity;
    self.released.items.len = 0;
    self.retired = 0;
}

/// Asserts that all command buffers have been released.
pub fn checkAssertions(self: *@This()) void {
    assert(self.released.items.len + self.retired == self.buffers.capacity - self.buffers.items.len);
}

//! A component that tracks the position and orientation of an entity in 2D. Hierarchical
//! relationships formed by the `Node` component are respected if present.
//!
//! You can synchronize a transform's `world_from_model` field, and the `world_from_model` fields of
//! all its relative children by calling `sync`. This is necessary when modifying the transform, or
//! changing the hierarchy by adding or removing a transform.
//!
//! To alleviate this burden, a number of setters are provided that call `sync` for you, and command
//! buffer integration is provided for automatically calling sync when transforms are added and
//! removed.
//!
//! For more information on command buffer integration, see `exec`.
//!
//! If you need features not provided by this implementation, for example a third dimension, you're
//! encouraged to use this as a reference for your own transform component.

const std = @import("std");
const zcs = @import("../root.zig");
const tracy = @import("tracy");

const math = std.math;
const assert = std.debug.assert;
const typeId = zcs.typeId;

const Entity = zcs.Entity;
const Entities = zcs.Entities;
const CmdBuf = zcs.CmdBuf;
const Any = zcs.Any;
const PointerLock = zcs.PointerLock;
const Node = zcs.ext.Node;
const Vec2 = zcs.ext.geom.Vec2;
const Rotor2 = zcs.ext.geom.Rotor2;
const Mat2x3 = zcs.ext.geom.Mat2x3;

const Zone = tracy.Zone;

const Transform2D = @This();

/// The transform's local position.
pos: Vec2 = .zero,
/// The transform's local orientation.
rot: Rotor2 = .identity,
/// The transform's world from model matrix.
world_from_model: Mat2x3 = .identity,
/// Whether or not this transform's space is relative to its parent.
relative: bool = true,

/// Move the transform in local space by `delta` and then calls `sync`.
pub fn move(self: *@This(), es: *const Entities, delta: Vec2) void {
    self.pos.add(delta);
    self.sync(es);
}

/// Set the local position to `pos` and then calls `sync`.
pub fn setPos(self: *@This(), es: *const Entities, pos: Vec2) void {
    self.pos = pos;
    self.sync(es);
}

/// Rotates the local space and then calls `sync`.
pub fn rotate(self: *@This(), es: *const Entities, rotation: Rotor2) void {
    self.rot.mul(rotation);
    self.sync(es);
}

/// Sets the local orientation to `rot` and then calls `sync`.
pub fn setRot(self: *@This(), es: *const Entities, rot: Rotor2) void {
    self.rot = rot;
    self.sync(es);
}

/// Returns the world space position.
pub inline fn getWorldPos(self: @This()) Vec2 {
    return self.world_from_model.getTranslation();
}

/// Updates the `world_from_model` matrix on this transform, and all of its transitive relative
/// children.
pub fn sync(self: *@This(), es: *const Entities) void {
    var transforms = self.preOrderIterator(es);
    while (transforms.next(es)) |transform| {
        const translation: Mat2x3 = .translation(transform.pos);
        const rotation: Mat2x3 = .rotation(transform.rot);
        const parent_world_from_model = transform.getRelativeWorldFromModel(es);
        transform.world_from_model = rotation.applied(translation).applied(parent_world_from_model);
    }
}

/// Returns the parent's world form model matrix, or identity if not relative.
pub inline fn getRelativeWorldFromModel(self: *const @This(), es: *const Entities) Mat2x3 {
    if (!self.relative) return .identity;
    const node = es.getComp(self, Node) orelse return .identity;
    const parent = node.parent.unwrap() orelse return .identity;
    const parent_transform = parent.get(es, Transform2D) orelse return .identity;
    return parent_transform.world_from_model;
}

/// Returns the forward direction of this transform.
pub fn getForward(self: *const @This()) Vec2 {
    return self.world_from_model.timesDir(.y_pos);
}

/// Returns a pre-order iterator over the subtree of relative transforms starting at `self`. This
/// will visit parents before children, and it will include `self`.
pub fn preOrderIterator(self: *@This(), es: *const Entities) PreOrderIterator {
    return .{
        .parent = self,
        .children = if (es.getComp(self, Node)) |node| node.preOrderIterator(es) else .empty,
        .pointer_lock = es.pointer_generation.lock(),
    };
}

/// A pre-order iterator over relative transforms.
pub const PreOrderIterator = struct {
    parent: ?*Transform2D,
    children: Node.PreOrderIterator,
    pointer_lock: PointerLock,

    /// Returns the next transform.
    pub fn next(self: *@This(), es: *const Entities) ?*Transform2D {
        self.pointer_lock.check(es.pointer_generation);

        if (self.parent) |parent| {
            self.parent = null;
            return parent;
        }

        while (self.children.next(es)) |node| {
            // If the next child has a transform and that transform is relative to its parent,
            // return it.
            if (es.getComp(node, Transform2D)) |transform| {
                if (transform.relative) {
                    return transform;
                }
            }
            // Otherwise, skip this subtree.
            self.children.skipSubtree(es, node);
        }

        return null;
    }
};

/// `Exec` provides helpers for processing hierarchy changes via the command buffer.
///
/// By convention, `Exec` only calls into the stable public interface of the types it's working
/// with. As such, documentation is sparse. You are welcome to call these methods directly, or
/// use them as reference for implementing your own command buffer iterator.
pub const Exec = struct {
    /// Similar to `Node.Exec.immediate`, but marks transforms as dirty as needed.
    pub fn immediate(es: *Entities, cb: *CmdBuf) void {
        immediateOrErr(es, cb) catch |err|
            @panic(@errorName(err));
    }

    /// Similar to `immediate`, but returns an error on failure instead of panicking. On error the
    /// commands are left partially evaluated.
    pub fn immediateOrErr(
        es: *Entities,
        cb: *CmdBuf,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow, ZcsEntityOverflow }!void {
        var default_exec: CmdBuf.Exec = .init();

        es.pointer_generation.increment();

        var batches = cb.iterator();
        while (batches.next()) |batch| {
            switch (batch) {
                .arch_change => |arch_change| {
                    {
                        var delta: CmdBuf.Batch.ArchChange.Delta = .{};
                        var ops = arch_change.iterator();
                        while (ops.next()) |op| {
                            Node.Exec.beforeArchChangeImmediate(es, arch_change, op);
                            delta.updateImmediate(op);
                        }

                        _ = arch_change.execImmediate(es, delta);
                    }

                    {
                        var ops = arch_change.iterator();
                        while (ops.next()) |op| {
                            afterArchChangeImmediate(es, arch_change, op);
                        }
                    }
                },
                .ext => |ext| {
                    try extImmediateOrErr(es, ext);
                    default_exec.extImmediateOrErr(ext);
                },
            }
        }

        try default_exec.finish(cb, es);
    }

    /// Call this after executing a command.
    pub inline fn afterArchChangeImmediate(
        es: *Entities,
        batch: CmdBuf.Batch.ArchChange,
        op: CmdBuf.Batch.ArchChange.Op,
    ) void {
        switch (op) {
            .add => |comp| if (comp.id == typeId(Transform2D)) {
                if (batch.entity.get(es, Transform2D)) |transform| {
                    transform.sync(es);
                }
            },
            .remove => |id| {
                if (id == typeId(Node)) {
                    if (batch.entity.get(es, Transform2D)) |transform| {
                        transform.sync(es);
                    }
                } else if (id == typeId(Transform2D)) {
                    if (batch.entity.get(es, Node)) |node| {
                        var children = node.childIterator();
                        while (children.next(es)) |child| {
                            if (es.getComp(child, Transform2D)) |child_transform| {
                                child_transform.sync(es);
                            }
                        }
                    }
                }
            },
            else => {},
        }
    }

    /// Call this to process an extension command.
    pub inline fn extImmediateOrErr(
        es: *Entities,
        payload: Any,
    ) error{ ZcsArchOverflow, ZcsChunkOverflow, ZcsChunkPoolOverflow }!void {
        try Node.Exec.extImmediateOrErr(es, payload);
        if (payload.as(Node.SetParent)) |set_parent| {
            if (set_parent.child.get(es, Transform2D)) |transform| {
                transform.sync(es);
            }
        }
    }
};

const std = @import("std");
const zcs = @import("zcs");
const tracy = @import("tracy");

const assert = std.debug.assert;

const Entities = zcs.Entities;
const Entity = zcs.Entity;
const CmdBuf = zcs.CmdBuf;
const Transform2D = zcs.ext.Transform2D;
const ZoneCmd = zcs.ext.ZoneCmd;

const Zone = tracy.Zone;

const max_entities = 1000000;
const iterations = 10;

pub const std_options: std.Options = .{
    .log_level = .info,
};

pub const tracy_impl = @import("tracy_impl");
pub const tracy_options: tracy.Options = .{
    .default_callstack_depth = 8,
};

const small = false;
const A = if (small) u2 else u64;
const B = if (small) u4 else u128;
const C = if (small) u8 else u256;

// Eventually we may make reusable benchmarks for comparing releases. Right now this is just a
// dumping ground for testing performance tweaks.
pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{ .thread_safe = false }){};
    const allocator = gpa.allocator();

    var expected_total: ?u256 = null;
    var expected_ra_total: ?u256 = null;

    for (0..iterations) |_| {
        var es: Entities = try .init(.{
            .gpa = allocator,
            .cap = .{
                .entities = max_entities,
                .arches = 1,
                .chunks = 4096,
                .chunk = 65536,
            },
            .warn_ratio = 1.0,
        });
        defer es.deinit(allocator);

        // also compare interning vs not etc
        {
            const fill_zone = Zone.begin(.{ .name = "fill immediate any", .src = @src() });
            for (0..max_entities) |i| {
                const e = Entity.reserveImmediate(&es);
                const a: A = @intCast(i);
                const b: B = @intCast(i);
                const c: C = @intCast(i);
                assert(e.changeArchAnyImmediate(&es, .{ .add = &.{
                    .init(A, &a),
                    .init(B, &b),
                    .init(C, &c),
                } }) catch |err| @panic(@errorName(err)));
            }
            fill_zone.end();
        }
    }

    for (0..iterations) |_| {
        var es: Entities = try .init(.{
            .gpa = allocator,
            .cap = .{
                .entities = max_entities,
                .arches = 1,
                .chunks = 4096,
                .chunk = 65536,
            },
            .warn_ratio = 1.0,
        });
        defer es.deinit(allocator);

        // also compare interning vs not etc
        {
            es.updateStats();
            const fill_zone = Zone.begin(.{ .name = "fill immediate", .src = @src() });
            for (0..max_entities) |i| {
                const e = Entity.reserveImmediate(&es);
                assert(e.changeArchImmediate(
                    &es,
                    struct { A, B, C },
                    .{
                        .add = .{ @intCast(i), @intCast(i), @intCast(i) },
                    },
                ));
            }
            fill_zone.end();
            es.updateStats();
        }
    }

    for (0..iterations) |_| {
        var tags: std.ArrayListUnmanaged(u8) = try .initCapacity(allocator, max_entities * 4);
        defer tags.deinit(allocator);
        var args: std.ArrayListUnmanaged(u64) = try .initCapacity(allocator, max_entities * 4);
        defer args.deinit(allocator);
        var bytes: std.ArrayListUnmanaged(u8) = try .initCapacity(allocator, max_entities * @sizeOf(C) * 3);
        defer bytes.deinit(allocator);

        {
            var last: ?usize = null;
            const fill_zone = Zone.begin(.{ .name = "baseline fill separate", .src = @src() });
            for (0..max_entities) |i| {
                const a: A = @intCast(i);
                const b: B = @intCast(i);
                const c: C = @intCast(i);

                if (i != last) {
                    // assert(args.items.len < args.capacity) implied by tags check
                    if (tags.items.len >= tags.capacity) @panic("OOB");
                    last = i;
                    tags.appendAssumeCapacity(10);
                    args.appendAssumeCapacity(i);
                }

                if (tags.items.len >= tags.capacity) @panic("OOB");
                tags.appendAssumeCapacity(0);
                // if (args.items.len >= args.capacity) @panic("OOB"); // Implied by tags check
                args.appendAssumeCapacity(@intFromPtr(zcs.typeId(A)));
                bytes.items.len = std.mem.alignForward(
                    usize,
                    bytes.items.len,
                    @alignOf(A),
                );
                if (bytes.items.len + @sizeOf(A) > bytes.capacity) @panic("OOB");
                bytes.appendSliceAssumeCapacity(std.mem.asBytes(&a));

                if (tags.items.len >= tags.capacity) @panic("OOB");
                tags.appendAssumeCapacity(0);
                // if (args.items.len >= args.capacity) @panic("OOB"); // Implied by tags check
                args.appendAssumeCapacity(@intFromPtr(zcs.typeId(B)));
                bytes.items.len = std.mem.alignForward(
                    usize,
                    bytes.items.len,
                    @alignOf(B),
                );
                if (bytes.items.len + @sizeOf(B) > bytes.capacity) @panic("OOB");
                bytes.appendSliceAssumeCapacity(std.mem.asBytes(&b));

                if (tags.items.len >= tags.capacity) @panic("OOB");
                tags.appendAssumeCapacity(0);
                // if (args.items.len >= args.capacity) @panic("OOB"); // Implied by tags check
                args.appendAssumeCapacity(@intFromPtr(zcs.typeId(C)));
                bytes.items.len = std.mem.alignForward(
                    usize,
                    bytes.items.len,
                    @alignOf(C),
                );
                if (bytes.items.len + @sizeOf(C) > bytes.capacity) @panic("OOB");
                bytes.appendSliceAssumeCapacity(std.mem.asBytes(&c));
            }
            fill_zone.end();
        }
    }

    for (0..iterations) |_| {
        var es: Entities = try .init(.{
            .gpa = allocator,
            .cap = .{
                .entities = max_entities,
                .arches = 64,
                .chunks = 4096,
                .chunk = 65536,
            },
            .warn_ratio = 1.0,
        });
        defer es.deinit(allocator);

        var cb: CmdBuf = try .init(.{
            .name = "cmd buf",
            .gpa = allocator,
            .es = &es,
            .cap = .{
                .cmds = max_entities * 3,
                .reserved_entities = max_entities,
            },
        });
        defer cb.deinit(allocator, &es);

        {
            const fill_fast_zone = Zone.begin(.{ .name = "fill cb", .src = @src() });
            defer fill_fast_zone.end();
            for (0..max_entities) |i| {
                const e = Entity.reserve(&cb);
                e.add(&cb, A, @intCast(i));
                e.add(&cb, B, @intCast(i));
                e.add(&cb, C, @intCast(i));
            }
        }
    }

    for (0..iterations) |_| {
        const alloc_zone = Zone.begin(.{ .name = "alloc", .src = @src() });

        const alloc_es_zone = Zone.begin(.{ .name = "es", .src = @src() });
        var es: Entities = try .init(.{
            .gpa = allocator,
            .cap = .{
                .entities = max_entities * 2,
                .arches = 64,
                .chunks = 4096,
                .chunk = 65536,
            },
            .warn_ratio = 1.0,
        });
        defer es.deinit(allocator);
        alloc_es_zone.end();

        const alloc_cb_zone = Zone.begin(.{ .name = "cb", .src = @src() });
        var cb: CmdBuf = try .init(.{
            .name = "cmd buf",
            .gpa = allocator,
            .es = &es,
            .cap = .{
                .cmds = max_entities * 4,
                .reserved_entities = max_entities,
            },
            .warn_ratio = 1.0,
        });
        defer cb.deinit(allocator, &es);
        alloc_cb_zone.end();

        alloc_zone.end();

        {
            const cmdbuf_zone = Zone.begin(.{ .name = "cb", .src = @src() });
            defer cmdbuf_zone.end();
            {
                const fill_zone = Zone.begin(.{ .name = "fill", .src = @src() });
                defer fill_zone.end();

                // Divided into two parts to test exec zones
                const exec_zone = ZoneCmd.begin(&cb, .{
                    .src = @src(),
                    .name = "exec zone",
                });
                defer exec_zone.end(&cb);

                {
                    const first_half_zone = ZoneCmd.begin(&cb, .{
                        .src = @src(),
                        .name = "first half",
                    });
                    defer first_half_zone.end(&cb);
                    for (0..max_entities / 2) |i| {
                        const e = Entity.reserve(&cb);
                        e.add(&cb, A, @intCast(i));
                        e.add(&cb, B, @intCast(i));
                        e.add(&cb, C, @intCast(i));
                    }
                }
                {
                    const second_half_zone = ZoneCmd.begin(&cb, .{
                        .src = @src(),
                        .name = "second half",
                    });
                    defer second_half_zone.end(&cb);
                    for (max_entities / 2..max_entities) |i| {
                        const e = Entity.reserve(&cb);
                        e.add(&cb, A, @intCast(i));
                        e.add(&cb, B, @intCast(i));
                        e.add(&cb, C, @intCast(i));
                    }
                }
            }
            CmdBuf.Exec.immediate(&es, &cb);
        }

        {
            var total: u256 = 0;
            {
                const iter_zone = Zone.begin(.{ .name = "iter.fast", .src = @src() });
                defer iter_zone.end();
                var iter = es.iterator(struct { a: *const A, b: *const B, c: *const C });
                while (iter.next(&es)) |vw| {
                    total +%= vw.a.*;
                    total +%= vw.b.*;
                    total +%= vw.c.*;
                }
            }
            std.debug.print("{}\n", .{total});
            if (expected_total == null) expected_total = total;
            if (expected_total != total) @panic("inconsistent result");
        }

        {
            var total: u256 = 0;
            es.forEach("sum", sum, .{ .total = &total });
            std.debug.print("{}\n", .{total});
            if (expected_total == null) expected_total = total;
            if (expected_total != total) @panic("inconsistent result");
        }

        {
            const zone = Zone.begin(.{ .src = @src(), .name = "sum threaded" });
            defer zone.end();

            var tp: std.Thread.Pool = undefined;
            try std.Thread.Pool.init(&tp, .{
                .allocator = allocator,
            });
            defer tp.deinit();
            var wg: std.Thread.WaitGroup = .{};
            es.forEachThreaded("sum threaded", sumThreaded, .{
                .tp = &tp,
                .wg = &wg,
                .ctx = .{},
                .cp = null,
            });
            tp.waitAndWork(&wg);
        }

        {
            var total: u256 = 0;
            {
                const iter_zone = Zone.begin(.{ .name = "iter.view", .src = @src() });
                defer iter_zone.end();
                var iter = es.iterator(struct { e: Entity });
                while (iter.next(&es)) |vw| {
                    const comps = vw.e.view(&es, struct {
                        a: *const A,
                        b: *const B,
                        c: *const C,
                    }).?;
                    total +%= comps.a.*;
                    total +%= comps.b.*;
                    total +%= comps.c.*;
                }
            }
            std.debug.print("{}\n", .{total});
            if (expected_total == null) expected_total = total;
            if (expected_total != total) @panic("inconsistent result");
        }

        {
            var total: u256 = 0;
            {
                const iter_zone = Zone.begin(.{ .name = "iter.get", .src = @src() });
                defer iter_zone.end();
                var iter = es.iterator(struct { e: Entity });
                while (iter.next(&es)) |vw| {
                    total +%= vw.e.get(&es, A).?.*;
                    total +%= vw.e.get(&es, B).?.*;
                    total +%= vw.e.get(&es, C).?.*;
                }
            }
            std.debug.print("{}\n", .{total});
            if (expected_total == null) expected_total = total;
            if (expected_total != total) @panic("inconsistent result");
        }

        {
            var total: u256 = 0;
            var default_rng: std.Random.DefaultPrng = .init(0);
            const rand = default_rng.random();
            {
                const iter_zone = Zone.begin(.{ .name = "es.random access", .src = @src() });
                defer iter_zone.end();
                for (0..max_entities / 2) |_| {
                    const ei: Entity.Index = @enumFromInt(rand.uintLessThan(u32, max_entities));
                    const e = ei.toEntity(&es);
                    const comps = e.view(&es, struct {
                        a: *const A,
                        b: *const B,
                        c: *const C,
                    }).?;
                    total +%= comps.a.*;
                    total +%= comps.b.*;
                    total +%= comps.c.*;
                }
            }
            std.debug.print("ecs ra: {}\n", .{total});
            if (expected_ra_total == null) expected_ra_total = total;
            if (expected_ra_total != total) @panic("inconsistent result");
        }
    }

    for (0..iterations) |_| {
        const zone = Zone.begin(.{ .name = "transform", .src = @src() });
        defer zone.end();

        const es_init_zone = Zone.begin(.{ .name = "es init", .src = @src() });
        var es: Entities = try .init(.{
            .gpa = allocator,
            .cap = .{
                .entities = max_entities,
                .arches = 64,
                .chunks = 4096,
                .chunk = 65536,
            },
            .warn_ratio = 1.0,
        });
        defer es.deinit(allocator);
        es_init_zone.end();

        {
            const entity_init_zone = Zone.begin(.{ .name = "entity init", .src = @src() });
            defer entity_init_zone.end();
            for (0..max_entities) |_| {
                assert(Entity.reserveImmediate(&es).changeArchImmediate(
                    &es,
                    struct { Transform2D },
                    .{ .add = .{.{}} },
                ));
            }
        }

        {
            const queue_zone = Zone.begin(.{ .name = "move", .src = @src() });
            defer queue_zone.end();
            var iter = es.iterator(struct { transform: *Transform2D });
            var f: f32 = 0;
            while (iter.next(&es)) |vw| {
                vw.transform.move(&es, .{ .x = f, .y = f });
                f += 0.01;
            }
        }
    }

    for (0..iterations) |_| {
        const alloc_zone = Zone.begin(.{ .name = "mal.alloc", .src = @src() });
        var es: std.MultiArrayList(struct {
            a: A,
            b: B,
            c: C,
        }) = .empty;
        defer es.deinit(std.heap.page_allocator);
        try es.setCapacity(std.heap.page_allocator, max_entities);
        alloc_zone.end();

        {
            const fill_zone = Zone.begin(.{ .name = "mal.fill", .src = @src() });
            defer fill_zone.end();
            for (0..max_entities) |i| {
                es.appendAssumeCapacity(.{
                    .a = @intCast(i),
                    .b = @intCast(i),
                    .c = @intCast(i),
                });
            }
        }

        {
            var total: u256 = 0;
            {
                const iter_zone = Zone.begin(.{ .name = "mal.iter", .src = @src() });
                defer iter_zone.end();
                for (es.items(.a), es.items(.b), es.items(.c)) |*a, *b, *c| {
                    total +%= a.*;
                    total +%= b.*;
                    total +%= c.*;
                }
            }
            std.debug.print("{}\n", .{total});
            if (expected_total == null) expected_total = total;
            if (expected_total != total) @panic("inconsistent result");
        }

        {
            var total: u256 = 0;
            var default_rng: std.Random.DefaultPrng = .init(0);
            const rand = default_rng.random();
            {
                const iter_zone = Zone.begin(.{ .name = "mal.random access", .src = @src() });
                defer iter_zone.end();
                for (0..max_entities) |_| {
                    const i = max_entities - rand.uintLessThan(u32, max_entities);
                    const comps = es.get(i);
                    total +%= comps.a;
                    total +%= comps.b;
                    total +%= comps.c;
                }
            }
            std.debug.print("mal ra: {}\n", .{total});
        }
    }
}

fn sum(ctx: struct { total: *u256 }, a: *const A, b: *const B, c: *const C) void {
    const total = ctx.total;
    total.* += a.*;
    total.* += b.*;
    total.* += c.*;
}

// This isn't expensive enough to be worth threading, but when you modify it to do more work it becomes worth it
threadlocal var thread_sum: u256 = 0;
fn sumThreaded(_: struct {}, a: *const A, b: *const B, c: *const C) void {
    thread_sum += a.*;
    thread_sum += b.*;
    thread_sum += c.*;
}

//! Storage for entities.
//!
//! See `SlotMap` for how handle safety works.
//!
//! See `README.md` for more information.

const std = @import("std");
const assert = std.debug.assert;
const tracy = @import("tracy");

const Allocator = std.mem.Allocator;

const Zone = tracy.Zone;

const log = std.log;

const zcs = @import("root.zig");
const typeId = zcs.typeId;
const Any = zcs.Any;
const CompFlag = zcs.CompFlag;
const Entity = zcs.Entity;
const PointerLock = zcs.PointerLock;
const TypeId = zcs.TypeId;
const ChunkList = zcs.ChunkList;
const ChunkPool = zcs.ChunkPool;
const Chunk = zcs.Chunk;
const HandleTab = zcs.HandleTab;
const Arches = zcs.Arches;
const CmdBuf = zcs.CmdBuf;
const CmdPool = zcs.CmdPool;
const view = zcs.view;

const Entities = @This();

handle_tab: HandleTab,
arches: Arches,
pointer_generation: PointerLock.Generation = .{},
reserved_entities: usize = 0,
chunk_pool: ChunkPool,
warned_saturated: u64 = 0,
warned_capacity: bool = false,
warned_chunk_pool: bool = false,
warned_arches: bool = false,
warn_ratio: f32,

const tracy_es_committed = "zcs: committed entities";
const tracy_es_reserved = "zcs: reserved entities";
const tracy_es_saturated = "zcs: saturated entities";
const tracy_chunks = "zcs: reserved chunks";
const tracy_arches = "zcs: archetypes";

/// Options for `init`.
pub const InitOptions = struct {
    /// Used to allocate the entity storage.
    gpa: Allocator,
    /// The capacity of the entity storage.
    cap: Capacity = .{},
    /// When usage of preallocated buffers exceeds this ratio of full capacity, emit a warning.
    warn_ratio: f32 = 0.2,
};

/// The capacity of `Entities`.
pub const Capacity = struct {
    /// The max number of entities.
    entities: u32 = 1000000,
    /// The max number of archetypes.
    arches: u32 = 64,
    /// The number of chunks to allocate.
    chunks: u16 = 4096,
    /// The size of a single chunk in bytes.
    chunk: u32 = 65536,
};

/// Initializes the entity storage with the given capacity.
pub fn init(options: InitOptions) Allocator.Error!@This() {
    const zone = Zone.begin(.{ .src = @src() });
    defer zone.end();

    var handle_tab: HandleTab = try .init(options.gpa, options.cap.entities);
    errdefer handle_tab.deinit(options.gpa);

    var chunk_pool: ChunkPool = try .init(options.gpa, .{
        .chunks = options.cap.chunks,
        .chunk = options.cap.chunk,
    });
    errdefer chunk_pool.deinit(options.gpa);

    var arches: Arches = try .init(options.gpa, options.cap.arches);
    errdefer arches.deinit(options.gpa);

    if (tracy.enabled) {
        var buf: [1024]u8 = undefined;
        const info = std.fmt.bufPrintZ(
            &buf,
            "{}",
            .{options},
        ) catch @panic("OOB");
        tracy.appInfo(info);

        for ([_][:0]const u8{
            tracy_es_committed,
            tracy_es_reserved,
            tracy_es_saturated,
            tracy_chunks,
            tracy_arches,
        }) |name| {
            tracy.plotConfig(.{
                .name = name,
                .format = .number,
                .mode = .line,
                .fill = true,
            });
        }
    }

    return .{
        .handle_tab = handle_tab,
        .arches = arches,
        .chunk_pool = chunk_pool,
        .warn_ratio = options.warn_ratio,
    };
}

/// Destroys the entity storage.
pub fn deinit(self: *@This(), gpa: Allocator) void {
    self.arches.deinit(gpa);
    self.chunk_pool.deinit(gpa);
    self.handle_tab.deinit(gpa);
    self.* = undefined;
}

/// Destroys all entities matching the given arch immediately.
pub fn destroyArchImmediate(self: *@This(), arch: CompFlag.Set) void {
    self.pointer_generation.increment();
    var chunk_lists_iter = self.arches.iterator(self, arch);
    while (chunk_lists_iter.next(self)) |chunk_list| {
        var chunk_list_iter = chunk_list.iterator(self);
        while (chunk_list_iter.next(self)) |chunk| {
            var chunk_iter = chunk.iterator(self);
            while (chunk_iter.next(self)) |entity| {
                self.handle_tab.remove(entity.key);
            }
            chunk.clear(self);
        }
    }
}

/// Recycles all entities compatible with the given archetype. This causes their handles to be
/// dangling, prefer `destroyArchImmediate` unless you're implementing a high throughput event
/// system.
///
/// Invalidates pointers.
pub fn recycleArchImmediate(self: *@This(), arch: CompFlag.Set) void {
    self.pointer_generation.increment();
    var chunk_lists_iter = self.arches.iterator(self, arch);
    while (chunk_lists_iter.next(self)) |chunk_list| {
        var chunk_list_iter = chunk_list.iterator(self);
        while (chunk_list_iter.next(self)) |chunk| {
            var chunk_iter = chunk.iterator(self);
            while (chunk_iter.next(self)) |entity| {
                self.handle_tab.recycle(entity.key);
            }
            chunk.clear(self);
        }
    }
}

/// Recycles all entities.
///
/// Invalidates pointers.
pub fn recycleImmediate(self: *@This()) void {
    self.pointer_generation.increment();
    self.handle_tab.recycleAll();
    self.reserved_entities = 0;
}

/// Returns the current number of entities.
pub fn count(self: *const @This()) usize {
    return self.handle_tab.count() - self.reserved_entities;
}

/// Returns the number of reserved but not committed entities that currently exist.
pub fn reserved(self: *const @This()) usize {
    return self.reserved_entities;
}

/// Given a pointer to a non zero sized component, returns the corresponding entity.
pub fn getEntity(es: *const Entities, from_comp: anytype) Entity {
    const T = @typeInfo(@TypeOf(from_comp)).pointer.child;

    // We could technically pack the entity into the pointer value since both are typically 64
    // bits. However, this would break support for getting a slice of zero sized components from
    // a chunk since all would have the same address.
    //
    // I've chosen to support the slice use case and not this one as it's simpler and seems
    // slightly more likely to come up in generic code. I don't expect either to come up
    // particularly often, this decision can be reversed in the future if one or the other turns
    // out to be desirable. If we do this, make sure to have an assertion/test that valid
    // entities are never fully zero since non optional pointers can't be zero unless explicitly
    // annotated as such.
    comptime assert(@sizeOf(T) != 0);

    return getEntityFromAny(es, .init(T, from_comp));
}

/// Similar to `getEntity`, but does not require compile time types. Assumes a valid pointer to a
/// non zero sized component.
pub fn getEntityFromAny(es: *const Entities, from_comp: Any) Entity {
    // Get the entity index from the chunk
    const loc = getLoc(es, from_comp);
    const indices = loc.chunk.view(es, struct { indices: []const Entity.Index }).?.indices;
    const entity_index = indices[@intFromEnum(loc.index_in_chunk)];

    // Get the entity handle
    assert(@intFromEnum(entity_index) < es.handle_tab.next_index);
    const entity = entity_index.toEntity(es);

    // Assert that the entity has been committed and return it
    assert(entity.committed(es));
    return entity;
}

/// Given a valid pointer to a non zero sized component `from`, returns the corresponding
/// component `Result`, or `null` if there isn't one attached to the same entity. See also
/// `Entity.get`.
pub fn getComp(es: *const Entities, from: anytype, Result: type) ?*Result {
    const T = @typeInfo(@TypeOf(from)).pointer.child;
    // See `from` for why this isn't allowed.
    comptime assert(@sizeOf(T) != 0);
    const slice = getCompFromAny(es, .init(T, from), typeId(Result));
    return @ptrCast(@alignCast(slice));
}

/// Similar to `getComp`, but does not require compile time types.
pub fn getCompFromAny(self: *const Entities, from_comp: Any, get_comp_id: TypeId) ?[]u8 {
    // Check assertions
    if (std.debug.runtime_safety) {
        _ = self.getEntityFromAny(from_comp);
    }

    // Get the component
    const flag = get_comp_id.comp_flag orelse return null;
    const loc = self.getLoc(from_comp);
    // https://github.com/Games-by-Mason/ZCS/issues/24
    const comp_buf_offset = loc.chunk.header().comp_buf_offsets.values[@intFromEnum(flag)];
    if (comp_buf_offset == 0) return null;
    const unsized: [*]u8 = @ptrFromInt(@intFromPtr(loc.chunk) +
        comp_buf_offset +
        get_comp_id.size * @intFromEnum(loc.index_in_chunk));
    return unsized[0..get_comp_id.size];
}

/// Looks up the location of an entity from a component.
fn getLoc(self: *const Entities, from_comp: Any) struct {
    chunk: *Chunk,
    index_in_chunk: Entity.Location.IndexInChunk,
} {
    // See `from` for why this isn't allowed
    assert(from_comp.id.size != 0);

    const pool = &self.chunk_pool;
    const flag = from_comp.id.comp_flag.?;

    // Make sure this component is actually in the chunk pool
    assert(@intFromPtr(from_comp.ptr) >= @intFromPtr(pool.buf.ptr));
    assert(@intFromPtr(from_comp.ptr) <= @intFromPtr(&pool.buf[pool.buf.len - 1]));

    // Get the corresponding chunk by rounding down to the chunk alignment. This works as chunks
    // are aligned to their size, in part to support this operation.
    const chunk: *Chunk = @ptrFromInt(pool.size_align.backward(@intFromPtr(from_comp.ptr)));

    // Calculate the index in this chunk that this component is at
    assert(chunk.header().arch(&self.arches).contains(flag));
    const comp_offset = @intFromPtr(from_comp.ptr) - @intFromPtr(chunk);
    assert(comp_offset != 0); // Zero when missing
    // https://github.com/Games-by-Mason/ZCS/issues/24
    const comp_buf_offset = chunk.header().comp_buf_offsets.values[@intFromEnum(flag)];
    const index_in_chunk = @divExact(comp_offset - comp_buf_offset, from_comp.id.size);

    return .{
        .chunk = chunk,
        .index_in_chunk = @enumFromInt(index_in_chunk),
    };
}

/// Calls `updateEntity` on each compatible entity in an implementation defined order.
/// See also `forEachChunk`.
///
/// `updateEntity` should take `ctx` as an argument, followed by any number of component pointers,
/// optional component pointers, or `Entity`s.
///
/// Invalidating pointers from the update function results in safety checked illegal behavior.
///
/// Note that the implementation only relies on ZCS's public interface. If you have a use case that
/// isn't served well by `forEach`, you can fork it into your code base and modify it as needed.
pub fn forEach(
    self: *@This(),
    comptime name: [:0]const u8,
    comptime updateEntity: anytype,
    ctx: view.params(@TypeOf(updateEntity))[0],
) void {
    const zone = Zone.begin(.{ .src = @src(), .name = name });
    defer zone.end();
    const params = view.params(@TypeOf(updateEntity));
    const View = view.Tuple(params[1..]);
    var iter = self.iterator(View);
    while (iter.next(self)) |vw| {
        @call(.auto, updateEntity, .{ctx} ++ vw);
    }
}

/// Prefer `forEach`. Calls `updateChunk` on each compatible chunk in an implementation
/// defined order, may be useful for batch optimizations.
///
/// `updateChunk` should take `ctx` as an argument, followed by any number of component slices,
/// optional component slices, or const slices of `Entity.Index`.
///
/// Invalidating pointers from the update function results in safety checked illegal behavior.
pub fn forEachChunk(
    self: *@This(),
    comptime name: [:0]const u8,
    comptime updateChunk: anytype,
    ctx: view.params(@TypeOf(updateChunk))[0],
) void {
    const zone = Zone.begin(.{ .src = @src(), .name = name });
    defer zone.end();
    const params = view.params(@TypeOf(updateChunk));
    const required_comps = view.comps(view.Tuple(params[1..]), .{ .size = .slice }) orelse return;
    var chunks = self.chunkIterator(required_comps);
    while (chunks.next(self)) |chunk| {
        const chunk_view = chunk.view(self, view.Tuple(params[1..])).?;
        @call(.auto, updateChunk, .{ctx} ++ chunk_view);
    }
}

/// Options for `forEachThreaded`.
pub fn ForEachThreadedOptions(f: type) type {
    return struct {
        const acquire_cb = view.params(f)[1] == *CmdBuf;
        const Ctx = view.params(f)[0];
        const Comps = view.Tuple(view.params(f)[if (acquire_cb) 2 else 1..]);

        ctx: Ctx,
        tp: *std.Thread.Pool,
        wg: *std.Thread.WaitGroup,
        cp: ?*CmdPool,
    };
}

/// Similar to `forEach`, but spawns each chunk's work as a thread pool task. Optionally, you may
/// add an argument of type `*CmdBuf` as the second argument to get a command buffer if `cp` is set
/// in `options`.
///
/// Each chunk may be given its own command buffer in an arbitrary order. As such, the order of
/// commands within a chunk (and therefore within an `updateEntity` callback) are guaranteed to be
/// serial, but no guarantees are made about command execution order between chunks.
///
/// Keep in mind that this is unlikely to be a performance win unless your update function is very
/// expensive. Iteration is cheap.
pub fn forEachThreaded(
    self: *@This(),
    comptime name: [:0]const u8,
    comptime updateEntity: anytype,
    options: ForEachThreadedOptions(@TypeOf(updateEntity)),
) void {
    const zone = Zone.begin(.{ .src = @src(), .name = name });
    defer zone.end();

    const Opt = @TypeOf(options);

    assert(!Opt.acquire_cb or options.cp != null);

    const Wrapped = struct {
        fn processChunk(
            es: *const Entities,
            chunk: *Chunk,
            ctx: @FieldType(@TypeOf(options), "ctx"),
        ) void {
            const batch_zone = Zone.begin(.{ .src = @src(), .name = name });
            defer batch_zone.end();

            const slices = chunk.view(es, view.Slice(Opt.Comps)).?;
            for (0..chunk.header().len) |i| {
                const vw = view.index(Opt.Comps, es, slices, @intCast(i));
                @call(.auto, updateEntity, .{ctx} ++ vw);
            }
        }

        fn processChunkCb(
            es: *const Entities,
            cp: *CmdPool,
            chunk: *Chunk,
            ctx: @FieldType(@TypeOf(options), "ctx"),
        ) void {
            const batch_zone = Zone.begin(.{ .src = @src(), .name = name });
            defer batch_zone.end();

            const ar = cp.acquire();
            defer cp.release(ar);

            const slices = chunk.view(es, view.Slice(Opt.Comps)).?;
            for (0..chunk.header().len) |i| {
                const vw = view.index(Opt.Comps, es, slices, @intCast(i));
                @call(.auto, updateEntity, .{ ctx, ar.cb } ++ vw);
            }
        }
    };

    const required_comps = view.comps(Opt.Comps, .{ .size = .one }) orelse return;
    var chunks = self.chunkIterator(required_comps);
    while (chunks.next(self)) |chunk| {
        if (Opt.acquire_cb) {
            options.tp.spawnWg(options.wg, Wrapped.processChunkCb, .{
                self,
                options.cp.?,
                chunk,
                options.ctx,
            });
        } else {
            options.tp.spawnWg(options.wg, Wrapped.processChunk, .{
                self,
                chunk,
                options.ctx,
            });
        }
    }
}

/// Options for `forEachChunkThreaded`.
pub fn ForEachChunkThreadedOptions(f: type) type {
    return struct {
        const Ctx = view.params(f)[0];
        const Comps = view.Tuple(view.params(f)[1..]);

        ctx: Ctx,
        tp: *std.Thread.Pool,
        wg: *std.Thread.WaitGroup,
    };
}

/// Similar to `forEach`, but with the threading model from `forEachThreaded`.
pub fn forEachChunkThreaded(
    self: *@This(),
    comptime name: [:0]const u8,
    comptime updateChunk: anytype,
    options: ForEachChunkThreadedOptions(@TypeOf(updateChunk)),
) void {
    const zone = Zone.begin(.{ .src = @src(), .name = name });
    defer zone.end();

    const Opt = @TypeOf(options);

    const Wrapped = struct {
        fn processChunk(
            es: *const Entities,
            chunk: *Chunk,
            ctx: @FieldType(@TypeOf(options), "ctx"),
        ) void {
            const batch_zone = Zone.begin(.{ .src = @src(), .name = name });
            defer batch_zone.end();

            const slices = chunk.view(es, view.Slice(Opt.Comps)).?;
            @call(.auto, updateChunk, .{ctx} ++ slices);
        }
    };

    const required_comps = view.comps(Opt.Comps, .{ .size = .slice }) orelse return;
    var chunks = self.chunkIterator(required_comps);
    while (chunks.next(self)) |chunk| {
        std.Thread.Pool.spawnWg(options.tp, options.wg, Wrapped.processChunk, .{
            self,
            chunk,
            options.ctx,
        });
    }
}

/// Emits a warning if any preallocated buffers are past `warn_ratio` capacity, or if any entity
/// slots have become saturated. If Tracy is enabled, sends usage statistics to Tracy.
///
/// It's recommended that you call this once a frame.
pub fn updateStats(self: *@This()) void {
    if (tracy.enabled) {
        tracy.plot(.{
            .name = tracy_es_committed,
            .value = .{ .i64 = @intCast(self.count()) },
        });
        tracy.plot(.{
            .name = tracy_es_reserved,
            .value = .{ .i64 = @intCast(self.reserved()) },
        });
        tracy.plot(.{
            .name = tracy_es_saturated,
            .value = .{ .i64 = @intCast(self.handle_tab.saturated) },
        });
        tracy.plot(.{
            .name = tracy_chunks,
            .value = .{ .i64 = @intCast(self.chunk_pool.reserved) },
        });
        tracy.plot(.{
            .name = tracy_arches,
            .value = .{ .i64 = @intCast(self.arches.map.count()) },
        });
    }

    if (self.warn_ratio < 1.0) {
        if (self.handle_tab.saturated > self.warned_saturated) {
            self.warned_saturated = self.handle_tab.saturated;
            log.warn("{} entity slots have been saturated", .{self.warned_saturated});
        }

        const handles: f32 = @floatFromInt(self.handle_tab.count());
        const handles_cap: f32 = @floatFromInt(self.handle_tab.capacity);
        if (!self.warned_capacity and handles > handles_cap * self.warn_ratio) {
            self.warned_capacity = true;
            log.warn("entities past {d}% capacity", .{self.warn_ratio * 100.0});
        }

        const chunks: f32 = @floatFromInt(self.chunk_pool.reserved);
        const chunks_cap_int = self.chunk_pool.buf.len / self.chunk_pool.size_align.toByteUnits();
        const chunks_cap: f32 = @floatFromInt(chunks_cap_int);
        if (!self.warned_chunk_pool and chunks > chunks_cap * self.warn_ratio) {
            self.warned_chunk_pool = true;
            log.warn("chunk pool past {d}% capacity", .{self.warn_ratio * 100.0});
        }

        const arches: f32 = @floatFromInt(self.arches.map.count());
        const arhces_cap: f32 = @floatFromInt(self.arches.map.capacity());
        if (!self.warned_arches and arches > arhces_cap * self.warn_ratio) {
            self.warned_arches = true;
            log.warn("archetypes past {d}% capacity", .{self.warn_ratio * 100.0});
        }
    }
}

/// Returns an iterator over all the chunks with at least the components in `required_comps` in
/// an implementation defined order.
///
/// Invalidating pointers while iterating results in safety checked illegal behavior.
pub fn chunkIterator(
    self: *const @This(),
    required_comps: CompFlag.Set,
) ChunkIterator {
    var lists = self.arches.iterator(self, required_comps);
    const chunks: ChunkList.Iterator = if (lists.next(self)) |l| l.iterator(self) else .empty(self);
    var result: ChunkIterator = .{
        .lists = lists,
        .chunks = chunks,
    };
    result.catchUp(self);
    return result;
}

/// See `chunkIterator`.
pub const ChunkIterator = struct {
    lists: Arches.Iterator,
    chunks: ChunkList.Iterator,

    /// Returns the pointer lock.
    pub fn pointerLock(self: *const ChunkIterator) PointerLock {
        return self.lists.pointer_lock;
    }

    /// Returns an empty iterator.
    pub fn empty(es: *const Entities) @This() {
        return .{
            .lists = .empty(es),
            .chunks = .empty(es),
        };
    }

    /// Advance the internal state so that `peek` is in sync.
    fn catchUp(self: *@This(), es: *const Entities) void {
        while (self.chunks.chunk == null) {
            if (self.lists.next(es)) |chunk_list| {
                self.chunks = chunk_list.iterator(es);
            } else {
                break;
            }
        }
    }

    /// Returns the current chunk without advancing.
    pub fn peek(self: *const @This(), es: *const Entities) ?*Chunk {
        return self.chunks.peek(es);
    }

    /// Advances the iterator, returning the next entity.
    pub fn next(self: *@This(), es: *const Entities) ?*Chunk {
        self.pointerLock().check(es.pointer_generation);

        // We need to loop here because while chunks can't be empty, chunk lists can
        const chunk = while (true) {
            // Get the next chunk in this list
            if (self.chunks.next(es)) |chunk| break chunk;

            // If that fails, get the next list and try again
            if (self.lists.next(es)) |chunk_list| {
                @branchHint(.likely);
                self.chunks = chunk_list.iterator(es);
                continue;
            }

            // If that fails, return null
            return null;
        };

        // Catch up the peek state.
        self.catchUp(es);

        return chunk;
    }
};

/// Returns an iterator over all entities that have at least the components in `required_comps` in
/// chunk order. The results are of type `View` which is a struct where each field is either a
/// pointer to a component, an optional pointer to a component, or `Entity`.
///
/// In the general case, it's simplest to consider chunk order to be implementation defined.
/// However, chunk order does have the useful guarantee that entities added to an archetype that
/// starts out empty with no intermittent deletions will always be iterated in order. This can be
/// useful for preserving order of transient events which are always cleared in one go.
///
/// Invalidating pointers while iterating results in safety checked illegal behavior.
pub fn iterator(self: *const @This(), View: type) Iterator(View) {
    const required_comps: CompFlag.Set = view.comps(View, .{ .size = .one }) orelse
        return .empty(self);
    const chunks = self.chunkIterator(required_comps);
    const slices = if (chunks.peek(self)) |c| c.view(self, view.Slice(View)).? else undefined;
    return .{
        .chunks = chunks,
        .slices = slices,
        .index_in_chunk = 0,
    };
}

/// See `Entities.iterator`.
pub fn Iterator(View: type) type {
    return struct {
        const Slices = view.Slice(View);

        chunks: ChunkIterator,
        slices: Slices,
        index_in_chunk: u32,

        /// Returns an empty iterator.
        pub fn empty(es: *const Entities) @This() {
            return .{
                .chunks = .empty(es),
                .slices = undefined,
                .index_in_chunk = 0,
            };
        }

        /// Advances the iterator, returning the next view.
        pub fn next(self: *@This(), es: *const Entities) ?View {
            // Check for pointer invalidation
            self.chunks.pointerLock().check(es.pointer_generation);

            // Get the current chunk
            var chunk = self.chunks.peek(es) orelse {
                @branchHint(.unlikely);
                return null;
            };
            assert(chunk.header().len > 0); // Free chunks are returned to the chunk pool

            // If we're done with the current chunk, advance to the next one
            if (self.index_in_chunk >= chunk.header().len) {
                _ = self.chunks.next(es).?;
                chunk = self.chunks.peek(es) orelse {
                    @branchHint(.unlikely);
                    return null;
                };
                self.index_in_chunk = 0;
                assert(chunk.header().len > 0); // Free chunks are returned to the chunk pool
                self.slices = chunk.view(es, Slices).?;
            }

            // Get the entity and advance the index, this can't overflow the counter since we can't
            // have as many entities as bytes in a chunk since the space would be used up by the
            // entity indices
            const result = view.index(View, es, self.slices, self.index_in_chunk);
            self.index_in_chunk += 1;
            return result;
        }
    };
}

//! A list of all chunks with a given archetype.

const std = @import("std");
const zcs = @import("root.zig");
const tracy = @import("tracy");

const ChunkPool = zcs.ChunkPool;
const CompFlag = zcs.CompFlag;
const Chunk = zcs.Chunk;
const Entities = zcs.Entities;
const PointerLock = zcs.PointerLock;
const Arches = zcs.Arches;
const Entity = zcs.Entity;

const Zone = tracy.Zone;

const typeId = zcs.typeId;

const assert = std.debug.assert;
const alignForward = std.mem.alignForward;
const math = std.math;

const ChunkList = @This();

/// The chunks in this chunk list, connected via the `next` and `prev` fields.
head: Chunk.Index,
/// The final chunk in this chunk list.
tail: Chunk.Index,
/// The chunks in this chunk list that have space available, connected via the `next_avail`
/// and `prev_avail` fields.
avail: Chunk.Index,
/// The offset to the entity index buffer.
index_buf_offset: u32,
/// A map from component flags to the byte offset of their arrays. Components that aren't
/// present or are zero sized have undefined offsets. It's generally faster to read this from
/// the chunk instead of the chunk list to avoid the extra cache miss.
comp_buf_offsets_cold: std.enums.EnumArray(CompFlag, u32),
/// The number of entities that can be stored in a single chunk from this list.
chunk_capacity: u32,

/// The index of a `ChunkList` in the `Arches` that owns it.
pub const Index = enum(u32) {
    /// Gets a chunk list from a chunk list ID.
    pub fn get(self: @This(), arches: *const Arches) *ChunkList {
        return &arches.map.values()[@intFromEnum(self)];
    }

    /// Gets the archetype for a chunk list.
    pub fn arch(self: Index, arches: *const Arches) CompFlag.Set {
        return arches.map.keys()[@intFromEnum(self)];
    }

    _,
};

/// Initializes a chunk list.
pub fn init(pool: *const ChunkPool, arch: CompFlag.Set) error{ZcsChunkOverflow}!@This() {
    const zone = Zone.begin(.{ .src = @src() });
    defer zone.end();

    // Sort the components from greatest to least alignment.
    const sorted = sortCompsByAlignment(arch);

    // Get the max alignment required by any component or entity index
    var max_align: u32 = @alignOf(Entity.Index);
    if (sorted.len > 0) max_align = @max(max_align, sorted.get(0).getId().alignment);

    // Initialize the offset to the start of the data
    const data_offset: u32 = alignForward(u32, @sizeOf(Chunk.Header), max_align);

    // Calculate how many bytes of data there are
    const bytes = math.sub(
        u32,
        @intCast(pool.size_align.toByteUnits()),
        data_offset,
    ) catch return error.ZcsChunkOverflow;

    // Calculate how much space one entity takes
    var entity_size: u32 = @sizeOf(Entity.Index);
    for (sorted.constSlice()) |comp| {
        const comp_size = math.cast(u32, comp.getId().size) orelse
            return error.ZcsChunkOverflow;
        entity_size = math.add(u32, entity_size, comp_size) catch
            return error.ZcsChunkOverflow;
    }

    // Calculate the capacity in entities
    const chunk_capacity: u32 = bytes / entity_size;

    // Check that we have enough space for at least one entity
    if (chunk_capacity <= 0) return error.ZcsChunkOverflow;

    // Calculate the offsets for each component
    var offset: u32 = data_offset;
    var comp_buf_offsets: std.enums.EnumArray(CompFlag, u32) = .initFill(0);
    var index_buf_offset: u32 = 0;
    for (sorted.constSlice()) |comp| {
        const id = comp.getId();

        // If we haven't found a place for the index buffer, check if it can go here
        if (index_buf_offset == 0 and id.alignment <= @alignOf(Entity.Index)) {
            assert(offset % @alignOf(Entity.Index) == 0);
            index_buf_offset = offset;
            const buf_size = math.mul(u32, @sizeOf(Entity.Index), chunk_capacity) catch
                return error.ZcsChunkOverflow;
            offset = math.add(u32, offset, buf_size) catch
                return error.ZcsChunkOverflow;
        }

        // Store the offset to this component type
        assert(offset % id.alignment == 0);
        comp_buf_offsets.set(comp, offset);
        const comp_size = math.cast(u32, id.size) orelse
            return error.ZcsChunkOverflow;
        const buf_size = math.mul(u32, comp_size, chunk_capacity) catch
            return error.ZcsChunkOverflow;
        offset = math.add(u32, offset, buf_size) catch
            return error.ZcsChunkOverflow;
    }

    // If we haven't found a place for the index buffer, place it at the end
    if (index_buf_offset == 0) {
        index_buf_offset = offset;
        const buf_size = math.mul(u32, @sizeOf(Entity.Index), chunk_capacity) catch
            return error.ZcsChunkOverflow;
        offset = math.add(u32, offset, buf_size) catch
            return error.ZcsChunkOverflow;
    }

    assert(offset <= pool.size_align.toByteUnits());

    return .{
        .head = .none,
        .tail = .none,
        .avail = .none,
        .comp_buf_offsets_cold = comp_buf_offsets,
        .index_buf_offset = index_buf_offset,
        .chunk_capacity = chunk_capacity,
    };
}

/// Adds an entity to the chunk list.
pub fn append(
    self: *@This(),
    es: *Entities,
    e: Entity,
) error{ZcsChunkPoolOverflow}!Entity.Location {
    const pool = &es.chunk_pool;
    const arches = &es.arches;

    // Ensure there's a chunk with space available
    if (self.avail == .none) {
        // Allocate a new chunk
        const new = try pool.reserve(es, arches.indexOf(self));
        const new_index = pool.indexOf(new);

        // Point the available list to the new chunk
        self.avail = new_index;

        // Add the new chunk to the end of the chunk list
        if (self.tail.get(pool)) |tail| {
            new.header().prev = self.tail;
            tail.header().next = new_index;
            self.tail = new_index;
        } else {
            self.head = new_index;
            self.tail = new_index;
        }
    }

    // Get the next chunk with space available
    const chunk = self.avail.get(pool).?;
    const header = chunk.header();
    assert(header.len < self.chunk_capacity);
    chunk.checkAssertions(es, .allow_empty);

    // Append the entity. This const cast is okay since the chunk was originally mutable, we
    // just don't expose mutable pointers to the index buf publicly.
    const index_in_chunk: Entity.Location.IndexInChunk = @enumFromInt(header.len);
    header.len += 1;
    const index_buf = @constCast(chunk.view(es, struct {
        indices: []const Entity.Index,
    }).?.indices);
    index_buf[@intFromEnum(index_in_chunk)] = @enumFromInt(e.key.index);

    // If the chunk is now full, remove it from the available list
    if (header.len == self.chunk_capacity) {
        @branchHint(.unlikely);
        assert(self.avail.get(pool) == chunk);
        self.avail = chunk.header().next_avail;
        header.next_avail = .none;
        if (self.avail.get(pool)) |avail| {
            const available_header = avail.header();
            assert(available_header.prev_avail.get(pool) == chunk);
            available_header.prev_avail = .none;
        }
    }

    self.checkAssertions(es);

    // Return the location we stored the entity
    return .{
        .chunk = es.chunk_pool.indexOf(chunk),
        .index_in_chunk = index_in_chunk,
    };
}

// Checks internal consistency.
pub fn checkAssertions(self: *const @This(), es: *const Entities) void {
    if (!std.debug.runtime_safety) return;

    const pool = &es.chunk_pool;

    if (self.head.get(pool)) |head| {
        const header = head.header();
        head.checkAssertions(es, .default);
        self.tail.get(pool).?.checkAssertions(es, .default);
        assert(@intFromBool(header.next != .none) ^
            @intFromBool(head == self.tail.get(pool)) != 0);
        assert(self.tail != .none);
    } else {
        assert(self.tail == .none);
        assert(self.avail == .none);
    }

    if (self.avail.get(pool)) |avail| {
        const header = avail.header();
        avail.checkAssertions(es, .default);
        assert(header.prev_avail == .none);
    }
}

/// Returns an iterator over this chunk list's chunks.
///
/// Invalidating pointers while iterating results in safety checked illegal behavior.
pub fn iterator(self: *const @This(), es: *const Entities) Iterator {
    self.checkAssertions(es);
    return .{
        .chunk = self.head.get(&es.chunk_pool),
        .pointer_lock = es.pointer_generation.lock(),
    };
}

/// An iterator over a chunk list's chunks.
pub const Iterator = struct {
    /// Returns an empty iterator.
    pub fn empty(es: *const Entities) @This() {
        return .{
            .chunk = null,
            .pointer_lock = es.pointer_generation.lock(),
        };
    }

    chunk: ?*Chunk,
    pointer_lock: PointerLock,

    /// Returns the next chunk and advances.
    pub fn next(self: *@This(), es: *const Entities) ?*Chunk {
        self.pointer_lock.check(es.pointer_generation);
        const chunk = self.chunk orelse {
            @branchHint(.unlikely);
            return null;
        };
        chunk.checkAssertions(es, .default);
        const header = chunk.header();
        self.chunk = header.next.get(&es.chunk_pool);
        return chunk;
    }

    /// Peeks at the next chunk.
    pub fn peek(self: @This(), es: *const Entities) ?*Chunk {
        self.pointer_lock.check(es.pointer_generation);
        return self.chunk;
    }
};

/// Compares the alignment of two component types.
fn alignmentGte(_: void, lhs: CompFlag, rhs: CompFlag) bool {
    const lhs_alignment = lhs.getId().alignment;
    const rhs_alignment = rhs.getId().alignment;
    return lhs_alignment >= rhs_alignment;
}

/// Returns a list of the components in this set sorted from greatest to least alignment. This
/// is an optimization to reduce padding, but also necessary to get consistent cutoffs for how
/// much data fits in a chunk regardless of registration order.
inline fn sortCompsByAlignment(set: CompFlag.Set) std.BoundedArray(CompFlag, CompFlag.max) {
    const zone = Zone.begin(.{ .src = @src() });
    defer zone.end();
    var comps: std.BoundedArray(CompFlag, CompFlag.max) = .{};
    var iter = set.iterator();
    while (iter.next()) |flag| comps.appendAssumeCapacity(flag);
    std.sort.pdq(CompFlag, comps.slice(), {}, alignmentGte);
    return comps;
}

test sortCompsByAlignment {
    defer CompFlag.unregisterAll();

    // Register various components with different alignments in an arbitrary order
    const a_1 = CompFlag.registerImmediate(typeId(struct { x: u8 align(1) }));
    const e_2 = CompFlag.registerImmediate(typeId(struct { x: u8 align(16) }));
    const d_2 = CompFlag.registerImmediate(typeId(struct { x: u8 align(8) }));
    const e_0 = CompFlag.registerImmediate(typeId(struct { x: u8 align(16) }));
    const b_2 = CompFlag.registerImmediate(typeId(struct { x: u8 align(2) }));
    const d_0 = CompFlag.registerImmediate(typeId(struct { x: u8 align(8) }));
    const a_2 = CompFlag.registerImmediate(typeId(struct { x: u8 align(1) }));
    const e_1 = CompFlag.registerImmediate(typeId(struct { x: u8 align(16) }));
    const c_0 = CompFlag.registerImmediate(typeId(struct { x: u8 align(4) }));
    const b_0 = CompFlag.registerImmediate(typeId(struct { x: u8 align(2) }));
    const b_1 = CompFlag.registerImmediate(typeId(struct { x: u8 align(2) }));
    const a_0 = CompFlag.registerImmediate(typeId(struct { x: u8 align(1) }));
    const c_2 = CompFlag.registerImmediate(typeId(struct { x: u8 align(4) }));
    const c_1 = CompFlag.registerImmediate(typeId(struct { x: u8 align(4) }));
    const d_1 = CompFlag.registerImmediate(typeId(struct { x: u8 align(8) }));

    // Test sorting all of them
    {
        // Sort them
        const sorted = sortCompsByAlignment(.initMany(&.{
            e_0,
            c_1,
            d_0,
            e_1,
            a_1,
            b_1,
            b_0,
            a_0,
            d_1,
            c_0,
            c_2,
            e_2,
            a_2,
            b_2,
            d_2,
        }));
        try std.testing.expectEqual(15, sorted.len);
        var prev: usize = math.maxInt(usize);
        for (sorted.constSlice()) |flag| {
            const curr = flag.getId().alignment;
            try std.testing.expect(curr <= prev);
            prev = curr;
        }
    }

    // Test sorting a subset of them
    {
        // Sort them
        const sorted = sortCompsByAlignment(.initMany(&.{
            e_0,
            d_0,
            c_0,
            a_0,
            b_0,
        }));
        try std.testing.expectEqual(5, sorted.len);
        try std.testing.expectEqual(e_0, sorted.get(0));
        try std.testing.expectEqual(d_0, sorted.get(1));
        try std.testing.expectEqual(c_0, sorted.get(2));
        try std.testing.expectEqual(b_0, sorted.get(3));
        try std.testing.expectEqual(a_0, sorted.get(4));
    }
}

const std = @import("std");
const zcs = @import("root.zig");

const assert = std.debug.assert;

const typeId = zcs.typeId;

const Entity = zcs.Entity;
const Entities = zcs.Entities;
const PointerLock = zcs.PointerLock;
const TypeId = zcs.TypeId;
const CompFlag = zcs.CompFlag;
const ChunkList = zcs.ChunkList;
const Arches = zcs.Arches;
const ChunkPool = zcs.ChunkPool;

/// A chunk of entity data where each entity has the same archetype. This type is mostly used
/// internally, you should prefer the higher level API in most cases.
pub const Chunk = opaque {
    /// A chunk's index in its `ChunkPool`.
    pub const Index = enum(u32) {
        /// The none index. The capacity is always less than this value, so there's no overlap.
        none = std.math.maxInt(u32),
        _,

        /// Gets a chunk from the chunk ID.
        pub fn get(self: Index, pool: *const ChunkPool) ?*Chunk {
            if (self == .none) return null;
            const byte_idx = @shlExact(
                @intFromEnum(self),
                @intCast(@intFromEnum(pool.size_align)),
            );
            const result: *Chunk = @ptrCast(&pool.buf[byte_idx]);
            assert(@intFromEnum(self) < pool.reserved);
            assert(pool.indexOf(result) == self);
            return result;
        }
    };

    /// Meta information for a chunk.
    pub const Header = struct {
        /// The offsets to each component, or 0 for each that is missing.
        ///
        /// This is also stored on the chunk list, but duplicating this state to the chunk
        /// measurably improves benchmarks, likely due to reducing cache misses.
        comp_buf_offsets: std.enums.EnumArray(CompFlag, u32),
        /// The chunk list this chunk is part of, if any.
        list: ChunkList.Index,
        /// The next chunk, if any.
        next: Index = .none,
        /// The previous chunk, if any.
        prev: Index = .none,
        /// The next chunk with available space, if any.
        next_avail: Index = .none,
        /// The previous chunk with available space, if any.
        prev_avail: Index = .none,
        /// The number of entities in this chunk.
        len: u32,

        /// Returns this chunk's archetype. When checking for individual components, prefer checking
        /// `comp_buf_offsets`. This value larger but nearer in memory.
        pub fn arch(self: *const @This(), lists: *const Arches) CompFlag.Set {
            return self.list.arch(lists);
        }
    };

    /// An offset from the start of a chunk to a component buffer.
    pub const CompBufOffset = enum(u32) {
        none = 0,
        _,

        pub inline fn unwrap(self: @This()) ?u32 {
            const result = @intFromEnum(self);
            if (result == 0) return null;
            return result;
        }
    };

    /// Checks for self consistency.
    pub fn checkAssertions(
        self: *Chunk,
        es: *const Entities,
        mode: enum {
            /// By default, chunks may not be empty since if they were they'd have been returned to
            /// the chunk pool.
            default,
            /// However if we're checking a just allocated chunk, or one we're about to clear, we
            /// can skip this check.
            allow_empty,
        },
    ) void {
        if (!std.debug.runtime_safety) return;

        const list = self.header().list.get(&es.arches);
        const pool = &es.chunk_pool;

        // Validate next/prev
        if (self.header().next.get(pool)) |next_chunk| {
            assert(next_chunk.header().prev.get(pool) == self);
        }

        if (self.header().prev.get(pool)) |prev_chunk| {
            assert(prev_chunk.header().next.get(pool) == self);
        }

        if (self == list.head.get(pool)) assert(self.header().prev == .none);
        if (self == list.tail.get(pool)) assert(self.header().next == .none);

        if (self.header().len >= list.chunk_capacity) {
            // Validate full chunks
            assert(self.header().len == list.chunk_capacity);
            assert(self.header().next_avail == .none);
            assert(self.header().prev_avail == .none);
        } else {
            // Available chunks shouldn't be empty, since empty chunks are returned to the chunk
            // pool. `allow_empty` is set to true when checking a chunk that's about to be returned
            // to the chunk pool.
            assert(mode == .allow_empty or self.header().len > 0);

            // Validate next/prev available
            if (self.header().next_avail.get(pool)) |next_available_chunk| {
                assert(next_available_chunk.header().prev_avail.get(pool) == self);
            }

            if (self.header().prev_avail.get(pool)) |prev_available_chunk| {
                assert(prev_available_chunk.header().next_avail.get(pool) == self);
            }

            if (self == list.avail.get(pool)) {
                assert(self.header().prev_avail == .none);
            }
        }
    }

    /// Returns a pointer to the chunk header.
    pub inline fn header(self: *Chunk) *Header {
        return @alignCast(@ptrCast(self));
    }

    /// Clears the chunk's entity data.
    pub fn clear(self: *Chunk, es: *Entities) void {
        // Get the header and chunk list
        const pool = &es.chunk_pool;
        const index = pool.indexOf(self);
        const list = self.header().list.get(&es.arches);

        // Validate this chunk
        self.checkAssertions(es, .allow_empty);

        // Remove this chunk from the chunk list head/tail
        if (list.head == index) list.head = self.header().next;
        if (list.tail == index) list.tail = self.header().prev;
        if (list.avail == index) list.avail = self.header().next_avail;

        // Remove this chunk from the chunk list normal and available linked lists
        if (self.header().prev.get(pool)) |prev| prev.header().next = self.header().next;
        if (self.header().next.get(pool)) |next| next.header().prev = self.header().prev;
        if (self.header().prev_avail.get(pool)) |prev| prev.header().next_avail = self.header().next_avail;
        if (self.header().next_avail.get(pool)) |next| next.header().prev_avail = self.header().prev_avail;

        // Reset this chunk, and add it to the pool's free list
        self.header().* = undefined;
        self.header().next = es.chunk_pool.free;
        es.chunk_pool.free = index;

        // Validate the chunk list
        list.checkAssertions(es);
    }

    /// Returns an entity slice view of type `View` into this chunk. See `zcs.view`.
    pub fn view(self: *@This(), es: *const Entities, View: type) ?View {
        const list = self.header().list.get(&es.arches);

        const view_arch = zcs.view.comps(View, .{ .size = .slice }) orelse return null;
        const chunk_arch = self.header().arch(&es.arches);
        if (!chunk_arch.supersetOf(view_arch)) return null;

        var result: View = undefined;
        inline for (@typeInfo(View).@"struct".fields) |field| {
            const As = zcs.view.UnwrapField(field.type, .{ .size = .slice });
            if (As == Entity.Index) {
                const unsized: [*]As = @ptrFromInt(@intFromPtr(self) + list.index_buf_offset);
                const sized = unsized[0..self.header().len];
                @field(result, field.name) = sized;
            } else {
                // https://github.com/Games-by-Mason/ZCS/issues/24
                const offset = if (typeId(As).comp_flag) |flag|
                    self.header().comp_buf_offsets.values[@intFromEnum(flag)]
                else
                    0;
                if (@typeInfo(field.type) == .optional and offset == 0) {
                    @field(result, field.name) = null;
                } else {
                    assert(offset != 0); // Arch checked above
                    const unsized: [*]As = @ptrFromInt(@intFromPtr(self) + offset);
                    const sized = unsized[0..self.header().len];
                    @field(result, field.name) = sized;
                }
            }
        }
        return result;
    }

    /// Similar to `view`, but only gets a single component slice and doesn't require comptime
    /// types.
    pub fn compsFromId(self: *Chunk, id: TypeId) ?[]u8 {
        const flag = id.comp_flag orelse return null;
        // https://github.com/Games-by-Mason/ZCS/issues/24
        const offset = self.header().comp_buf_offsets.values[@intFromEnum(flag)];
        if (offset == 0) return null;
        const ptr: [*]u8 = @ptrFromInt(@intFromPtr(self) + offset);
        return ptr[0 .. self.header().len * id.size];
    }

    /// Swap removes an entity from the chunk, updating the location of the moved entity. This is
    /// typically used internally, for external uses you're likely looking for
    /// `Entity.destroy` or `Entity.destroyImmediate`.
    pub fn swapRemove(
        self: *@This(),
        es: *Entities,
        index_in_chunk: Entity.Location.IndexInChunk,
    ) void {
        const pool = &es.chunk_pool;
        const index = pool.indexOf(self);
        const list = self.header().list.get(&es.arches);
        const was_full = self.header().len >= list.chunk_capacity;

        // Get the last entity
        const indices = @constCast(self.view(es, struct {
            indices: []const Entity.Index,
        }).?.indices);
        const new_len = self.header().len - 1;
        const moved = indices[new_len];

        // Early out if we're popping the end of the list
        if (@intFromEnum(index_in_chunk) == new_len) {
            // Clear the previous entity data
            if (std.debug.runtime_safety) {
                indices[@intFromEnum(index_in_chunk)] = undefined;
                var it = self.header().arch(&es.arches).iterator();
                while (it.next()) |flag| {
                    const id = flag.getId();
                    const comp_buffer = self.compsFromId(id).?;
                    const comp_offset = new_len * id.size;
                    const comp = comp_buffer[comp_offset..][0..id.size];
                    @memset(comp, undefined);
                }
            }

            // Update the chunk's length, possibly returning it to the chunk pool
            if (new_len == 0) {
                self.clear(es);
            } else {
                self.header().len = new_len;
            }

            // Early out
            return;
        }

        // Overwrite the removed entity index with the popped entity index
        indices[@intFromEnum(index_in_chunk)] = moved;

        // Move the moved entity's components
        {
            var move = self.header().arch(&es.arches).iterator();
            while (move.next()) |flag| {
                const id = flag.getId();

                const comp_buffer = self.compsFromId(id).?;
                const new_comp_offset = @intFromEnum(index_in_chunk) * id.size;
                const new_comp = comp_buffer[new_comp_offset..][0..id.size];

                const prev_comp_offset = new_len * id.size;
                const prev_comp = comp_buffer[prev_comp_offset..][0..id.size];

                @memcpy(new_comp, prev_comp);
                @memset(prev_comp, undefined);
            }
        }

        // Pop the last entity
        self.header().len = new_len;

        // Update the location of the moved entity in the handle table
        const moved_loc = &es.handle_tab.slots[@intFromEnum(moved)].value;
        assert(moved_loc.chunk.get(&es.chunk_pool) == self);
        moved_loc.index_in_chunk = index_in_chunk;

        // If this chunk was previously full, add it to this chunk list's available list
        if (was_full) {
            if (list.avail.get(pool)) |head| {
                // Don't disturb the front of the available list if there is one, this decreases
                // fragmentation by guaranteeing that we fill one chunk at a time.
                self.header().next_avail = head.header().next_avail;
                if (self.header().next_avail.get(pool)) |next_avail| {
                    next_avail.header().prev_avail = index;
                }
                self.header().prev_avail = list.avail;
                head.header().next_avail = index;
            } else {
                // If the available list is empty, set it to this chunk
                list.avail = index;
            }
        }

        list.checkAssertions(es);
        self.checkAssertions(es, .default);
    }

    /// Returns an iterator over this chunk's entities.
    ///
    /// Invalidating pointers while iterating results in safety checked illegal behavior.
    pub fn iterator(self: *@This(), es: *const Entities) Iterator {
        return .{
            .chunk = self,
            .index_in_chunk = @enumFromInt(0),
            .pointer_lock = es.pointer_generation.lock(),
        };
    }

    /// An iterator over a chunk's entities.
    pub const Iterator = struct {
        chunk: *Chunk,
        index_in_chunk: Entity.Location.IndexInChunk,
        pointer_lock: PointerLock,

        pub fn next(self: *@This(), es: *const Entities) ?Entity {
            self.pointer_lock.check(es.pointer_generation);
            if (@intFromEnum(self.index_in_chunk) >= self.chunk.header().len) {
                @branchHint(.unlikely);
                return null;
            }
            const indices = self.chunk.view(es, struct { indices: []const Entity.Index }).?.indices;
            const entity_index = indices[@intFromEnum(self.index_in_chunk)];
            self.index_in_chunk = @enumFromInt(@intFromEnum(self.index_in_chunk) + 1);
            return entity_index.toEntity(es);
        }
    };
};

//! A map from archetypes to their chunk lists.

const std = @import("std");
const zcs = @import("root.zig");
const tracy = @import("tracy");

const Zone = tracy.Zone;

const assert = std.debug.assert;

const Entities = zcs.Entities;
const CompFlag = zcs.CompFlag;
const PointerLock = zcs.PointerLock;
const ChunkList = zcs.ChunkList;
const ChunkPool = zcs.ChunkPool;

const Allocator = std.mem.Allocator;

pub const Arches = @This();

capacity: u32,
map: std.ArrayHashMapUnmanaged(
    CompFlag.Set,
    ChunkList,
    struct {
        pub fn eql(_: @This(), lhs: CompFlag.Set, rhs: CompFlag.Set, _: usize) bool {
            return lhs.eql(rhs);
        }
        pub fn hash(_: @This(), key: CompFlag.Set) u32 {
            return @truncate(std.hash.int(key.bits.mask));
        }
    },
    false,
),

/// Initializes the chunk lists.
pub fn init(gpa: Allocator, capacity: u32) Allocator.Error!@This() {
    const zone = Zone.begin(.{ .src = @src() });
    defer zone.end();

    var map: @FieldType(@This(), "map") = .{};
    errdefer map.deinit(gpa);
    // We reserve one extra to work around a slightly the slightly awkward get or put API.
    try map.ensureTotalCapacity(gpa, @as(u32, capacity) + 1);
    map.lockPointers();
    return .{
        .capacity = capacity,
        .map = map,
    };
}

/// Frees the chunk lists.
pub fn deinit(self: *@This(), gpa: Allocator) void {
    self.map.unlockPointers();
    self.map.deinit(gpa);
    self.* = undefined;
}

/// Resets arches back to its initial state.
pub fn clear(self: *@This()) void {
    self.map.unlockPointers();
    self.map.clearRetainingCapacity();
    self.map.lockPointers();
}

/// Gets the chunk list for the given archetype, initializing it if it doesn't exist.
pub fn getOrPut(
    self: *@This(),
    pool: *const ChunkPool,
    arch: CompFlag.Set,
) error{ ZcsChunkOverflow, ZcsArchOverflow }!*ChunkList {
    // This is a bit awkward, but works around there not being a get or put variation
    // that fails when allocation is needed.
    //
    // In practice this code path will only be executed when we're about to fail in a likely
    // fatal way, so the mild amount of extra work isn't worth creating a whole new gop
    // variant over.
    //
    // Note that we reserve space for the requested capacity + 1 in `init` to make this
    // work.
    const gop = self.map.getOrPutAssumeCapacity(arch);
    errdefer if (!gop.found_existing) {
        @branchHint(.cold);
        // We have to unlock pointers to do this, but we're just doing a swap remove so the
        // indices that we already store into the array won't change.
        self.map.unlockPointers();
        assert(self.map.swapRemove(arch));
        self.map.lockPointers();
    };
    if (!gop.found_existing) {
        @branchHint(.unlikely);
        if (self.map.count() > self.capacity) return error.ZcsArchOverflow;
        gop.value_ptr.* = try .init(pool, arch);
    }
    return gop.value_ptr;
}

/// Gets the index of a chunk list.
pub fn indexOf(lists: *const @This(), self: *const ChunkList) ChunkList.Index {
    const vals = lists.map.values();

    assert(@intFromPtr(self) >= @intFromPtr(vals.ptr));
    assert(@intFromPtr(self) < @intFromPtr(vals.ptr) + vals.len * @sizeOf(ChunkList));

    const offset = @intFromPtr(self) - @intFromPtr(vals.ptr);
    const index = offset / @sizeOf(ChunkList);
    return @enumFromInt(index);
}

/// Returns an iterator over the chunk lists that have the given components.
///
/// Invalidating pointers while iterating results in safety checked illegal behavior.
pub fn iterator(self: @This(), es: *const Entities, required_comps: CompFlag.Set) Iterator {
    return .{
        .required_comps = required_comps,
        .all = self.map.iterator(),
        .pointer_lock = es.pointer_generation.lock(),
    };
}

/// An iterator over chunk lists that have the given components.
pub const Iterator = struct {
    required_comps: CompFlag.Set,
    all: @FieldType(Arches, "map").Iterator,
    pointer_lock: PointerLock,

    /// Returns an empty iterator.
    pub fn empty(es: *const Entities) @This() {
        return .{
            .required_comps = .{},
            .all = b: {
                const map: @FieldType(Arches, "map") = .{};
                break :b map.iterator();
            },
            .pointer_lock = es.pointer_generation.lock(),
        };
    }

    pub fn next(self: *@This(), es: *const Entities) ?*const ChunkList {
        self.pointer_lock.check(es.pointer_generation);
        while (self.all.next()) |item| {
            if (item.key_ptr.*.supersetOf(self.required_comps)) {
                return item.value_ptr;
            }
        }
        return null;
    }
};

//! An entity view is a user defined struct or tuple used as a temporary view of an entity. It may
//! contain any number of fields, where each field is either of type `Entity`, an optional single
//! item pointer to a component, or a single item pointer to a component.
//!
//! Additionally, entity slice views are views over multiple entities. Slice views are similar to
//! normal entity views, but instead of single item pointers they have slices, and `Entity.Index` is
//! used in place of `Entity`.
//!
//! These views are temporary, as immediate operations on entity's are allowed to move component
//! memory.
//!
//! This file provides infrastructure to support entity views and entity slice views.

const std = @import("std");
const assert = std.debug.assert;
const zcs = @import("root.zig");
const typeId = zcs.typeId;
const Entities = zcs.Entities;
const Entity = zcs.Entity;
const Any = zcs.Any;
const CompFlag = zcs.CompFlag;

/// Options for a view.
pub const ViewOptions = struct {
    /// The pointer type for the view's components.
    size: std.builtin.Type.Pointer.Size,
};

/// Given a field type from an entity view or entity slice view, returns the underlying component
/// type or `Entity`/`Entity.Index` as appropriate.
///
/// For entity views, `size` should be set to `.one`. Otherwise it should be set to `.slice` (or the
/// desired pointer size.)
///
/// If `track_ids` is `true`, `ThreadId` is also allowed as a parameter.
pub fn UnwrapField(T: type, options: ViewOptions) type {
    // If we're looking for a single element and `T` is an entity, return it directly
    if (options.size == .one and T == Entity) {
        return Entity;
    }

    // Get the component pointer
    const SomePtr = Unwrap(T);
    comptime assert(@typeInfo(SomePtr).pointer.size == options.size);
    const Result = @typeInfo(SomePtr).pointer.child;

    // If we're looking for multiple elements and `T` points to entity indices, return it directly
    if (options.size != .one and Result == Entity.Index) {
        comptime assert(@typeInfo(T) != .optional);
        comptime assert(@typeInfo(T).pointer.is_const == true);
        return Result;
    }

    // Check that we have a valid component type, and then return it
    zcs.TypeInfo.checkType(Result);
    return Result;
}

/// Indexes an entity slice view, returning an entity view.
pub fn index(View: type, es: *const Entities, slices: anytype, i: u32) View {
    var view: View = undefined;
    inline for (@typeInfo(View).@"struct".fields, slices) |field, slice| {
        if (UnwrapField(@TypeOf(slice), .{ .size = .slice }) == Entity.Index) {
            const entity_index = slice[i];
            @field(view, field.name) = entity_index.toEntity(es);
        } else {
            @field(view, field.name) = switch (@typeInfo(@TypeOf(slice))) {
                .optional => if (slice) |unwrapped| &unwrapped[i] else null,
                else => &slice[i],
            };
        }
    }
    return view;
}

/// Converts an entity view or entity slice view into a comp flag set, or `null` if any of the
/// component fields are unregistered. Size should be set to `.one` for entity views, and `.slice`
/// or the desired pointer size for entity slice views.
pub inline fn comps(T: type, options: ViewOptions) ?CompFlag.Set {
    var arch: CompFlag.Set = .{};
    inline for (@typeInfo(T).@"struct".fields) |field| {
        if (field.type != Entity and @typeInfo(field.type) != .optional) {
            const Unwrapped = zcs.view.UnwrapField(field.type, options);
            if (Unwrapped == Entity.Index) continue;
            const flag = typeId(Unwrapped).comp_flag orelse return null;
            arch.insert(flag);
        }
    }
    return arch;
}

/// Converts an entity view type into an entity slice view type.
pub fn Slice(EntityView: type) type {
    const entity_view_fields = @typeInfo(EntityView).@"struct".fields;
    comptime var fields: [entity_view_fields.len]std.builtin.Type.StructField = undefined;
    inline for (&fields, entity_view_fields, 0..) |*field, entity_view_field, i| {
        const T = entity_view_field.type;
        const S = b: {
            if (T == Entity) break :b []const Entity.Index;

            const Ptr = switch (@typeInfo(T)) {
                .optional => |optional| optional.child,
                .pointer => T,
                else => @compileError("expected pointer, found " ++ @typeName(T)),
            };
            if (@typeInfo(Ptr) != .pointer) {
                @compileError("expected pointer, found " ++ @typeName(T));
            }
            const Child = @typeInfo(Ptr).pointer.child;
            const S = if (@typeInfo(Ptr).pointer.is_const) []const Child else []Child;
            break :b if (@typeInfo(T) == .optional) ?S else S;
        };
        field.* = .{
            .name = std.fmt.comptimePrint("{}", .{i}),
            .type = S,
            .default_value_ptr = null,
            .is_comptime = false,
            .alignment = @alignOf(S),
        };
    }
    return @Type(.{ .@"struct" = .{
        .layout = .auto,
        .fields = &fields,
        .decls = &.{},
        .is_tuple = true,
    } });
}

/// Returns the list of parameter types for a function type.
pub fn params(T: type) [@typeInfo(T).@"fn".params.len]type {
    var results: [@typeInfo(T).@"fn".params.len]type = undefined;
    inline for (&results, @typeInfo(T).@"fn".params) |*result, param| {
        result.* = if (param.type) |Param| Param else {
            @compileError("cannot get type of `anytype` parameter");
        };
    }
    return results;
}

/// Generates a tuple from a list of types. Similar to `std.meta.Tuple`, but does not call
/// `@setEvalBranchQuota`.
pub fn Tuple(types: []const type) type {
    comptime var fields: [types.len]std.builtin.Type.StructField = undefined;
    inline for (&fields, types, 0..) |*field, param, i| {
        field.* = .{
            .name = std.fmt.comptimePrint("{}", .{i}),
            .type = param,
            .default_value_ptr = null,
            .is_comptime = false,
            .alignment = @alignOf(param),
        };
    }
    return @Type(.{ .@"struct" = .{
        .layout = .auto,
        .fields = &fields,
        .decls = &.{},
        .is_tuple = true,
    } });
}

/// If `T` is an optional type, returns its child. Otherwise returns it unchanged.
pub fn Unwrap(T: type) type {
    return switch (@typeInfo(T)) {
        .optional => |optional| optional.child,
        else => T,
    };
}

//! A Tracy Zone that begins/ends inside of a command buffer. Unlike most extensions, this
//! extension's `Exec` is automatically called from the default exec implementation provided that
//! Tracy is enabled.

const std = @import("std");
const zcs = @import("../root.zig");
const tracy = @import("tracy");

const assert = std.debug.assert;

const Zone = tracy.Zone;

const CmdBuf = zcs.CmdBuf;
const Entities = zcs.Entities;
const Any = zcs.Any;

const SourceLocation = tracy.SourceLocation;

loc: *const SourceLocation,

pub const BeginOptions = Zone.BeginOptions;

/// Emits a begin zone command to the command buffer if Tracy is enabled.
pub fn begin(cb: *CmdBuf, comptime opt: SourceLocation.InitOptions) @This() {
    if (tracy.enabled) {
        const loc: *const SourceLocation = .init(opt);
        cb.ext(BeginCmd, .{ .loc = loc });
        return .{ .loc = loc };
    }
}

/// Emits an end zone command to the command buffer if Tracy is enabled.
pub fn end(self: @This(), cb: *CmdBuf) void {
    if (tracy.enabled) {
        cb.ext(EndCmd, .{ .loc = self.loc });
    }
}

/// A begin zone command.
pub const BeginCmd = struct { loc: *const SourceLocation };

/// An end zone command.
pub const EndCmd = struct { loc: *const SourceLocation };

/// `Exec` provides helpers for beginning/ending Tracy zones while executing the command buffer.
/// Unlike most extensions this extension is called automatically by the default exec, provided that
/// Tracy is enabled.
///
/// By convention, `exec` only calls into the stable public interface of the types it's working
/// with. As such, documentation is sparse. You are welcome to call these methods directly, or
/// use them as reference for implementing your own command buffer iterator.
pub const Exec = struct {
    const Stack = if (tracy.enabled) b: {
        break :b std.BoundedArray(struct { zone: Zone, loc: *const SourceLocation }, 32);
    } else struct {};

    stack: Stack = .{},

    /// Executes an extension command.
    pub inline fn extImmediate(self: *@This(), payload: Any) void {
        if (tracy.enabled) {
            if (payload.as(BeginCmd)) |b| {
                const zone = Zone.beginFromPtr(b.loc);
                self.stack.append(.{ .zone = zone, .loc = b.loc }) catch @panic("OOB");
            } else if (payload.as(EndCmd)) |e| {
                const frame = self.stack.pop() orelse @panic("OOB");
                assert(frame.loc == e.loc);
                frame.zone.end();
            }
        }
    }

    pub fn finish(self: *@This()) void {
        if (tracy.enabled) {
            assert(self.stack.len == 0);
        }
    }
};

//! A pool of `Chunk`s.

const std = @import("std");
const zcs = @import("root.zig");
const tracy = @import("tracy");

const Chunk = zcs.Chunk;
const Entities = zcs.Entities;
const ChunkList = zcs.ChunkList;
const Entity = zcs.Entity;

const assert = std.debug.assert;
const math = std.math;

const Alignment = std.mem.Alignment;
const Allocator = std.mem.Allocator;

const Zone = tracy.Zone;

const ChunkPool = @This();

/// Memory reserved for chunks
buf: []u8,
/// The number of unique chunks that have ever been reserved.
reserved: u32,
/// The chunk size and alignment are both set to this value. This is done for a couple reasons:
/// 1. It makes `Entity.from` slightly cheaper
/// 2. It reduces false sharing
/// 3. We need to be aligned by more than `TypeInfo.max_align` so that the place our allocation
///    ends up doesn't change how much stuff can fit in it.
///
/// If this becomes an issue, we stop doing this by giving up 1 as long as we set the alignment
/// to at least a cache line and assert 3. However seeing as it only adds padding before the
/// first chunk, this is unlikely to ever matter.
size_align: Alignment,
/// Freed chunks, connected by the `next` field. All other fields are undefined.
free: Chunk.Index = .none,

/// The pool's capcity.
pub const Capacity = struct {
    /// The number of chunks to reserve. Supports the range `[0, math.maxInt(u32))`, max int
    /// is reserved for the none index.
    chunks: u16,
    /// The size of each chunk.
    chunk: u32,
};

/// Allocates a chunk pool.
pub fn init(gpa: Allocator, cap: Capacity) Allocator.Error!@This() {
    const zone = Zone.begin(.{ .src = @src() });
    defer zone.end();

    // The max size is reserved for invalid indices.
    assert(cap.chunks < math.maxInt(u32));
    assert(cap.chunk >= zcs.TypeInfo.max_align.toByteUnits());

    // Allocate the chunk data, aligned to the size of a chunk
    const alignment = Alignment.fromByteUnits(cap.chunk);
    const len = @as(usize, cap.chunk) * @as(usize, cap.chunks);
    const buf = (gpa.rawAlloc(
        len,
        alignment,
        @returnAddress(),
    ) orelse return error.OutOfMemory)[0..len];
    errdefer comptime unreachable;

    return .{
        .buf = buf,
        .reserved = 0,
        .size_align = alignment,
    };
}

/// Frees a chunk pool.
pub fn deinit(self: *@This(), gpa: Allocator) void {
    gpa.rawFree(self.buf, self.size_align, @returnAddress());
    self.* = undefined;
}

/// Resets the chunk pool back to its initial state.
pub fn clear(self: *@This()) void {
    self.reserved = 0;
    self.free = .none;
}

/// Reserves a chunk from the chunk pool
pub fn reserve(
    self: *@This(),
    es: *const Entities,
    list: ChunkList.Index,
) error{ZcsChunkPoolOverflow}!*Chunk {
    // Get a free chunk. Try the free list first, then fall back to bump allocation from the
    // preallocated buffer.
    const chunk = if (self.free.get(self)) |free| b: {
        // Pop the next chunk from the free list
        self.free = free.header().next;
        break :b free;
    } else b: {
        // Pop the next chunk from the preallocated buffer
        const byte_idx = @shlExact(self.reserved, @intCast(@intFromEnum(self.size_align)));
        if (byte_idx >= self.buf.len) return error.ZcsChunkPoolOverflow;
        const chunk: *Chunk = @ptrCast(&self.buf[byte_idx]);
        self.reserved = self.reserved + 1;
        break :b chunk;
    };
    errdefer comptime unreachable; // Already modified the free list!

    // Check the alignment
    assert(self.size_align.check(@intFromPtr(chunk)));

    // Initialize the chunk and return it
    const header = chunk.header();
    header.* = .{
        .comp_buf_offsets = list.get(&es.arches).comp_buf_offsets_cold,
        .list = list,
        .len = 0,
    };
    return chunk;
}

/// Gets the index of a chunk.
pub fn indexOf(self: *const @This(), chunk: *const Chunk) Chunk.Index {
    assert(@intFromPtr(chunk) >= @intFromPtr(self.buf.ptr));
    assert(@intFromPtr(chunk) < @intFromPtr(self.buf.ptr) + self.buf.len);
    const offset = @intFromPtr(chunk) - @intFromPtr(self.buf.ptr);
    assert(offset < self.buf.len);
    return @enumFromInt(@shrExact(offset, @intFromEnum(self.size_align)));
}

//! Runtime type information.

const std = @import("std");
const assert = std.debug.assert;

const zcs = @import("root.zig");
const CompFlag = zcs.CompFlag;

/// Runtime information for a type.
pub const TypeInfo = struct {
    /// The maximum allowed alignment.
    pub const max_align: std.mem.Alignment = .@"16";

    /// The component's type name.
    name: []const u8,
    /// The component type's size.
    size: usize,
    /// The component type's alignment.
    alignment: u8,
    /// If this type has been registered as a component, this holds the component flag. Component
    /// types are registered as they become needed.
    comp_flag: ?CompFlag = null,

    /// Returns the type ID for the given type.
    pub inline fn init(comptime T: type) *@This() {
        comptime checkType(T);

        return &struct {
            var info: TypeInfo = .{
                .name = @typeName(T),
                .size = @sizeOf(T),
                .alignment = @alignOf(T),
            };
        }.info;
    }

    /// Asserts at compile time that ZCS's runtime type information supports this type.
    pub fn checkType(T: type) void {
        // Storing optionals, pointers, and `Entity` directly as components would create
        // ambiguities when creating entity views. It's unfortunate that we have to disallow
        // them, but the extra typing to wrap them in the rare case that you need this ability
        // is expected to be well worth it for the convenience views provide.
        //
        // There's no reason these couldn't be allowed for `Any` in general, but we want to get
        // compile time errors when trying to use bad types, so we just rule them out for any use of
        // `Any` instead.
        //
        // `Entity.Index` and `CmdBuf` are likely indicative of a mistake and so are ruled
        // out.
        if (@typeInfo(T) == .optional or
            T == zcs.Entity or
            T == zcs.Entity.Index or
            T == zcs.CmdBuf)
        {
            @compileError("unsupported component type '" ++ @typeName(T) ++ "'; consider wrapping in struct");
        }

        comptime assert(@alignOf(T) <= max_align.toByteUnits());
    }
};

/// This pointer can be used as a unique ID identifying a component type.
pub const TypeId = *TypeInfo;

//! See `CompFlag`.

const std = @import("std");
const assert = std.debug.assert;

const zcs = @import("root.zig");
const Any = zcs.Any;
const TypeId = zcs.TypeId;
const Meta = Any.Meta;

/// The tag type for `Flag`.
const FlagInt = u6;

/// A tightly packed index for each registered component type.
pub const CompFlag = enum(FlagInt) {
    /// The maximum registered component flags.
    pub const max = std.math.maxInt(FlagInt);

    /// A set of component flags.
    pub const Set = std.enums.EnumSet(CompFlag);

    /// The list of registered components.
    var registered: std.BoundedArray(TypeId, CompFlag.max) = .{};

    /// Assigns the given ID the next flag index if it doesn't have one, and then returns its flag.
    /// Not thread safe.
    pub fn registerImmediate(id: TypeId) CompFlag {
        // Early out if we're already registered
        if (id.comp_flag) |f| {
            @branchHint(.likely);
            return f;
        }

        // Debug log that we're registering the component
        std.log.scoped(.zcs).debug("register comp: {s}", .{id.name});

        // Warn if we've registered a large number of components
        if (registered.len == CompFlag.max / 2) {
            std.log.warn(
                "{} component types registered, you're at 50% the fatal capacity!",
                .{registered.len},
            );
        }

        // Fail if we're out of component types
        if (registered.len > registered.buffer.len) {
            @panic("component type overflow");
        }

        // Pick the next sequential flag
        const flag: CompFlag = @enumFromInt(registered.len);
        id.comp_flag = flag;

        // This function is not thread safe, but reading from `registered` is, so we update the
        // registered list, and then increment the counter atomically.
        registered.buffer[registered.len] = id;
        _ = @atomicRmw(usize, &registered.len, .Add, 1, .release);

        // Return the registered flag
        return flag;
    }

    /// Gets the list of registered component types for introspection purposes. Components are
    /// registered lazily, this list will grow over time. Thread safe.
    pub fn getAll() []const TypeId {
        return registered.constSlice();
    }

    /// This function is intended to be used only in tests. Unregisters all component types.
    pub fn unregisterAll() void {
        for (registered.slice()) |id| id.comp_flag = null;
        registered.clear();
    }

    /// Returns the ID for this flag.
    pub fn getId(self: @This()) TypeId {
        return registered.get(@intFromEnum(self));
    }

    _,
};

//! A pointer to arbitrary data with a runtime known type.

const std = @import("std");
const assert = std.debug.assert;

const zcs = @import("root.zig");

const typeId = zcs.typeId;

const Entities = zcs.Entities;
const CompFlag = zcs.CompFlag;
const TypeId = zcs.TypeId;

id: TypeId,
ptr: *const anyopaque,

/// Initialize a component from a pointer to a component type.
pub fn init(T: type, ptr: *const T) @This() {
    return .{
        .id = typeId(T),
        .ptr = ptr,
    };
}

/// Returns the component as the given type if it matches its ID, or null otherwise.
pub fn as(self: @This(), T: anytype) ?*const T {
    if (self.id != typeId(T)) {
        @branchHint(.unlikely);
        return null;
    }
    return @alignCast(@ptrCast(self.ptr));
}

/// Returns the component as a constant slice of `u8`s.
pub fn constSlice(self: @This()) []const u8 {
    return self.bytes()[0..self.id.size];
}

/// Similar to `constSlice`, but returns the data as a many item pointer.
pub fn bytes(self: @This()) [*]const u8 {
    return @ptrCast(self.ptr);
}

/// Pointer stability assertions for builds with runtime safety enabled.
const std = @import("std");
const assert = std.debug.assert;

const PointerLock = @This();

const enabled = std.debug.runtime_safety;

/// The current pointer generation.
pub const Generation = struct {
    n: if (enabled) u64 else u0 = 0,

    /// Increments the pointer generation.
    pub inline fn increment(self: *@This()) void {
        if (enabled) {
            self.n +%= 1;
        }
    }

    /// Returns a pointer lock with the current generation.
    pub inline fn lock(self: @This()) PointerLock {
        return .{ .generation = self };
    }
};

/// The pointer generation from when this lock was created.
generation: Generation,

/// Asserts that pointers have not been invalidated since this lock was created.
pub fn check(self: @This(), generation: Generation) void {
    if (self.generation.n != generation.n) {
        @panic("pointers invalidated");
    }
}

//! An entity component system.
//!
//! See `Entities` for entity storage and iteration, `Entity` for entity creation and modification,
//! and `CmdBuf` for queuing commands during iteration or from multiple threads.

const std = @import("std");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const slot_map = @import("slot_map");

pub const Entities = @import("Entities.zig");
pub const Entity = @import("entity.zig").Entity;
pub const Any = @import("Any.zig");
pub const CompFlag = @import("comp_flag.zig").CompFlag;
pub const CmdBuf = @import("CmdBuf.zig");
pub const CmdPool = @import("CmdPool.zig");
pub const TypeInfo = @import("type_id.zig").TypeInfo;
pub const TypeId = @import("type_id.zig").TypeId;
pub const PointerLock = @import("PointerLock.zig");
pub const Chunk = @import("chunk.zig").Chunk;
pub const ChunkList = @import("ChunkList.zig");
pub const ChunkPool = @import("ChunkPool.zig");
pub const Arches = @import("Arches.zig");
pub const view = @import("view.zig");
pub const ext = @import("ext.zig");
pub const meta = @import("meta.zig");

/// Returns the component ID for the given type.
pub const typeId = TypeInfo.init;

/// A handle table that associates persistent entity keys with values that point to their storage.
pub const HandleTab = slot_map.SlotMap(Entity.Location, .{});

test {
    std.testing.refAllDecls(@This());
}

//! Metaprogramming helpers. See also `view`.

const std = @import("std");

/// Returns true if the given value is comptime known, false otherwise.
pub inline fn isComptimeKnown(value: anytype) bool {
    return @typeInfo(@TypeOf(.{value})).@"struct".fields[0].is_comptime;
}

test isComptimeKnown {
    try std.testing.expect(isComptimeKnown(123));
    const foo = 456;
    try std.testing.expect(isComptimeKnown(foo));
    var bar: u8 = 123;
    bar += 1;
    try std.testing.expect(!isComptimeKnown(bar));
}

//! Optional extensions not part of the core ECS.

const std = @import("std");

pub const Node = @import("ext/Node.zig");
pub const Transform2D = @import("ext/Transform2D.zig");
pub const ZoneCmd = @import("ext/ZoneCmd.zig");
pub const geom = @import("geom");

test {
    std.testing.refAllDecls(@This());
}

/// See build.zig.
pub fn main() void {}

inline fn nextAny(self: *@This(), id: TypeId) *const anyopaque { }

This code segment functions to: Adjust the current buffer position to meet alignment requirements and retrieve a specific-sized data block from a contiguous storage area.

```zig
[TASK_BEGIN]
        pub inline fn nextAny(self: *@This(), id: TypeId) *const anyopaque {
            const alignment = id.getAlignment();
            const size = id.getSize();
            const aligned = std.mem.alignForward(usize, self.comp_bytes_index, alignment);
            const ptr = @ptrCast(self.cb.data.items.ptr + aligned);
            self.comp_bytes_index = aligned + size;
            return ptr;
        }
[TASK_END]
```