diff --git a/src/entity.zig b/src/entity.zig index f190e87..af51c9e 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -1,7 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const c = @import("./c.zig"); +const flecs = @import("./main.zig"); +const c = flecs.c; const err = @import("./error.zig"); const util = @import("./util.zig"); @@ -119,8 +120,8 @@ pub fn Entity(comptime ctx: anytype) type { for (path.parts[0..(path.parts.len - 1)]) |part| switch (part) { .name => |n| { // TODO: Use an allocator that's well-fitted for super short-lived allocations. - const nameZ = try world.allocator.dupeZ(u8, n); - defer world.allocator.free(nameZ); + const nameZ = try flecs.allocator.dupeZ(u8, n); + defer flecs.allocator.free(nameZ); const parent = scope orelse c.ecs_get_scope(world.raw); const found = c.ecs_lookup_child(world.raw, parent, nameZ.ptr); if (found == 0) { @@ -145,10 +146,10 @@ pub fn Entity(comptime ctx: anytype) type { defer _ = if (previous) |s| c.ecs_set_scope(world.raw, s); // TODO: Use an allocator that's well-fitted for super short-lived allocations. - const nameZ = if (name) |n| try world.allocator.dupeZ(u8, n) else null; - defer if (nameZ) |n| world.allocator.free(n); - const symbolZ = if (config.symbol) |s| try world.allocator.dupeZ(u8, s) else null; - defer if (symbolZ) |s| world.allocator.free(s); + const nameZ = if (name) |n| try flecs.allocator.dupeZ(u8, n) else null; + defer if (nameZ) |n| flecs.allocator.free(n); + const symbolZ = if (config.symbol) |s| try flecs.allocator.dupeZ(u8, s) else null; + defer if (symbolZ) |s| flecs.allocator.free(s); var desc = std.mem.zeroInit(c.ecs_entity_desc_t, .{ .sep = "".ptr, // Disable tokenization. diff --git a/src/iter.zig b/src/iter.zig index 83a562c..3dcd80a 100644 --- a/src/iter.zig +++ b/src/iter.zig @@ -1,4 +1,5 @@ -const c = @import("./c.zig"); +const flecs = @import("./main.zig"); +const c = flecs.c; const Entity = @import("./entity.zig").Entity; const Id = @import("./id.zig").Id; @@ -17,14 +18,14 @@ pub fn Iter(comptime ctx: anytype) type { } pub fn fromRawValue(world: *World(ctx), value: c.ecs_iter_t) !Self { - var raw = try world.allocator.create(c.ecs_iter_t); + var raw = try flecs.allocator.create(c.ecs_iter_t); raw.* = value; return .{ .world = world, .raw = raw, .owned = true }; } pub fn deinit(self: Self) void { if (self.isValid()) c.ecs_iter_fini(self.raw); - if (self.owned) self.world.allocator.destroy(self.raw); + if (self.owned) flecs.allocator.destroy(self.raw); } pub fn isValid(self: Self) bool { diff --git a/src/main.zig b/src/main.zig index d393ae1..658f279 100644 --- a/src/main.zig +++ b/src/main.zig @@ -1,3 +1,6 @@ +const std = @import("std"); +const Allocator = std.mem.Allocator; + pub usingnamespace @import("./component.zig"); pub usingnamespace @import("./entity.zig"); pub usingnamespace @import("./id.zig"); @@ -28,7 +31,72 @@ pub fn Lookup(comptime ctx: anytype, comptime T: type) type { }; } +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 { + var 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 { - const std = @import("std"); std.testing.refAllDecls(@This()); } diff --git a/src/world.zig b/src/world.zig index 43b7f2a..2e50de4 100644 --- a/src/world.zig +++ b/src/world.zig @@ -1,7 +1,8 @@ const std = @import("std"); const Allocator = std.mem.Allocator; -const c = @import("./c.zig"); +const flecs = @import("./main.zig"); +const c = flecs.c; const err = @import("./error.zig"); const util = @import("./util.zig"); @@ -13,34 +14,33 @@ const Iter = @import("./iter.zig").Iter; pub fn World(comptime ctx: anytype) type { return struct { raw: *c.ecs_world_t, - allocator: Allocator, const Self = @This(); - pub fn init(alloc: Allocator) !*Self { - var result = try alloc.create(Self); + pub fn init() !*Self { + std.debug.assert(flecs.is_initialized); + var result = try flecs.allocator.create(Self); result.raw = c.ecs_init().?; - result.allocator = alloc; return result; } - pub fn initWithArgs(alloc: Allocator, args: [][*:0]const u8) !*Self { - var result = try alloc.create(Self); + pub fn initWithArgs(args: [][*:0]const u8) !*Self { + std.debug.assert(flecs.is_initialized); + var result = try flecs.allocator.create(Self); result.raw = c.ecs_init_w_args(args.len, args.ptr).?; - result.allocator = alloc; return result; } - pub fn initMinimal(alloc: Allocator) !*Self { - var result = try alloc.create(Self); + pub fn initMinimal() !*Self { + std.debug.assert(flecs.is_initialized); + var result = try flecs.allocator.create(Self); result.raw = c.ecs_mini().?; - result.allocator = alloc; return result; } pub fn deinit(self: *Self) void { _ = c.ecs_fini(self.raw); - self.allocator.destroy(self); + flecs.allocator.destroy(self); } pub fn progress(self: *Self, delta_time: f32) bool { @@ -115,7 +115,7 @@ pub fn World(comptime ctx: anytype) type { func: SystemCallback, pub fn init(world: *Self, callback: SystemCallback) !*SystemCallbackContext { - var result = try world.allocator.create(SystemCallbackContext); + var result = try flecs.allocator.create(SystemCallbackContext); result.world = world; result.func = callback; return result; @@ -123,7 +123,7 @@ pub fn World(comptime ctx: anytype) type { fn free(context: ?*anyopaque) callconv(.C) void { const self: *SystemCallbackContext = @alignCast(@ptrCast(context)); - self.world.allocator.destroy(self); + flecs.allocator.destroy(self); } // FIXME: Dependency loop. diff --git a/test/entity.zig b/test/entity.zig index d13a27d..c1f384b 100644 --- a/test/entity.zig +++ b/test/entity.zig @@ -2,7 +2,6 @@ // https://github.com/SanderMertens/flecs/blob/master/test/api/src/Entity.c const std = @import("std"); -const alloc = std.testing.allocator; const expect = std.testing.expect; const expectFmt = std.testing.expectFmt; const expectEqual = std.testing.expectEqual; @@ -19,7 +18,8 @@ const Path = context.Path; const World = context.World; test "Entity_init_id" { - var world = try World.initMinimal(alloc); + flecs.init(std.testing.allocator); + var world = try World.initMinimal(); defer world.deinit(); const e = try world.entity(.{}, .{}); @@ -28,29 +28,31 @@ test "Entity_init_id" { } test "Entity_init_id_name" { - var world = try World.initMinimal(alloc); + flecs.init(std.testing.allocator); + var world = try World.initMinimal(); defer world.deinit(); const e = try world.entity(.{ .name = "foo" }, .{}); // try expect(e.raw != 0); -- Not necessary, world.entity() returns error if result would be 0. try expectEqualStrings("foo", e.getName().?); - const path2 = try e.getPath(alloc); + const path2 = try e.getPath(flecs.allocator); defer path2.deinit(); try expectFmt("foo", "{}", .{path2}); } test "Entity_init_id_path" { - var world = try World.initMinimal(alloc); + flecs.init(std.testing.allocator); + var world = try World.initMinimal(); defer world.deinit(); - const p = try Path.fromString("parent.child", null, alloc); + const p = try Path.fromString("parent.child", null, flecs.allocator); defer p.deinit(); const e = try world.entity(.{ .path = p }, .{}); // try expect(e.raw != 0); -- Not necessary, world.entity() returns error if result would be 0. try expectEqualStrings("child", e.getName().?); - const path = try e.getPath(alloc); + const path = try e.getPath(flecs.allocator); defer path.deinit(); try expectFmt("parent.child", "{}", .{path}); } diff --git a/test/world.zig b/test/world.zig index 57f10b8..551f7a3 100644 --- a/test/world.zig +++ b/test/world.zig @@ -31,7 +31,8 @@ fn move(it: Iter) void { } test "World_progress_w_0" { - var world = try World.init(std.testing.allocator); + flecs.init(std.testing.allocator); + var world = try World.init(); defer world.deinit(); _ = try world.component(Position); @@ -67,7 +68,8 @@ test "World_progress_w_0" { } test "World_progress_w_t" { - var world = try World.init(std.testing.allocator); + flecs.init(std.testing.allocator); + var world = try World.init(); defer world.deinit(); _ = try world.component(Position);