|
|
|
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);
|
|
|
|
|
|
|
|
/// Looks up an entity ID unique to this `Context` for the provided
|
|
|
|
/// type that has been registered previously, typically done by
|
|
|
|
/// calling functions such as `World.component(...)`.
|
|
|
|
pub fn lookup(comptime T: type) c.ecs_entity_t {
|
|
|
|
return lookupMut(T).*;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a pointer unique to this `Context` and the provided type
|
|
|
|
/// that holds an entity ID. Useful for associating types to entities.
|
|
|
|
pub fn lookupMut(comptime T: type) *c.ecs_entity_t {
|
|
|
|
_ = T; // Only necessary to create a unique type.
|
|
|
|
const EntityHolder = struct {
|
|
|
|
pub var id: c.ecs_entity_t = 0;
|
|
|
|
};
|
|
|
|
return &EntityHolder.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");
|
|
|
|
}
|