1 module dcord.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 dcord.client; 13 14 import vibe.core.core : runTask, sleep; 15 import vibe.core.sync : createManualEvent, LocalManualEvent; 16 17 // Commonly used public imports 18 public import dcord.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 LocalManualEvent 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 initialize() {}; 148 149 this() {} 150 151 this(IModel parent, VibeJSON obj) { 152 throw new Exception(":("); 153 } 154 155 this(Client client, VibeJSON obj) { 156 throw new Exception(":("); 157 } 158 } 159 160 /** 161 Base template for all models. Provides utility methods for AsyncChaining and 162 a base constructor that calls the parent IModel constructor. 163 */ 164 mixin template Model() { 165 this() {} 166 167 this(IModel parent, VibeJSON obj) { 168 this.load(parent.client, obj); 169 } 170 171 this(Client client, VibeJSON obj) { 172 this.load(client, obj); 173 } 174 175 void load(Client client, VibeJSON obj) { 176 version (TIMING) { 177 client.log.tracef("Starting creation of model %s", this.toString); 178 auto sw = StopWatch(AutoStart.yes); 179 } 180 181 this.client = client; 182 this.deserializeFromJSON(obj); 183 this.initialize(); 184 185 version (TIMING) { 186 this.client.log.tracef("Finished creation of model %s in %sms", this.toString, 187 sw.peek().to!("msecs", real)); 188 } 189 } 190 191 /// Allows chaining based on a delay. Returns a new AsyncChainer of this type. 192 auto after(Duration delay) { 193 return new AsyncChainer!(typeof(this))(this, delay); 194 } 195 196 /// Allows arbitrary chaining. Returns a new AsyncChainer of this type. 197 auto chain() { 198 return new AsyncChainer!(typeof(this))(this); 199 } 200 201 void call(string s, T...)(T args) { 202 __traits(getMember, this, s)(args); 203 } 204 205 } 206 207 /** 208 ModelMap serves as an abstraction layer around associative arrays that store 209 models. Usually ModelMaps will be a direct mapping of ID (Snowflake) -> Model. 210 */ 211 class ModelMap(TKey, TValue) { 212 /// Underlying associative array 213 TValue[TKey] data; 214 215 static ModelMap!(TKey, TValue) fromJSONArray(string key)(IModel model, VibeJSON data) { 216 auto result = new ModelMap!(TKey, TValue); 217 218 foreach (value; data) { 219 TValue v = new TValue(model.client, value); 220 result[__traits(getMember, v, key)] = v; 221 } 222 223 return result; 224 } 225 226 /// Set the key to a value. 227 TValue set(TKey key, TValue value) { 228 if (value is null) { 229 this.remove(key); 230 return null; 231 } 232 233 this.data[key] = value; 234 return value; 235 } 236 237 /// Return the value for a key. Throws an exception if the key does not exist 238 TValue get(TKey key) { 239 return this.data[key]; 240 } 241 242 /// Return the value for a key, or if it doesn't exist a specified default value 243 TValue get(TKey key, TValue def) { 244 if(this.has(key)) return this.get(key); 245 return def; 246 } 247 248 /// Removes a key. 249 void remove(TKey key) { 250 this.data.remove(key); 251 } 252 253 /// Returns true if the key exists within the mapping 254 bool has(TKey key) { 255 return (key in this.data) != null; 256 } 257 258 /// Indexing by key 259 TValue opIndex(TKey key) { 260 return this.get(key); 261 } 262 263 /// Indexing assignment 264 void opIndexAssign(TValue value, TKey key) { 265 this.set(key, value); 266 } 267 268 /// Returns the length of the mapping 269 size_t length() { 270 return this.data.length; 271 } 272 273 /// Returns a new mapping from a subset of keys 274 auto subset(TKey[] keysWanted) { 275 auto obj = new ModelMap!(TKey, TValue); 276 277 foreach (k; keysWanted) { 278 obj[k] = this.get(k); 279 } 280 281 return obj; 282 } 283 284 /** 285 Allows using a delegate to filter the keys/values of the mapping into a new 286 mapping. 287 288 Params: 289 f = a delegate which returns true if the passed in key/value matches 290 */ 291 auto filter(bool delegate(TKey, TValue) f) { 292 return this.subset(this.data.keys.filter!(k => f(k, this.get(k))).array); 293 } 294 295 /** 296 Allows using a delegate to filter the values of the mapping 297 298 Params: 299 f = a delegate which returns true if the passed in value matches. 300 */ 301 auto filter(bool delegate(TValue) f) { 302 return this.data.values.filter!(f); 303 } 304 305 /** 306 Allows applying a delegate over the values of the mapping 307 308 Params: 309 f = a delegate which is applied to each value in the mapping. 310 */ 311 auto each(void delegate(TValue) f) { 312 return this.data.values.each!(f); 313 } 314 315 /** 316 Returns a single value from the mapping, based on the return value of a 317 delegate 318 319 Params: 320 f = a delegate which returns true if the value passed in matches. 321 def = default value to return if nothing matches. 322 */ 323 TValue pick(bool delegate(TValue) f, TValue def=null) { 324 foreach (value; this.data.values) { 325 if (f(value)) { 326 return value; 327 } 328 } 329 return def; 330 } 331 332 /// Returns an array of keys from the mapping 333 auto keys() { 334 return this.data.keys; 335 } 336 337 /// Returns an array of values from the mapping 338 auto values() { 339 return this.data.values; 340 } 341 342 /// Return the set-union for an array of keys 343 TKey[] keyUnion(TKey[] other) { 344 return nWayUnion([this.keys, other]).array; 345 } 346 347 /// Allows applying an operation as a delegate to the ModelMap 348 int opApply(int delegate(ref TKey, ref TValue) dg) { 349 int result = 0; 350 foreach (a, b; this.data) { 351 result = dg(a, b); 352 if (result) break; 353 } 354 return result; 355 } 356 }