Implement Flecs' OS API alloc functions

- Add init(alloc) function to initialize Flecs interop
- Set Flecs' OS API functions to use given allocator
- Move World.allocator to a global allocator
main
copygirl 1 year ago
parent cf60a43e10
commit a372768702
  1. 15
      src/entity.zig
  2. 7
      src/iter.zig
  3. 70
      src/main.zig
  4. 28
      src/world.zig
  5. 16
      test/entity.zig
  6. 6
      test/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");
@ -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.

@ -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 {

@ -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());
}

@ -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.

@ -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});
}

@ -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);

Loading…
Cancel
Save