1 module dscord.types.base; 2 3 import std.conv, 4 std.typecons, 5 std.stdio, 6 std.algorithm, 7 std.traits, 8 std.functional; 9 10 import dscord.client; 11 12 import vibe.core.core : runTask, sleep; 13 import vibe.core.sync; 14 15 // Commonly used public imports 16 public import dscord.util.json; 17 public import std.datetime; 18 19 // TODO: Eventually this should be a type 20 alias Snowflake = ulong; 21 22 string toString(Snowflake s) { 23 return to!string(s); 24 } 25 26 /* 27 AsyncChainer is a utility for exposing methods that can help 28 chain actions with various delays/resolving patterns. 29 */ 30 class AsyncChainer(T) { 31 private { 32 T obj; 33 AsyncChainer!T parent; 34 ManualEvent resolveEvent; 35 } 36 37 /** 38 The base constructor which handles the optional creation of ManualEvent used 39 in the case where this member of the AsyncChain has a delay (or depends on 40 something with a delay). 41 42 Params: 43 obj = the object to wrap for chaining 44 hasResolver = if true, create a ManualEvent used for resolving 45 */ 46 this(T obj, bool hasResolver = false) { 47 this.obj = obj; 48 49 if (hasResolver) { 50 this.resolveEvent = createManualEvent(); 51 } 52 } 53 54 /** 55 Delayed constructor creates an AsyncChainer chain member which waits for 56 the specified delay before resolving the current and next members of the 57 chain. 58 59 Params: 60 obj = the object to wrap for chaining 61 delay = a duration to delay before resolving 62 parent = the parent member in the chain to depend on before resolving 63 */ 64 this(T obj, Duration delay, AsyncChainer!T parent = null) { 65 this(obj, true); 66 67 this.parent = parent; 68 69 runTask({ 70 // If we have a parent, wait on its resolve event first 71 if (this.parent) { 72 this.parent.resolveEvent.wait(); 73 } 74 75 // Then sleep for the delay 76 sleep(delay); 77 78 // And trigger our resolve event 79 this.resolveEvent.emit(); 80 }); 81 } 82 83 /** 84 Utility method for chaining. Returns a new child member of the chain. 85 */ 86 AsyncChainer!T after(Duration delay) { 87 return new AsyncChainer!T(this.obj, delay, this); 88 } 89 90 /** 91 opDispatch override that provides a mechanisim for wrapped chaining of the 92 inner object. 93 */ 94 AsyncChainer!T opDispatch(string func, Args...)(Args args) { 95 if (this.resolveEvent) { 96 auto next = new AsyncChainer!T(this.obj, true); 97 98 runTask({ 99 this.resolveEvent.wait(); 100 this.obj.call!(func)(args); 101 next.resolveEvent.emit(); 102 }); 103 104 return next; 105 } else { 106 this.obj.call!(func)(args); 107 return this; 108 } 109 } 110 } 111 112 /** 113 Base class for all models. Provides a simple interface definition and some 114 utility constructor code. 115 */ 116 class IModel { 117 Client client; 118 119 void init() {}; 120 void load(ref JSON obj) {}; 121 122 this(Client client, ref JSON obj) { 123 debug { 124 client.log.tracef("Starting creation of model %s", this.toString); 125 auto sw = StopWatch(AutoStart.yes); 126 } 127 128 this.client = client; 129 this.init(); 130 this.load(obj); 131 132 debug { 133 this.client.log.tracef("Finished creation of model %s in %sms", this.toString, 134 sw.peek().to!("msecs", real)); 135 } 136 } 137 } 138 139 /** 140 Base template for all models. Provides utility methods for AsyncChaining and 141 a base constructor that calls the parent IModel constructor. 142 */ 143 mixin template Model() { 144 this(Client client, ref JSON obj) { 145 super(client, obj); 146 } 147 148 auto after(Duration delay) { 149 return new AsyncChainer!(typeof(this))(this, delay); 150 } 151 152 auto chain() { 153 return new AsyncChainer!(typeof(this))(this); 154 } 155 156 void call(string blah, T...)(T args) { 157 __traits(getMember, this, blah)(args); 158 } 159 } 160 161 /** 162 Utility method which reads a Snowflake off of a fast JSON object. 163 */ 164 Snowflake readSnowflake(ref JSON obj) { 165 string data = obj.read!string; 166 if (!data) return 0; 167 return data.to!Snowflake; 168 } 169 170 /** 171 Utility method which loads many of a model T off of a fast JSON object. Returns 172 an array of model T objects. 173 */ 174 T[] loadManyArray(T)(Client client, ref JSON obj) { 175 T[] data; 176 177 foreach (item; obj) { 178 data ~= new T(client, obj); 179 } 180 181 return data; 182 } 183 184 /** 185 Utility method that loads many of a model T off of a fast JSON object. Calls 186 the delegate f for each member loaded, returning nothing. 187 */ 188 void loadMany(T)(Client client, ref JSON obj, void delegate(T) F) { 189 foreach (item; obj) { 190 F(new T(client, obj)); 191 } 192 } 193 194 /** 195 Utility method that loads many of a model T off of a fast JSON object, passing 196 in a sub-type TSub as the first argument to the constructor. Calls the delegate 197 f for each member loaded, returning nothing. 198 */ 199 void loadManyComplex(TSub, T)(TSub sub, ref JSON obj, void delegate(T) F) { 200 foreach (item; obj) { 201 F(new T(sub, obj)); 202 } 203 } 204 205 /** 206 A utility wrapper around an associative array that stores models. 207 */ 208 class ModelMap(TKey, TValue) { 209 TValue[TKey] data; 210 211 /** 212 Set the key to a value. 213 */ 214 TValue set(TKey key, TValue value) { 215 if (value is null) { 216 this.remove(key); 217 return null; 218 } 219 220 this.data[key] = value; 221 return value; 222 } 223 224 /** 225 Return the value for a key. 226 */ 227 TValue get(TKey key) { 228 return this.data[key]; 229 } 230 231 /** 232 Return the value for a key, or if it doesn't exist a default value. 233 */ 234 TValue get(TKey key, TValue def) { 235 if (this.has(key)) { 236 return this.get(key); 237 } 238 return def; 239 } 240 241 /** 242 Utility method that returns the value for a key. 243 */ 244 TValue opCall(TKey key) { 245 return this.data[key]; 246 } 247 248 /** 249 Removes a key. 250 */ 251 void remove(TKey key) { 252 this.data.remove(key); 253 } 254 255 /** 256 Returns true if the key exists within the mapping. 257 */ 258 bool has(TKey key) { 259 return (key in this.data) != null; 260 } 261 262 TValue opIndex(TKey key) { 263 return this.get(key); 264 } 265 266 void opIndexAssign(TValue value, TKey key) { 267 this.set(key, value); 268 } 269 270 /** 271 Returns the length of the mapping. 272 */ 273 size_t length() { 274 return this.data.length; 275 } 276 277 /** 278 Allows using a delegate to filter the values of the mapping. 279 280 Params: 281 f = a delegate which returns true if the passed in value matches. 282 */ 283 auto filter(bool delegate(TValue) f) { 284 return this.data.values.filter!(f); 285 } 286 287 /** 288 Allows applying a delegate over the values of the mapping. 289 290 Params: 291 f = a delegate which is applied to each value in the mapping. 292 */ 293 auto each(void delegate(TValue) f) { 294 return this.data.values.each!(f); 295 } 296 297 /** 298 Returns a single value from the mapping, based on the return value of a 299 delegate. 300 301 Params: 302 f = a delegate which returns true if the value passed in matches. 303 */ 304 TValue pick(bool delegate(TValue) f) { 305 foreach (value; this.data.values) { 306 if (f(value)) { 307 return value; 308 } 309 } 310 return null; 311 } 312 313 /** 314 Returns an array of keys from the mapping. 315 */ 316 auto keys() { 317 return this.data.keys; 318 } 319 320 /** 321 returns an array of values from the mapping. 322 */ 323 auto values() { 324 return this.data.values; 325 } 326 }