const std = @import("std"); const Sink = @import("sink.zig").Sink; const Delegate = @import("delegate.zig").Delegate; pub fn Signal(comptime Event: type) type { return struct { const Self = @This(); calls: std.ArrayList(Delegate(Event)), allocator: ?*std.mem.Allocator = null, pub fn init(allocator: *std.mem.Allocator) Self { // we purposely do not store the allocator locally in this case so we know not to destroy ourself in deint! return Self{ .calls = std.ArrayList(Delegate(Event)).init(allocator), }; } /// heap allocates a Signal pub fn create(allocator: *std.mem.Allocator) *Self { var signal = allocator.create(Self) catch unreachable; signal.calls = std.ArrayList(Delegate(Event)).init(allocator); signal.allocator = allocator; return signal; } pub fn deinit(self: *Self) void { self.calls.deinit(); // optionally destroy ourself as well if we came from an allocator if (self.allocator) |allocator| allocator.destroy(self); } pub fn size(self: Self) usize { return self.calls.items.len; } pub fn empty(self: Self) bool { return self.size == 0; } /// Disconnects all the listeners from a signal pub fn clear(self: *Self) void { self.calls.items.len = 0; } pub fn publish(self: Self, arg: Event) void { for (self.calls.items) |call| { call.trigger(arg); } } /// Constructs a sink that is allowed to modify a given signal pub fn sink(self: *Self) Sink(Event) { return Sink(Event).init(self); } }; } fn tester(param: u32) void { std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } const Thing = struct { field: f32 = 0, pub fn tester(_: *Thing, param: u32) void { std.testing.expectEqual(@as(u32, 666), param) catch unreachable; } }; test "Signal/Sink" { var signal = Signal(u32).init(std.testing.allocator); defer signal.deinit(); var sink = signal.sink(); sink.connect(tester); try std.testing.expectEqual(@as(usize, 1), signal.size()); // bound listener var thing = Thing{}; sink.connectBound(&thing, "tester"); signal.publish(666); sink.disconnect(tester); signal.publish(666); try std.testing.expectEqual(@as(usize, 1), signal.size()); sink.disconnectBound(&thing); try std.testing.expectEqual(@as(usize, 0), signal.size()); } test "Sink Before null" { var signal = Signal(u32).init(std.testing.allocator); defer signal.deinit(); var sink = signal.sink(); sink.connect(tester); try std.testing.expectEqual(@as(usize, 1), signal.size()); var thing = Thing{}; sink.before(null).connectBound(&thing, "tester"); try std.testing.expectEqual(@as(usize, 2), signal.size()); }