High-level wrapper around Flecs, a powerful ECS (Entity Component System) library, written in Zig language
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

102 lines
3.6 KiB

const std = @import("std");
const Allocator = std.mem.Allocator;
const c = @import("./c.zig");
pub var is_setup = false;
pub var allocator: Allocator = undefined;
pub fn setup(allocator_: Allocator) void {
if (is_setup) std.debug.panic("setup must only be called once", .{});
is_setup = true;
allocator = allocator_;
c.ecs_os_set_api_defaults();
c.ecs_os_api.log_ = flecsLog;
c.ecs_os_api.abort_ = flecsAbort;
c.ecs_os_api.malloc_ = flecsMalloc;
c.ecs_os_api.realloc_ = flecsRealloc;
c.ecs_os_api.calloc_ = flecsCalloc;
c.ecs_os_api.free_ = flecsFree;
_ = c.ecs_log_set_level(-2);
}
// From the looks of it, Flecs does not log errors to stderr
// by default, so let's just use our own logging function.
fn flecsLog(level: i32, file: [*c]const u8, line: i32, msg: [*c]const u8) callconv(.C) void {
const log = std.log.scoped(.Flecs);
const fmt = "{s} at {s}:{d}\n{s}";
switch (level) {
-4 => log.err(fmt, .{ "Fatal", file, line, msg }),
-3 => log.err(fmt, .{ "Error", file, line, msg }),
-2 => log.warn(fmt, .{ "Warning", file, line, msg }),
-1 => std.debug.panic("Attempting to use unused log level -1", .{}),
0 => log.debug(fmt, .{ "Tracing", file, line, msg }),
else => log.debug(fmt, .{ "Debug tracing", file, line, msg }),
}
}
fn flecsAbort() callconv(.C) void {
std.debug.dumpCurrentStackTrace(@returnAddress());
@breakpoint();
std.posix.exit(1);
}
fn flecsMalloc(size: i32) callconv(.C) ?*anyopaque {
if (size == 0) return null;
return allocLengthEncodedSlice(size, null).ptr;
}
fn flecsRealloc(ptr: ?*anyopaque, size: i32) callconv(.C) ?*anyopaque {
if (size == 0) {
flecsFree(ptr);
return null;
} else {
const old = if (ptr) |p| sliceFromPtr(p) else null;
return allocLengthEncodedSlice(size, old).ptr;
}
}
fn flecsCalloc(size: i32) callconv(.C) ?*anyopaque {
if (size == 0) return null;
const slice = allocLengthEncodedSlice(size, null);
@memset(slice, 0);
return slice.ptr;
}
fn flecsFree(ptr: ?*anyopaque) callconv(.C) void {
if (ptr) |p| {
const slice = sliceFromPtr(p);
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: ?[]align(@sizeOf(i32)) u8) []u8 {
const slice_len = @sizeOf(i32) + @as(usize, @intCast(size));
const slice = if (old_slice) |old|
allocator.realloc(old, slice_len) catch @panic("OOM")
else
allocator.allocWithOptions(u8, slice_len, @sizeOf(i32), null) catch @panic("OOM");
@as([*]i32, @ptrCast(slice.ptr))[0] = 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) []align(@sizeOf(i32)) u8 {
const slice_ptr = @as([*]align(@sizeOf(i32)) u8, @alignCast(@ptrCast(ptr))) - @sizeOf(i32);
const slice_len = @as([*]i32, @ptrCast(slice_ptr))[0];
return slice_ptr[0..(@sizeOf(i32) + @as(usize, @intCast(slice_len)))];
}