1 /** 2 Copyright: Copyright (c) 2013-2018 Andrey Penechko. 3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 Authors: Andrey Penechko. 5 */ 6 7 module voxelman.utils.signal; 8 9 import std.algorithm : countUntil; 10 import std.functional : toDelegate; 11 import std.traits : isDelegate, ParameterTypeTuple, isFunctionPointer; 12 13 struct Signal(Args...) 14 { 15 alias SlotType = void delegate(Args); 16 17 SlotType[] slots; 18 19 void emit(Args args) @trusted 20 { 21 foreach(slot; slots) 22 slot(args); 23 } 24 25 void connect(Slot)(Slot slot) @trusted if(is(ParameterTypeTuple!Slot == Args)) 26 { 27 static if(isDelegate!Slot) 28 { 29 slots ~= slot; 30 } 31 else static if(isFunctionPointer!Slot) 32 { 33 slots ~= toDelegate(slot); 34 } 35 } 36 37 void disconnect(Slot)(Slot slot) @trusted 38 { 39 static if(isDelegate!Slot) 40 { 41 auto haystackPos = countUntil(slots, slot); 42 43 if(haystackPos >= 0) 44 { 45 slots = slots[0..haystackPos] ~ slots[haystackPos+1 .. $]; 46 } 47 } 48 else static if(isFunctionPointer!Slot) 49 { 50 // struct from functional toDelegate 51 static struct DelegateFields { 52 union { 53 SlotType del; 54 struct { 55 void* contextPtr; 56 void* funcPtr; 57 } 58 } 59 } 60 61 auto haystackPos = countUntil!((SlotType _slot, ){return (cast(DelegateFields)_slot).contextPtr == slot;})(slots); 62 63 if(haystackPos >= 0) 64 { 65 slots = slots[0..haystackPos] ~ slots[haystackPos+1 .. $]; 66 } 67 } 68 69 } 70 71 void disconnectAll() @trusted 72 { 73 slots = []; 74 } 75 } 76 77 // Test for signal with 0 arguments 78 unittest 79 { 80 Signal!() test1; // Signal for slots not taking any parameters. 81 82 auto num = 0; 83 auto slot = (){num += 1;}; // Slot is plain delegate. 84 test1.connect(slot); 85 assert(num == 0); // Slot doesn't gets called upon connecting. 86 87 test1.emit(); 88 assert(num == 1); // Each connected slot is called only once. 89 90 test1.disconnect(slot); 91 assert(num == 1); // Doesn't called upon disconnecting. 92 93 test1.emit(); 94 assert(num == 1); 95 } 96 97 // Test for signal with 1 argument 98 unittest 99 { 100 // Slot that takes one argument. 101 // Slots can have any number of parameters. 102 Signal!int test2; 103 104 auto num = 0; 105 auto slot = (int increment){num += increment;}; 106 test2.connect(slot); 107 assert(num == 0); 108 109 test2.emit(3); 110 assert(num == 3); 111 112 test2.disconnect(slot); 113 assert(num == 3); 114 115 test2.emit(4); 116 assert(num == 3); 117 } 118 119 // Test for multiple slots 120 unittest 121 { 122 Signal!int test3; 123 124 auto num = 0; 125 auto slot1 = (int inc){num += inc;}; 126 auto slot2 = (int mult){num *= mult;}; 127 128 test3.connect(slot1); 129 test3.connect(slot2); 130 assert(num == 0); 131 132 test3.emit(2); 133 assert(num == 4); 134 135 test3.connect(slot1); 136 test3.emit(3); 137 assert(num == (4 + 3) * 3 + 3); // 24 138 139 test3.disconnect(slot1); 140 test3.emit(2); 141 assert(num == 24 * 2 + 2); // 50 142 143 test3.disconnectAll(); 144 test3.emit(4); 145 assert(num == 50); 146 } 147 148 // Test for static slots 149 unittest 150 { 151 Signal!(int*, int) test4; 152 153 auto num = 0; 154 // Testing static functions. 155 static void staticSlot(int* num, int inc){*num += inc;} 156 157 test4.connect(&staticSlot); 158 assert(num == 0); 159 160 test4.emit(&num, 2); 161 assert(num == 2); 162 163 test4.disconnect(&staticSlot); 164 test4.emit(&num, 2); 165 assert(num == 2); 166 }