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