Improved error handling

- Add FlecsError error enum
- Add function to get last Flecs error
- Return FlecsError where applicable
- Some functions don't return "Self" anymore
- Hacky workaround for dependency loop bug
main
copygirl 1 year ago
parent e3627fda0c
commit 4b7be3e1f7
  1. 46
      src/entity.zig
  2. 101
      src/error.zig
  3. 72
      src/world.zig
  4. 24
      test/world.zig

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const c = @import("./c.zig"); const c = @import("./c.zig");
const err = @import("./error.zig");
const util = @import("./util.zig"); const util = @import("./util.zig");
const Id = @import("./id.zig").Id; const Id = @import("./id.zig").Id;
@ -33,7 +34,7 @@ pub const EntityConfig = struct {
/// ///
/// Each `Entity` can have a number of `Id`s added to it, which could be /// Each `Entity` can have a number of `Id`s added to it, which could be
/// components (such as `Position`), zero-size tags (such as `Disabled`) or /// components (such as `Position`), zero-size tags (such as `Disabled`) or
/// relationship `Pair`s (such as `(ChildOf, parent)`). /// relationship `Pair`s (such as `.{ ChildOf, parent }`).
/// ///
/// Entities can be created using `World.new()` and deleted with `delete()`. /// Entities can be created using `World.new()` and deleted with `delete()`.
/// When an entity is deleted it is no longer considered "alive". A world can /// When an entity is deleted it is no longer considered "alive". A world can
@ -48,13 +49,32 @@ pub fn Entity(comptime ctx: anytype) type {
const Self = @This(); const Self = @This();
/// Returns an `Entity` for the specified world and raw entity id.
///
/// No safety checks are done, so if you pass an invalid id, an
/// invalid `Entity` might be returned that could cause panics if
/// passed to other API functions without ensuring their validity.
pub fn fromRaw(world: *World(ctx), raw: c.ecs_entity_t) Self { pub fn fromRaw(world: *World(ctx), raw: c.ecs_entity_t) Self {
return .{ .world = world, .raw = raw }; return .{ .world = world, .raw = raw };
} }
/// Creates a new `Entity` in the specified world. /// Creates a new `Entity` in the specified world.
/// See `World.entity(...)` for more information. /// May return an existing entity if one with the given name exists.
pub fn new(world: *World(ctx), config: EntityConfig, add: anytype) Self { /// May return an error if the entity creation failed.
///
/// The `config` argument may be used to specify the `name` and
/// `symbol` used to create the entity with. If an entity with the
/// given name already exists, that one is modified instead.
/// Does not take ownership of any of the provided values.
///
/// The `add` argument is a tuple that specifies the `Id`s the entity
/// is created with initially. For example:
/// `.{ Position, Velocity, .{ ChildOf, parent } }`
///
/// When you add components, they are default-initialized. To set
/// their values you'll need to use one of the setter functions on the
/// `Entity` returned from this function.
pub fn new(world: *World(ctx), config: EntityConfig, add: anytype) !Self {
var desc = std.mem.zeroes(c.ecs_entity_desc_t); var desc = std.mem.zeroes(c.ecs_entity_desc_t);
desc.name = if (config.name) |n| n.ptr else null; desc.name = if (config.name) |n| n.ptr else null;
desc.symbol = if (config.symbol) |s| s.ptr else null; desc.symbol = if (config.symbol) |s| s.ptr else null;
@ -69,19 +89,20 @@ pub fn Entity(comptime ctx: anytype) type {
desc.add[i] = util.anyToId(ctx, a); desc.add[i] = util.anyToId(ctx, a);
const result = c.ecs_entity_init(world.raw, &desc); const result = c.ecs_entity_init(world.raw, &desc);
if (result == 0) return err.getLastErrorOrUnknown();
return Self.fromRaw(world, result); return Self.fromRaw(world, result);
} }
/// Ensures this `Entity` is valid, returning an error otherwise. /// Ensures this `Entity` is valid, returning an error otherwise.
/// Entities that are valid can be used with API functions. /// Invalid entities may cause panics when used with API functions.
/// ///
/// An `Entity` is valid if .. /// An `Entity` is valid if ..
/// - .. it is not `0`. /// - .. it is not `0`.
/// - .. it does not have invalid bits set. /// - .. it does not have dead bits set.
/// - .. it does not have any `Id` flags set. /// - .. it does not have any `Id` flags set.
/// - .. no entity with this id is alive, but this entity's generation is `0`. /// - .. no entity with this id is alive, but this entity's generation is `0`.
/// - .. an entity with this exact id and generation is alive in the world. /// - .. an entity with this exact id and generation is alive in the world.
pub fn ensureValid(self: Self) !Self { pub fn ensureValid(self: Self) !void {
// `0` is not a valid entity id. // `0` is not a valid entity id.
if (self.raw == 0) return EntityError.IsNone; if (self.raw == 0) return EntityError.IsNone;
// Entity ids should not contain data in dead zone bits. // Entity ids should not contain data in dead zone bits.
@ -96,17 +117,11 @@ pub fn Entity(comptime ctx: anytype) type {
const entity = c.ecs_get_alive(self.world.raw, self.raw); const entity = c.ecs_get_alive(self.world.raw, self.raw);
if (c.ECS_GENERATION(entity) != c.ECS_GENERATION(self.raw)) if (c.ECS_GENERATION(entity) != c.ECS_GENERATION(self.raw))
return EntityError.GenMismatch; return EntityError.GenMismatch;
return self;
} }
/// Ensures this `Entity` is alive in this world, returning an error if not. /// Ensures this `Entity` is alive in this world, returning an error if not.
pub fn ensureAlive(self: Self) !Self { pub fn ensureAlive(self: Self) !void {
return if ((try ensureValid(self)).isAlive()) self else EntityError.IsNotAlive; if (!c.ecs_is_alive(self.world.raw, self.raw)) return EntityError.IsNotAlive;
}
pub fn isAlive(self: Self) bool {
return c.ecs_is_alive(self.world.raw, self.raw);
} }
pub fn asId(self: Self) Id(ctx) { pub fn asId(self: Self) Id(ctx) {
@ -131,10 +146,9 @@ pub fn Entity(comptime ctx: anytype) type {
return @alignCast(@ptrCast(c.ecs_get_id(self.world.raw, self.raw, id))); return @alignCast(@ptrCast(c.ecs_get_id(self.world.raw, self.raw, id)));
} }
pub fn set(self: Self, comptime T: type, value: T) Self { pub fn set(self: Self, comptime T: type, value: T) void {
const id = Lookup(ctx, T).id; const id = Lookup(ctx, T).id;
_ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(T), &value); _ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(T), &value);
return self;
} }
}; };
} }

