diff --git a/src/main.zig b/src/main.zig index 80b55ab..ac065ab 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const Allocator = std.mem.Allocator; +const os_api = @import("./os_api.zig"); // Meant for internal use, but exposed since flecs-zig-ble // doesn't wrap nearly enough of Flecs' available features. @@ -19,70 +19,14 @@ pub usingnamespace @import("./iter.zig"); pub usingnamespace @import("./pair.zig"); pub usingnamespace @import("./world.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; -} - -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))]; +/// Must be called before creating a `World`. Subsequent calls are a no-op, +/// but the same allocator must be used. +pub fn init(allocator: std.mem.Allocator) void { + if (!os_api.is_setup) + os_api.setup(allocator) + else if (allocator.ptr != os_api.allocator.ptr) + std.debug.panic("init called multiple times, but allocator does not match", .{}); } test { diff --git a/src/os_api.zig b/src/os_api.zig new file mode 100644 index 0000000..59ee330 --- /dev/null +++ b/src/os_api.zig @@ -0,0 +1,78 @@ +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(); + var os_api = c.ecs_os_api; + os_api.malloc_ = flecsMalloc; + os_api.realloc_ = flecsRealloc; + os_api.calloc_ = flecsCalloc; + os_api.free_ = flecsFree; + c.ecs_os_set_api(&os_api); + _ = c.ecs_log_set_level(-1); // No tracing. +} + +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)))]; +} diff --git a/src/test/flecs/entity.zig b/src/test/flecs/entity.zig index cd18b7b..af8765c 100644 --- a/src/test/flecs/entity.zig +++ b/src/test/flecs/entity.zig @@ -36,7 +36,7 @@ test "Entity_init_id_name" { const e = try world.entity(.{ .name = "foo" }, .{}); try expect.equal("foo", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("foo", "{}", .{path}); } @@ -51,7 +51,7 @@ test "Entity_init_id_path" { const e = try world.entity(.{ .path = e_path }, .{}); try expect.equal("child", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child", "{}", .{path}); } @@ -116,7 +116,7 @@ test "Entity_init_id_name_w_scope" { try expect.true(e.has(.{ ChildOf, scope })); try expect.equal("child", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child", "{}", .{path}); } @@ -138,7 +138,7 @@ test "Entity_init_id_path_w_scope" { try expect.equal("grandchild", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child.grandchild", "{}", .{path}); } @@ -154,13 +154,13 @@ test "Entity_init_id_fullpath_w_scope" { _ = world.setScope(scope); try expect.equal(scope, world.scope()); - const p = try Path.fromString("::parent.child.grandchild", .{ .root_sep = "::", .sep = "." }, flecszigble.allocator); + const p = try Path.fromString("::parent.child.grandchild", .{ .root_sep = "::", .sep = "." }, alloc); defer p.deinit(); const e = try world.entity(.{ .path = p }, .{}); try expect.equal("grandchild", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child.grandchild", "{}", .{path}); } @@ -176,7 +176,7 @@ test "Entity_init_id_fullpath_w_scope_existing" { _ = world.setScope(scope); try expect.equal(scope, world.scope()); - const p = try Path.fromString("::parent.child.grandchild", .{ .root_sep = "::", .sep = "." }, flecszigble.allocator); + const p = try Path.fromString("::parent.child.grandchild", .{ .root_sep = "::", .sep = "." }, alloc); defer p.deinit(); const e = try world.entity(.{ .path = p }, .{}); @@ -185,7 +185,7 @@ test "Entity_init_id_fullpath_w_scope_existing" { try expect.equal("grandchild", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child.grandchild", "{}", .{path}); } @@ -203,7 +203,7 @@ test "Entity_init_id_name_1_comp" { try expect.true(e.has(TagA)); try expect.equal("foo", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("foo", "{}", .{path}); } @@ -223,7 +223,7 @@ test "Entity_init_id_name_2_comp" { try expect.true(e.has(TagA)); try expect.equal("foo", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("foo", "{}", .{path}); } @@ -249,7 +249,7 @@ test "Entity_init_id_name_2_comp_w_scope" { try expect.true(e.has(TagA)); try expect.equal("child", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child", "{}", .{path}); } @@ -292,12 +292,12 @@ test "Entity_init_id_path_w_sep" { var world = try World.initMinimal(); defer world.deinit(); - const p = try Path.fromString("parent::child", .{ .root_sep = null, .sep = "::" }, flecszigble.allocator); + const p = try Path.fromString("parent::child", .{ .root_sep = null, .sep = "::" }, alloc); defer p.deinit(); const e = try world.entity(.{ .path = p }, .{}); try expect.equal("child", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child", "{}", .{path}); } @@ -343,7 +343,7 @@ test "Entity_find_id_name_w_scope" { const e = try world.entity(.{ .name = "child" }, .{}); try expect.equal("child", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child", "{}", .{path}); @@ -361,7 +361,7 @@ test "Entity_find_id_path" { const e = try world.entity(.{ .path = e_path }, .{}); try expect.equal("child", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child", "{}", .{path}); @@ -385,7 +385,7 @@ test "Entity_find_id_path_w_scope" { const e = try world.entity(.{ .path = e_path }, .{}); try expect.equal("grandchild", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child.grandchild", "{}", .{path}); @@ -419,7 +419,7 @@ test "Entity_find_id_name_match_w_scope" { const e = try world.entity(.{ .name = "child" }, .{}); try expect.equal("child", e.name()); - const path = try e.path(flecszigble.allocator); + const path = try e.path(alloc); defer path.deinit(); try expect.fmt("parent.child", "{}", .{path}); @@ -612,7 +612,7 @@ test "Entity_init_w_scope_name" { const child = try world.entity(.{ .name = "foo" }, .{}); try expect.equal("foo", child.name()); - const path = try child.path(flecszigble.allocator); + const path = try child.path(alloc); defer path.deinit(); try expect.fmt("parent.foo.foo", "{}", .{path}); } diff --git a/src/world.zig b/src/world.zig index 6ee7ba2..d48c6b9 100644 --- a/src/world.zig +++ b/src/world.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const Allocator = std.mem.Allocator; +const os_api = @import("./os_api.zig"); const flecszigble = @import("./main.zig"); const c = @import("./c.zig"); @@ -21,24 +21,24 @@ pub fn World(comptime ctx: anytype) type { raw: *c.ecs_world_t, pub fn init() !*Self { - std.debug.assert(flecszigble.is_initialized); - var result = try flecszigble.allocator.create(Self); + std.debug.assert(os_api.is_setup); + var result = try os_api.allocator.create(Self); result.raw = c.ecs_init().?; try Context.registerFlecsLookups(result); return result; } pub fn initWithArgs(args: [][*:0]const u8) !*Self { - std.debug.assert(flecszigble.is_initialized); - var result = try flecszigble.allocator.create(Self); + std.debug.assert(os_api.is_setup); + var result = try os_api.allocator.create(Self); result.raw = c.ecs_init_w_args(args.len, args.ptr).?; try Context.registerFlecsLookups(result); return result; } pub fn initMinimal() !*Self { - std.debug.assert(flecszigble.is_initialized); - var result = try flecszigble.allocator.create(Self); + std.debug.assert(os_api.is_setup); + var result = try os_api.allocator.create(Self); result.raw = c.ecs_mini().?; try Context.registerFlecsCoreLookups(result); return result; @@ -46,7 +46,7 @@ pub fn World(comptime ctx: anytype) type { pub fn deinit(self: *Self) void { _ = c.ecs_fini(self.raw); - flecszigble.allocator.destroy(self); + os_api.allocator.destroy(self); } pub fn enableRest(self: *Self, port: u16) !void { @@ -212,7 +212,7 @@ pub fn World(comptime ctx: anytype) type { func: SystemCallback, pub fn init(world: *Self, callback: SystemCallback) !*SystemCallbackContext { - var result = try flecszigble.allocator.create(SystemCallbackContext); + var result = try os_api.allocator.create(SystemCallbackContext); result.world = world; result.func = callback; return result; @@ -220,7 +220,7 @@ pub fn World(comptime ctx: anytype) type { fn free(context: ?*anyopaque) callconv(.C) void { const self: *SystemCallbackContext = @alignCast(@ptrCast(context)); - flecszigble.allocator.destroy(self); + os_api.allocator.destroy(self); } // FIXME: Dependency loop.