1 /* 2 MIT License 3 Copyright (c) 2017 Boris Barboris 4 */ 5 6 module daii.closure; 7 8 import std.conv: to; 9 import std.experimental.allocator: make, dispose; 10 import std.experimental.allocator.mallocator: Mallocator; 11 import std.functional: forward; 12 13 import daii.refcounted; 14 import daii.utils; 15 16 17 /// Abstract callable object. 18 abstract class Closure(Ret, Args...) 19 { 20 Ret call(Args args) @trusted; 21 } 22 23 template AllocationContext(Allocator = Mallocator, bool Atomic = true) 24 { 25 template CtxRefCounted(T) 26 { 27 alias CtxRefCounted = RefCounted!(T, Atomic, Allocator); 28 } 29 30 /// RAII wrapper for callable closure. 31 struct Delegate(Ret, Args...) 32 { 33 @disable this(); 34 35 private this(CtxRefCounted!(Closure!(Ret, Args)) clos) @safe 36 { 37 _closure = clos; 38 } 39 40 CtxRefCounted!(Closure!(Ret, Args)) _closure; 41 42 bool opEquals(const ref Delegate!(Ret, Args) s) const @safe @nogc 43 { 44 return _closure.v is s._closure.v; 45 } 46 47 bool opEquals(const Delegate!(Ret, Args) s) const @safe 48 { 49 return _closure.v is s._closure.v; 50 } 51 52 Ret opCall(Args args) @safe 53 { 54 return _closure.v.call(forward!args); 55 } 56 } 57 58 /// Bread and butter 59 auto autodlg(ExArgs...)(ExArgs exargs) @safe 60 { 61 static if (isStaticAllocator!Allocator) 62 enum f_idx = 0; // index of function in exargs 63 else 64 { 65 static assert(isAllocator!(ExArgs[0])); 66 enum f_idx = 1; // 0 is allocator instance 67 } 68 static assert(ExArgs.length > f_idx); 69 static assert(isFunctionPointerType!(ExArgs[f_idx])); 70 71 // return type of the delegate 72 alias RetType = ReturnType!(ExArgs[f_idx]); 73 alias AllArgs = ParamTypes!(ExArgs[f_idx]); 74 75 static assert(AllArgs.length >= (ExArgs.length - 1 - f_idx)); 76 77 // Actual type of a delegate is deduced here. Arguments of function 78 // passed by programmer are reduced until all captured variables are 79 // bound to respective parameters in the function. 80 81 // This is how many arguments are in the resulting delegate type opCall. 82 enum int dlgArgCount = AllArgs.length - ExArgs.length + 1 + f_idx; 83 84 // Deleagte argument types are taken from the function pointer 85 alias DlgArgs = Take!(dlgArgCount, AllArgs); 86 87 // Captured variable types are taken from, surprise, captured variables, 88 // passed after the function in exargs. 89 alias CapturedArgs = Skip!(1 + f_idx, ExArgs); 90 91 static class CClosure: Closure!(RetType, DlgArgs) 92 { 93 RetType function(AllArgs) _f; 94 95 // captured variables are mixed-in inside class body 96 mixin(fieldExpand!(CapturedArgs.length, "CapturedArgs")); 97 98 this(RetType function(AllArgs) f, CapturedArgs cpt) @safe 99 { 100 _f = f; 101 foreach (i, field; CapturedArgs) 102 { 103 // captured fields are named field0, field1 ... 104 mixin("this.field" ~ to!string(i)) = cpt[i]; 105 } 106 } 107 108 override RetType call(DlgArgs args) @trusted 109 { 110 static if (CapturedArgs.length > 0) 111 { 112 enum string captee_fields = 113 fieldsToParamListStr(CapturedArgs.length); 114 return _f(forward!args, mixin(captee_fields)); 115 } 116 else 117 return _f(forward!args); 118 } 119 } 120 121 // reference-counted closure is constructed... 122 CtxRefCounted!(Closure!(RetType, DlgArgs)) rfc = 123 CtxRefCounted!(CClosure).make(exargs); 124 // and passed to the delegate 125 Delegate!(RetType, DlgArgs) dlg = Delegate!(RetType, DlgArgs)(rfc); 126 return dlg; 127 } 128 } 129 130 unittest 131 { 132 alias Dlg = AllocationContext!(Mallocator, true).Delegate!(void, int); 133 static int counter = 0; 134 static int ccount = 0; 135 class CClosure: Closure!(void, int) 136 { 137 this() { ccount++; } 138 ~this() { ccount--; } 139 override void call(int v) { counter++; } 140 } 141 { 142 RefCounted!(Closure!(void, int)) rfc = RefCounted!CClosure.make; 143 Dlg dlg = Dlg(rfc); 144 dlg(3); 145 dlg(-1); 146 assert(counter == 2); 147 assert(ccount == 1); 148 } 149 assert(ccount == 0); 150 } 151 152 unittest 153 { 154 alias Ctx = AllocationContext!(Mallocator, true); 155 alias Dlg = Ctx.Delegate!(void, int); 156 int sum = 0; 157 int[] arr = [3, 5, 1, 9, 4]; 158 void map(int[] arr, Dlg dlg) 159 { 160 for (int i = 0; i < arr.length; i++) 161 dlg(arr[i]); 162 } 163 Dlg d = Ctx.autodlg((int x, int* s) { *s += x; }, &sum); 164 map(arr, d); 165 assert(sum == 22); 166 } 167 168 unittest 169 { 170 alias Ctx = AllocationContext!(Mallocator, true); 171 alias Dlg = Ctx.Delegate!(int, int, int); 172 int[] arr = [3, 5, 1, 9, 4]; 173 int reduce(D)(int[] arr, D dlg) 174 { 175 int res = dlg(arr[0], arr[1]); 176 for (int i = 2; i < arr.length; i++) 177 res = dlg(res, arr[i]); 178 return res; 179 } 180 Dlg d = Ctx.autodlg((int x, int y) => x + y); 181 int sum = reduce(arr, d); 182 assert(sum == 22); 183 } 184 185 unittest 186 { 187 alias Ctx = AllocationContext!(Mallocator, true); 188 alias Dlg = Ctx.Delegate!(void); 189 Dlg d = Ctx.autodlg((){}); 190 d(); 191 } 192 193 unittest 194 { 195 alias Ctx = AllocationContext!(Mallocator, true); 196 alias Dlg = Ctx.Delegate!(int, int, float); 197 Dlg d = Ctx.autodlg((int x, float y){ return 3;}); 198 d(4, 4.0f); 199 } 200 201 import std.experimental.allocator.showcase; 202 203 unittest 204 { 205 alias AllocType = StackFront!(4096, Mallocator); 206 AllocType al; 207 alias Ctx = AllocationContext!(AllocType*, true); 208 alias Dlg = Ctx.Delegate!(int, int, float); 209 Dlg d = Ctx.autodlg(&al, (int x, float y){ return 3;}); 210 d(4, 4.0f); 211 }