@ -0,0 +1,101 @@
const c = @import("./c.zig");
pub const FlecsError = error{
InvalidOperation,
InvalidParameter,
ConstraintViolated,
OutOfMemory,
OutOfRange,
Unsupported,
InternalError,
AlreadyDefined,
MissingOsApi,
OperationFailed,
InvalidConversion,
IdInUse,
CycleDetected,
LeakDetected,
DoubleFree,
InconsistentName,
NameInUse,
NotAComponent,
InvalidComponentSize,
InvalidComponentAlignment,
ComponentNotRegistered,
InconsistentComponentId,
InconsistentComponentAction,
ModuleUndefined,
MissingSymbol,
AlreadyInUse,
AccessViolation,
ColumnIndexOutOfRange,
ColumnIsNotShared,
ColumnIsShared,
ColumnTypeMismatch,
InvalidWhileReadonly,
LockedStorage,
InvalidFromWorker,
/// An unexpected error occurred in Flecs,
/// but we're not exactly sure what happened.
Unknown,
};
/// Gets the last logged error that occurred in Flecs.
/// Calling this function resets the error code.
pub fn getLastError() ?FlecsError {
return intToFlecsError(c.ecs_log_last_error());
}
/// Gets the last logged error that occurred in Flecs.
/// Calling this function resets the error code.
pub fn getLastErrorOrUnknown() FlecsError {
return getLastError() orelse FlecsError.Unknown;
}
pub fn intToFlecsError(i: i32) ?FlecsError {
return switch (i) {
c.ECS_INVALID_OPERATION => FlecsError.InvalidOperation,
c.ECS_INVALID_PARAMETER => FlecsError.InvalidParameter,
c.ECS_CONSTRAINT_VIOLATED => FlecsError.ConstraintViolated,
c.ECS_OUT_OF_MEMORY => FlecsError.OutOfMemory,
c.ECS_OUT_OF_RANGE => FlecsError.OutOfRange,
c.ECS_UNSUPPORTED => FlecsError.Unsupported,
c.ECS_INTERNAL_ERROR => FlecsError.InternalError,
c.ECS_ALREADY_DEFINED => FlecsError.AlreadyDefined,
c.ECS_MISSING_OS_API => FlecsError.MissingOsApi,
c.ECS_OPERATION_FAILED => FlecsError.OperationFailed,
c.ECS_INVALID_CONVERSION => FlecsError.InvalidConversion,
c.ECS_ID_IN_USE => FlecsError.IdInUse,
c.ECS_CYCLE_DETECTED => FlecsError.CycleDetected,
c.ECS_LEAK_DETECTED => FlecsError.LeakDetected,
c.ECS_DOUBLE_FREE => FlecsError.DoubleFree,
c.ECS_INCONSISTENT_NAME => FlecsError.InconsistentName,
c.ECS_NAME_IN_USE => FlecsError.NameInUse,
c.ECS_NOT_A_COMPONENT => FlecsError.NotAComponent,
c.ECS_INVALID_COMPONENT_SIZE => FlecsError.InvalidComponentSize,
c.ECS_INVALID_COMPONENT_ALIGNMENT => FlecsError.InvalidComponentAlignment,
c.ECS_COMPONENT_NOT_REGISTERED => FlecsError.ComponentNotRegistered,
c.ECS_INCONSISTENT_COMPONENT_ID => FlecsError.InconsistentComponentId,
c.ECS_INCONSISTENT_COMPONENT_ACTION => FlecsError.InconsistentComponentAction,
c.ECS_MODULE_UNDEFINED => FlecsError.ModuleUndefined,
c.ECS_MISSING_SYMBOL => FlecsError.MissingSymbol,
c.ECS_ALREADY_IN_USE => FlecsError.AlreadyInUse,
c.ECS_ACCESS_VIOLATION => FlecsError.AccessViolation,
c.ECS_COLUMN_INDEX_OUT_OF_RANGE => FlecsError.ColumnIndexOutOfRange,
c.ECS_COLUMN_IS_NOT_SHARED => FlecsError.ColumnIsNotShared,
c.ECS_COLUMN_IS_SHARED => FlecsError.ColumnIsShared,
c.ECS_COLUMN_TYPE_MISMATCH => FlecsError.ColumnTypeMismatch,
c.ECS_INVALID_WHILE_READONLY => FlecsError.InvalidWhileReadonly,
c.ECS_LOCKED_STORAGE => FlecsError.LockedStorage,
c.ECS_INVALID_FROM_WORKER => FlecsError.InvalidFromWorker,
else => null,
};
}

