From 4b7be3e1f7b409a626380175df4b6280f199ca9f Mon Sep 17 00:00:00 2001 From: copygirl Date: Sat, 2 Sep 2023 15:33:08 +0200 Subject: [PATCH] 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 --- src/entity.zig | 46 ++++++++++++++-------- src/error.zig | 101 +++++++++++++++++++++++++++++++++++++++++++++++++ src/world.zig | 72 +++++++++++++++++------------------ test/world.zig | 24 ++++++------ 4 files changed, 179 insertions(+), 64 deletions(-) create mode 100644 src/error.zig diff --git a/src/entity.zig b/src/entity.zig index 2fb3123..af07894 100644 --- a/src/entity.zig +++ b/src/entity.zig @@ -1,6 +1,7 @@ const std = @import("std"); const c = @import("./c.zig"); +const err = @import("./error.zig"); const util = @import("./util.zig"); 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 /// 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()`. /// 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(); + /// 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 { return .{ .world = world, .raw = raw }; } /// Creates a new `Entity` in the specified world. - /// See `World.entity(...)` for more information. - pub fn new(world: *World(ctx), config: EntityConfig, add: anytype) Self { + /// May return an existing entity if one with the given name exists. + /// 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); desc.name = if (config.name) |n| n.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); const result = c.ecs_entity_init(world.raw, &desc); + if (result == 0) return err.getLastErrorOrUnknown(); return Self.fromRaw(world, result); } /// 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 .. /// - .. 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. /// - .. 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. - pub fn ensureValid(self: Self) !Self { + pub fn ensureValid(self: Self) !void { // `0` is not a valid entity id. if (self.raw == 0) return EntityError.IsNone; // 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); if (c.ECS_GENERATION(entity) != c.ECS_GENERATION(self.raw)) return EntityError.GenMismatch; - - return self; } /// Ensures this `Entity` is alive in this world, returning an error if not. - pub fn ensureAlive(self: Self) !Self { - return if ((try ensureValid(self)).isAlive()) self else EntityError.IsNotAlive; - } - - pub fn isAlive(self: Self) bool { - return c.ecs_is_alive(self.world.raw, self.raw); + pub fn ensureAlive(self: Self) !void { + if (!c.ecs_is_alive(self.world.raw, self.raw)) return EntityError.IsNotAlive; } 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))); } - 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; _ = c.ecs_set_id(self.world.raw, self.raw, id, @sizeOf(T), &value); - return self; } }; } diff --git a/src/error.zig b/src/error.zig new file mode 100644 index 0000000..64fe3d5 --- /dev/null +++ b/src/error.zig @@ -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, + }; +} diff --git a/src/world.zig b/src/world.zig index 333d1a1..0c3a88a 100644 --- a/src/world.zig +++ b/src/world.zig @@ -2,10 +2,12 @@ const std = @import("std"); const Allocator = std.mem.Allocator; const c = @import("./c.zig"); +const err = @import("./error.zig"); const util = @import("./util.zig"); const Lookup = @import("./main.zig").Lookup; const Entity = @import("./entity.zig").Entity; +const EntityError = @import("./entity.zig").EntityError; const EntityConfig = @import("./entity.zig").EntityConfig; const Iter = @import("./iter.zig").Iter; @@ -23,7 +25,7 @@ pub fn World(comptime ctx: anytype) type { 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); result.raw = c.ecs_init_w_args(args.len, args.ptr).?; 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 /// error if the entity is invalid or not alive in this `World`. 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 @@ -52,33 +56,24 @@ pub fn World(comptime ctx: anytype) type { } /// Creates a new `Entity` in this `World`. - /// - /// 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. - /// - /// 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) { + /// See `Entity.new(...)` for more information. + pub fn entity(self: *Self, config: EntityConfig, add: anytype) !Entity(ctx) { return Entity(ctx).new(self, config, add); } - pub fn component( - self: *Self, - comptime T: type, - ) Entity(ctx) { + pub fn component(self: *Self, comptime T: type) !Entity(ctx) { 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, .{ - .entity = self.entity(.{ .name = name, .symbol = name, .use_low_id = true }, .{}).raw, + .entity = entity2.raw, .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( @@ -87,23 +82,24 @@ pub fn World(comptime ctx: anytype) type { callback: SystemCallback, phase: anytype, expr: [:0]const u8, - ) Entity(ctx) { - var context = SystemCallbackContext.init(self, callback); - - const p = util.anyToEntity(ctx, phase); - const e = if (p != 0) - self.entity(.{ .name = name }, .{ .{ c.EcsDependsOn, p }, p }) + ) !Entity(ctx) { + const phase2 = util.anyToEntity(ctx, phase); + const entity2 = try if (phase2 != 0) + self.entity(.{ .name = name }, .{ .{ c.EcsDependsOn, phase2 }, phase2 }) else self.entity(.{ .name = name }, .{}); - var desc = std.mem.zeroes(c.ecs_system_desc_t); - desc.entity = e.raw; + var context = try SystemCallbackContext.init(self, callback); + 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.callback = &SystemCallbackContext.invoke; - desc.binding_ctx = context; - desc.binding_ctx_free = &SystemCallbackContext.free; const result = c.ecs_system_init(self.raw, &desc); + if (result == 0) return err.getLastErrorOrUnknown(); return Entity(ctx).fromRaw(self, result); } @@ -112,8 +108,8 @@ pub fn World(comptime ctx: anytype) type { world: *Self, func: SystemCallback, - pub fn init(world: *Self, callback: SystemCallback) *SystemCallbackContext { - var result = world.allocator.create(SystemCallbackContext) catch @panic("OOM"); + pub fn init(world: *Self, callback: SystemCallback) !*SystemCallbackContext { + var result = try world.allocator.create(SystemCallbackContext); result.world = world; result.func = callback; return result; @@ -124,7 +120,11 @@ pub fn World(comptime ctx: anytype) type { 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)); var iter = Iter(ctx).fromRawPtr(context.world, it.?); context.func(iter); diff --git a/test/world.zig b/test/world.zig index 4e09d6d..57f10b8 100644 --- a/test/world.zig +++ b/test/world.zig @@ -34,18 +34,18 @@ test "World_progress_w_0" { var world = try World.init(std.testing.allocator); defer world.deinit(); - _ = world.component(Position); - _ = world.component(Velocity); + _ = try world.component(Position); + _ = 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(); c.ecs_set_context(world.raw, &ctx); - _ = e1.set(Position, .{ .x = 0, .y = 0 }); - _ = e1.set(Velocity, .{ .x = 1, .y = 2 }); + e1.set(Position, .{ .x = 0, .y = 0 }); + e1.set(Velocity, .{ .x = 1, .y = 2 }); _ = world.progress(0); @@ -70,18 +70,18 @@ test "World_progress_w_t" { var world = try World.init(std.testing.allocator); defer world.deinit(); - _ = world.component(Position); - _ = world.component(Velocity); + _ = try world.component(Position); + _ = 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(); c.ecs_set_context(world.raw, &ctx); - _ = e1.set(Position, .{ .x = 0, .y = 0 }); - _ = e1.set(Velocity, .{ .x = 1, .y = 2 }); + e1.set(Position, .{ .x = 0, .y = 0 }); + e1.set(Velocity, .{ .x = 1, .y = 2 }); _ = world.progress(2);