1 module vsignal.slot;
2 
3 // https://issues.dlang.org/show_bug.cgi?id=5710
4 /**
5 Binds a function to a Slot. A payload can be passed if the function to call is
6 a data member function of the same or if the function accepts as its first
7 parameter a reference to its type.
8 
9 Params:
10 	pred = the function to bind.
11 	slot = the Slot that holds the function.
12 	instance = the payload to keep.
13 */
14 package void connect(alias pred, F)(auto ref Slot!F slot)
15 {
16 	import vsignal.utils : tryForward;
17 
18 	with(slot)
19 	{
20 		payload = null;
21 		fn = (const scope void*, Parameters args) {
22 			static if (is(ReturnType == void))
23 				pred(tryForward!(pred, args));
24 			else
25 				return ReturnType(pred(tryForward!(pred, args)));
26 		};
27 	}
28 }
29 
30 @safe pure nothrow @nogc unittest
31 {
32 	Slot!(void delegate(int)) slot;
33 
34 	class C
35 	{
36 		static void opCall(int) {}
37 		static void fooA(const int) {}
38 		static void fooB(ref int) {}
39 		static void fooC(const ref int) {}
40 	}
41 
42 	// all lambda listeners can omit the type
43 	slot.connect!((ref _)       {});
44 	slot.connect!((const ref _) {});
45 	slot.connect!((_)           {});
46 	slot.connect!((const _)     {});
47 
48 	slot.connect!(C.opCall);
49 	slot.connect!(C.fooA);
50 	slot.connect!(C.fooB);
51 	slot.connect!(C.fooC);
52 }
53 
54 ///
55 package void connect(alias pred, T, F)(auto ref Slot!F slot, ref T instance)
56 {
57 	with(slot)
58 	{
59 		payload = () @trusted { return cast(from!"std.traits".Unqual!T*) &instance; } ();
60 		fn = (const scope void* payload, Parameters args)
61 		{
62 			import core.lifetime : forward;
63 			import vsignal.utils : invoke;
64 
65 			T* type = () @trusted { return cast(T*) payload; } ();
66 
67 			static if (is(ReturnType == void))
68 				invoke!pred(*type, forward!args);
69 			else
70 				return ReturnType(invoke!pred(*type, forward!args));
71 		};
72 	}
73 }
74 
75 @safe pure nothrow @nogc unittest
76 {
77 	Slot!(void delegate(int)) slot;
78 
79 	struct Listener
80 	{
81 		void opCall(int) {}
82 		void fooA(const int) {}
83 		void fooB(ref int) {}
84 		void fooC(const ref int) {}
85 	}
86 
87 	Listener listener;
88 
89 	slot.connect!(Listener.opCall)(listener);
90 	slot.connect!(Listener.fooA)(listener);
91 	slot.connect!(Listener.fooB)(listener);
92 	slot.connect!(Listener.fooC)(listener);
93 }
94 
95 package struct Slot(F)
96 {
97 	import vsignal.utils : from;
98 
99 	alias SlotType = F;
100 	alias ReturnType = from!"std.traits".ReturnType!F;
101 	alias Parameters = from!"std.traits".Parameters!F;
102 	alias Function   = from!"std.traits".SetFunctionAttributes!(
103 		ReturnType delegate(const scope void*, Parameters),
104 		from!"std.traits".functionLinkage!SlotType,
105 		from!"std.traits".functionAttributes!SlotType
106 	);
107 
108 	auto opCall(Parameters args)
109 	{
110 		return fn(payload, from!"core.lifetime".forward!args);
111 	}
112 
113 	bool opEquals(F)(const Slot!F other) const
114 	{
115 		return fn.funcptr is other.fn.funcptr && payload is other.payload;
116 	}
117 
118 	void reset()
119 	{
120 		fn = null;
121 		payload = null;
122 	}
123 
124 	bool opCast(T : bool)() const
125 	{
126 		return fn.funcptr !is null;
127 	}
128 
129 package:
130 	Function fn;
131 	void* payload;
132 }
133 
134 ///
135 unittest
136 {
137 	@safe struct Listener
138 	{
139 		int i;
140 
141 		auto foo(int var) return
142 		{
143 			i = var;
144 			return &this;
145 		}
146 	}
147 
148 	Listener listener;
149 	alias myfoo = Listener.foo;
150 
151 	Slot!(Listener* delegate(int) @safe) slot;
152 
153 	slot.connect!(myfoo)(listener);
154 
155 	assert(slot(4) is &listener);
156 	assert(listener.i == 4);
157 }