@ -2,10 +2,12 @@ const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const c = @import("./c.zig"); const c = @import("./c.zig");
const err = @import("./error.zig");
const util = @import("./util.zig"); const util = @import("./util.zig");
const Lookup = @import("./main.zig").Lookup; const Lookup = @import("./main.zig").Lookup;
const Entity = @import("./entity.zig").Entity; const Entity = @import("./entity.zig").Entity;
const EntityError = @import("./entity.zig").EntityError;
const EntityConfig = @import("./entity.zig").EntityConfig; const EntityConfig = @import("./entity.zig").EntityConfig;
const Iter = @import("./iter.zig").Iter; const Iter = @import("./iter.zig").Iter;
@ -23,7 +25,7 @@ pub fn World(comptime ctx: anytype) type {
return result; return result;
} }
pub fn initWithArgs(alloc: Allocator, args: [][*:0]u8) !*Self { pub fn initWithArgs(alloc: Allocator, args: [][*:0]const u8) !*Self {
var result = try alloc.create(Self); var result = try alloc.create(Self);
result.raw = c.ecs_init_w_args(args.len, args.ptr).?; result.raw = c.ecs_init_w_args(args.len, args.ptr).?;
result.allocator = alloc; result.allocator = alloc;
@ -42,7 +44,9 @@ pub fn World(comptime ctx: anytype) type {
/// Returns an `Entity` for the specified `ecs_entity_t` value, or an /// Returns an `Entity` for the specified `ecs_entity_t` value, or an
/// error if the entity is invalid or not alive in this `World`. /// error if the entity is invalid or not alive in this `World`.
pub fn lookupAlive(self: *Self, id: c.ecs_entity_t) !Entity(ctx) { pub fn lookupAlive(self: *Self, id: c.ecs_entity_t) !Entity(ctx) {
return Entity(ctx).fromRaw(self, id).ensureAlive(); const result = Entity(ctx).fromRaw(self, id);
try result.ensureAlive();
return result;
} }
/// Returns the component `Entity` registered for the specified /// Returns the component `Entity` registered for the specified
@ -52,33 +56,24 @@ pub fn World(comptime ctx: anytype) type {
} }
/// Creates a new `Entity` in this `World`. /// Creates a new `Entity` in this `World`.
/// /// See `Entity.new(...)` for more information.
/// The `config` argument may be used to specify the `name` and pub fn entity(self: *Self, config: EntityConfig, add: anytype) !Entity(ctx) {
/// `symbol` used to create the entity with. If an entity with the
/// given name already exists, that one is modified instead.
///
/// The `add` argument is a tuple that specifies the `Id`s the entity
/// is created with initially. For example:
/// `.{ Position, Velocity, .{ ChildOf, parent } }`
///
/// When you add components, they are default-initialized. To set
/// their values you'll need to use one of the setter functions on the
/// `Entity` returned from this function.
pub fn entity(self: *Self, config: EntityConfig, add: anytype) Entity(ctx) {
return Entity(ctx).new(self, config, add); return Entity(ctx).new(self, config, add);
} }
pub fn component( pub fn component(self: *Self, comptime T: type) !Entity(ctx) {
self: *Self,
comptime T: type,
) Entity(ctx) {
const name = util.simpleTypeName(T); const name = util.simpleTypeName(T);
const entity2 = try self.entity(.{ .name = name, .symbol = name, .use_low_id = true }, .{});
const desc = std.mem.zeroInit(c.ecs_component_desc_t, .{ const desc = std.mem.zeroInit(c.ecs_component_desc_t, .{
.entity = self.entity(.{ .name = name, .symbol = name, .use_low_id = true }, .{}).raw, .entity = entity2.raw,
.type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) }, .type = .{ .size = @sizeOf(T), .alignment = @alignOf(T) },
}); });
Lookup(ctx, T).id = c.ecs_component_init(self.raw, &desc);
return Entity(ctx).fromRaw(self, Lookup(ctx, T).id); var result = c.ecs_component_init(self.raw, &desc);
if (result == 0) return err.getLastErrorOrUnknown();
Lookup(ctx, T).id = result;
return Entity(ctx).fromRaw(self, result);
} }
pub fn system( pub fn system(
@ -87,23 +82,24 @@ pub fn World(comptime ctx: anytype) type {
callback: SystemCallback, callback: SystemCallback,
phase: anytype, phase: anytype,
expr: [:0]const u8, expr: [:0]const u8,
) Entity(ctx) { ) !Entity(ctx) {
var context = SystemCallbackContext.init(self, callback); const phase2 = util.anyToEntity(ctx, phase);
const entity2 = try if (phase2 != 0)
const p = util.anyToEntity(ctx, phase); self.entity(.{ .name = name }, .{ .{ c.EcsDependsOn, phase2 }, phase2 })
const e = if (p != 0)
self.entity(.{ .name = name }, .{ .{ c.EcsDependsOn, p }, p })
else else
self.entity(.{ .name = name }, .{}); self.entity(.{ .name = name }, .{});
var desc = std.mem.zeroes(c.ecs_system_desc_t); var context = try SystemCallbackContext.init(self, callback);
desc.entity = e.raw; var desc = std.mem.zeroInit(c.ecs_system_desc_t, .{
.entity = entity2.raw,
.callback = &SystemCallbackContext.invoke,
.binding_ctx = context,
.binding_ctx_free = &SystemCallbackContext.free,
});
desc.query.filter.expr = expr; desc.query.filter.expr = expr;
desc.callback = &SystemCallbackContext.invoke;
desc.binding_ctx = context;
desc.binding_ctx_free = &SystemCallbackContext.free;
const result = c.ecs_system_init(self.raw, &desc); const result = c.ecs_system_init(self.raw, &desc);
if (result == 0) return err.getLastErrorOrUnknown();
return Entity(ctx).fromRaw(self, result); return Entity(ctx).fromRaw(self, result);
} }
@ -112,8 +108,8 @@ pub fn World(comptime ctx: anytype) type {
world: *Self, world: *Self,
func: SystemCallback, func: SystemCallback,
pub fn init(world: *Self, callback: SystemCallback) *SystemCallbackContext { pub fn init(world: *Self, callback: SystemCallback) !*SystemCallbackContext {
var result = world.allocator.create(SystemCallbackContext) catch @panic("OOM"); var result = try world.allocator.create(SystemCallbackContext);
result.world = world; result.world = world;
result.func = callback; result.func = callback;
return result; return result;
@ -124,7 +120,11 @@ pub fn World(comptime ctx: anytype) type {
self.world.allocator.destroy(self); self.world.allocator.destroy(self);
} }
fn invoke(it: ?*c.ecs_iter_t) callconv(.C) void { // FIXME: Dependency loop.
// Currently needs manual changing of the generated C code.
// fn invoke(it: ?*c.ecs_iter_t) callconv(.C) void {
fn invoke(it2: *anyopaque) callconv(.C) void {
const it: ?*c.ecs_iter_t = @alignCast(@ptrCast(it2));
const context: *SystemCallbackContext = @alignCast(@ptrCast(it.?.binding_ctx)); const context: *SystemCallbackContext = @alignCast(@ptrCast(it.?.binding_ctx));
var iter = Iter(ctx).fromRawPtr(context.world, it.?); var iter = Iter(ctx).fromRawPtr(context.world, it.?);
context.func(iter); context.func(iter);

@ -34,18 +34,18 @@ test "World_progress_w_0" {
var world = try World.init(std.testing.allocator); var world = try World.init(std.testing.allocator);
defer world.deinit(); defer world.deinit();
_ = world.component(Position); _ = try world.component(Position);
_ = world.component(Velocity); _ = try world.component(Velocity);
const e1 = world.entity(.{}, .{ Position, Velocity }); const e1 = try world.entity(.{}, .{ Position, Velocity });
const move_system = world.system("move", move, c.EcsOnUpdate, "Position, Velocity"); const move_system = try world.system("move", move, c.EcsOnUpdate, "Position, Velocity");
var ctx = util.Probe.init(); var ctx = util.Probe.init();
c.ecs_set_context(world.raw, &ctx); c.ecs_set_context(world.raw, &ctx);
_ = e1.set(Position, .{ .x = 0, .y = 0 }); e1.set(Position, .{ .x = 0, .y = 0 });
_ = e1.set(Velocity, .{ .x = 1, .y = 2 }); e1.set(Velocity, .{ .x = 1, .y = 2 });
_ = world.progress(0); _ = world.progress(0);
@ -70,18 +70,18 @@ test "World_progress_w_t" {
var world = try World.init(std.testing.allocator); var world = try World.init(std.testing.allocator);
defer world.deinit(); defer world.deinit();
_ = world.component(Position); _ = try world.component(Position);
_ = world.component(Velocity); _ = try world.component(Velocity);
const e1 = world.entity(.{}, .{ Position, Velocity }); const e1 = try world.entity(.{}, .{ Position, Velocity });
const move_system = world.system("move", move, c.EcsOnUpdate, "Position, Velocity"); const move_system = try world.system("move", move, c.EcsOnUpdate, "Position, Velocity");
var ctx = util.Probe.init(); var ctx = util.Probe.init();
c.ecs_set_context(world.raw, &ctx); c.ecs_set_context(world.raw, &ctx);
_ = e1.set(Position, .{ .x = 0, .y = 0 }); e1.set(Position, .{ .x = 0, .y = 0 });
_ = e1.set(Velocity, .{ .x = 1, .y = 2 }); e1.set(Velocity, .{ .x = 1, .y = 2 });
_ = world.progress(2); _ = world.progress(2);

Loading…
Cancel
Save