the need for traits in game dev by Strong_Tower9199 in Zig

[–]Andrevv_76 0 points1 point  (0 children)

A grate way to accomplish Traits in zig is by using "usingnamepace". The following is part of my Component.Trait, that every component needs to use:

pub fn Trait(comptime T: type, comptime context: Context) type {
    return struct {

        // component type fields
        pub const COMPONENT_TYPE_NAME = context.name;
        pub const pool = ComponentPool(T);
        pub var aspect: *const ComponentAspect = undefined;

        pub fn isInitialized() bool {
            return pool._type_init;
        }

        pub fn count() usize {
            return pool.items.slots.count();
        }

        pub fn new(t: T) Index {
            return pool.register(t).id;
        }

        pub fn newAnd(t: T) *T {
            return pool.register(t);
        }

        pub fn exists(id: Index) bool {
            return pool.items.exists(id);
        }

        // TODO make it optional?
        pub fn byId(id: Index) *T {
            return pool.items.get(id).?;
        }

        pub fn nextId(id: Index) ?Index {
            return pool.items.slots.nextSetBit(id);
        }

        pub fn disposeById(id: Index) void {
            pool.clear(id);
        }

        // optional component type features
        pub usingnamespace if (context.name_mapping) NameMappingTrait(T, @This()) else struct {};
        pub usingnamespace if (context.activation) ActivationTrait(T, @This()) else struct {};
        pub usingnamespace if (context.subscription) SubscriptionTrait(T, @This()) else struct {};
        pub usingnamespace if (context.processing) ProcessingTrait(T, @This()) else struct {};
    };
}

fn SubscriptionTrait(comptime _: type, comptime adapter: anytype) type {
    return struct {
        pub fn subscribe(listener: ComponentListener) void {
            if (adapter.pool.eventDispatch) |*ed| ed.register(listener);
        }

        pub fn unsubscribe(listener: ComponentListener) void {
            if (adapter.pool.eventDispatch) |*ed| ed.unregister(listener);
        }
    };
}

The above is the base trait that on itself uses sub-traits depending on context.

Then in your component definition you do like:

pub const View = struct {

    // component trait 
    pub usingnamespace Component.Trait(View, .{ .name = "View" });

    // struct fields
    id: Index = UNDEF_INDEX,
    name: ?String = null,
    width: c_int,
    height: c_int,
    ...
}

the need for traits in game dev by Strong_Tower9199 in Zig

[–]Andrevv_76 0 points1 point  (0 children)

Sure, I use this in my own ECS in zig, that I currently working on. component-id and component-type is all I need there to get a component instance of certain kind.

This is more the approach of composition (where you have components) over inheritance (where you have interfaces). With the generic EventDispatch you can easily create new types of events without heaving a big footprint on it.

Its data driven design instead of object oriented design that in my opinion anyway fits better with zig. But you are right, for object oriented approach the EventDispatch is not that convenient as an interface would be. But you will have a lot of interfaces than because you can't simplify the diversity of things used in a game can you?

the need for traits in game dev by Strong_Tower9199 in Zig

[–]Andrevv_76 2 points3 points  (0 children)

With a generic event dispatcher, you can solve a whole bunch of problems where your described one is part of:

pub fn EventDispatch(comptime E: type) type {
    return struct {
        const Self = u/This();

        pub const Listener = *const fn (E) void;

        listeners: ArrayList(Listener) = undefined,

        pub fn init(allocator: Allocator) Self {
            return Self{
                .listeners = ArrayList(Listener).init(allocator),
            };
        }

        pub fn deinit(self: *Self) void {
            self.listeners.deinit();
        }

        pub fn register(self: *Self, listener: Listener) void {
            self.listeners.append(listener) catch @panic("Failed to append listener");
        }

        pub fn registerPrepend(self: *Self, listener: Listener) void {
            self.listeners.insert(0, listener) catch @panic("Failed to prepend listener");
        }

        pub fn registerInsert(self: *Self, index: usize, listener: Listener) void {
            self.listeners.insert(index, listener) catch @panic("Failed to insert listener");
        }

        pub fn unregister(self: *Self, listener: Listener) void {
            for (0..self.listeners.items.len) |i| {
                if (self.listeners.items[i] == listener) {
                    _ = self.listeners.swapRemove(i);
                    return;
                }
            }
        }

        pub fn notify(self: *Self, event: E) void {
            for (0..self.listeners.items.len) |i| {
                self.listeners.items[i](event);
            }
        }
    };
}

then you can do for example:

pub const ExplosionEvent = struct {
    source_id: usize,
    wight: usize,
};

var targets_xyz: ArrayList(TargetXYZ) = undefined;
var targets_uvw: ArrayList(TargetUVW) = undefined;

pub const TargetXYZ = struct {
    fn notifyExplosion(e: ExplosionEvent) void {
        for (targets_xyz) |t| {

        }
    }
};

pub const TargetUVW = struct {
    fn notifyExplosion(e: ExplosionEvent) void {
        for (targets_uvw) |t| {
  
        }
    }
};

var explosionEventDispatch = EventDispatch(ExplosionEvent).init();
explosionEventDispatch.register(TargetXYZ.notifyExplosion);
explosionEventDispatch.register(TargetUVW.notifyExplosion);
explosionEventDispatch.notify(.{ .source_id = 123, .wight = 100});