const std = @import("std"); const Allocator = std.mem.Allocator; const meta = @import("./meta.zig"); pub const c = @import("./c.zig"); pub usingnamespace @import("./error.zig"); pub usingnamespace @import("./entity.zig"); pub usingnamespace @import("./id.zig"); pub usingnamespace @import("./iter.zig"); pub usingnamespace @import("./pair.zig"); pub usingnamespace @import("./world.zig"); pub const Path = @import("./path.zig"); pub var is_initialized = false; pub var allocator: Allocator = undefined; /// Ensures that some global settings are set up to interface with Flecs. /// Must be called before creating a `World`. Subsequent calls are a no-op. pub fn init(alloc: Allocator) void { if (is_initialized) { std.debug.assert(allocator.ptr == alloc.ptr); return; } is_initialized = true; allocator = alloc; c.ecs_os_api.malloc_ = flecsMalloc; c.ecs_os_api.realloc_ = flecsRealloc; c.ecs_os_api.calloc_ = flecsCalloc; c.ecs_os_api.free_ = flecsFree; } pub fn Context(comptime ctx: anytype) type { return struct { pub const Entity = @import("./entity.zig").Entity(ctx); pub const Id = @import("./id.zig").Id(ctx); pub const Iter = @import("./iter.zig").Iter(ctx); pub const Pair = @import("./pair.zig").Pair(ctx); pub const World = @import("./world.zig").World(ctx); /// Returns a pointer unique to this context and the provided type /// that holds an entity ID. Useful for associating types to entities. pub fn lookup(comptime T: type) *c.ecs_entity_t { _ = T; // Only necessary to create a unique type. return &(struct { pub var id: c.ecs_entity_t = 0; }).id; } /// Converts the specified value to an `ecs_entity_t`. /// /// If `null` is passed to this function, either directly or through an /// optional type of one of the supported types, `0` is returned. It's up to /// the caller to do any necessary testing for `0` values, and whether the /// returned entity is valid or alive. /// /// The following can be converted: /// - `Entity` and `ecs_entity_t` - Self-explanatory. /// - `type` - Looks up the entity associated with that type. pub fn anyToEntity(value: anytype) c.ecs_entity_t { return switch (@TypeOf(value)) { @TypeOf(null) => 0, c.ecs_entity_t => value, Entity => value.raw, ?Entity => if (value) |v| v.raw else 0, type => lookup(value).*, else => @compileError("Value of type " ++ @typeName(@TypeOf(value)) ++ " can't be converted to Entity"), }; } /// Converts the specified value to an `ecs_id_t`. /// /// If `null` is passed to this function, either directly or through an /// optional type of one of the supported types, `0` is returned. It's up to /// the caller to do any necessary testing for `0` values, and whether the /// returned id is valid. /// /// The following can be converted: /// - `Id` and `ecs_id_it` - Self-explanatory. /// - `Entity` and `ecs_entity_t` - Self-explanatory. /// - `Pair` - Converts to the equivalent `Id`. /// - `.{ relation, target }` - A `Pair`, converted using `anyToEntity()`. /// - `type` - Looks up the entity associated with that type. pub fn anyToId(value: anytype) c.ecs_id_t { const T = @TypeOf(value); if (comptime meta.isTuple(T)) { if (@typeInfo(T).Struct.fields.len != 2) @compileError("Value of type " ++ @typeName(T) ++ " must be a tuple with 2 elements, to be a Pair"); const relation = anyToEntity(value[0]); const target = anyToEntity(value[1]); return c.ecs_make_pair(relation, target); } return switch (T) { @TypeOf(null) => 0, c.ecs_id_t => value, Id => value.raw, ?Id => if (value) |v| v.raw else 0, Pair => value.raw, ?Pair => if (value) |v| v.raw else 0, // Technically same type as `ecs_id_it`. // c.ecs_entity_t => value, Entity => value.raw, ?Entity => if (value) |v| v.raw else 0, type => lookup(value).*, else => @compileError("Value of type " ++ @typeName(T) ++ " can't be converted to Id"), }; } }; } fn flecsMalloc(size: i32) callconv(.C) ?*anyopaque { return allocLengthEncodedSlice(size, null).ptr; } fn flecsRealloc(ptr: ?*anyopaque, size: i32) callconv(.C) ?*anyopaque { return allocLengthEncodedSlice(size, sliceFromPtr(ptr.?)).ptr; } fn flecsCalloc(size: i32) callconv(.C) ?*anyopaque { const slice = allocLengthEncodedSlice(size, null); @memset(slice, 0); return slice.ptr; } fn flecsFree(ptr: ?*anyopaque) callconv(.C) void { const slice = sliceFromPtr(ptr.?); allocator.free(slice); } /// Reserves an additional `@sizeOf(i32)` bytes, which is used to store the /// length so we can use a simple pointer offset to "encode" the full slice /// information (including length) into just a single pointer. /// /// Optionally allows passing a slice to be reallocated into this new slice. /// The `old_slice` must be the full slice as returned by `sliceFromPtr(...)`. /// /// Returns the pointer from the offset where the actual data is stored. /// This allows manipulating the contents, such as zeroing it out. fn allocLengthEncodedSlice(size: i32, old_slice: ?[]u8) []u8 { const slice_len = @as(usize, @intCast(size)) + @sizeOf(i32); const slice = if (old_slice) |old| allocator.realloc(old, slice_len) catch @panic("OOM") else allocator.allocWithOptions(u8, slice_len, @alignOf(i32), null) catch @panic("OOM"); @as(*i32, @alignCast(@ptrCast(slice.ptr))).* = size; return slice[@sizeOf(i32)..]; } /// Recovers the original slice that was allocated by `allocSlice` to get the /// specified pointer. Returns the full slice including the "encoded" length. fn sliceFromPtr(ptr: *anyopaque) []u8 { const slice_ptr = @as([*]align(@alignOf(i32)) u8, @alignCast(@ptrCast(ptr))) - @sizeOf(i32); const slice_len: usize = @intCast(@as(*i32, @ptrCast(slice_ptr)).*); return slice_ptr[0..(slice_len + @sizeOf(i32))]; } test { std.testing.refAllDecls(@This()); _ = @import("./test/main.zig"); }