1 module discord.w.cache; 2 3 import std.algorithm; 4 import std.typecons; 5 import std.traits; 6 7 package alias Identity(alias A) = A; 8 9 // TODO: implement redis or write to disk 10 11 class SimpleCache(T, string idMember = "id", size_t limit = size_t.max) 12 { 13 @safe: 14 alias IDType = typeof(__traits(getMember, T.init, idMember)); 15 16 private bool locked; 17 T[] entries; 18 size_t index; 19 20 private void putUnsafe(T data) 21 { 22 index = entries.length; 23 static if (limit != size_t.max) 24 { 25 if (index >= limit) 26 index = (index % limit); 27 else 28 entries.length++; 29 } 30 else 31 entries.length++; 32 entries[index] = data; 33 } 34 35 void put(T data) 36 { 37 if (locked) 38 assert(false, "Attempted to update cache while locked (deadlock)"); 39 synchronized (this) 40 { 41 auto id = __traits(getMember, data, idMember); 42 if (entries.canFind!((a) => __traits(getMember, a, idMember) == id)) 43 throw new Exception("Entry with this ID already in cache"); 44 putUnsafe(data); 45 } 46 } 47 48 bool has(IDType id) 49 { 50 if (locked) 51 assert(false, "Attempted to update cache while locked (deadlock)"); 52 synchronized (this) 53 { 54 return entries.canFind!(a => __traits(getMember, a, idMember) == id); 55 } 56 } 57 58 /// Returns: the IDs which could not been found 59 IDType[] removeAll(IDType[] ids) 60 { 61 if (locked) 62 assert(false, "Attempted to update cache while locked (deadlock)"); 63 synchronized (this) 64 { 65 foreach_reverse (i, element; entries) 66 { 67 auto rm = ids.countUntil(__traits(getMember, element, idMember)); 68 if (rm != -1) 69 { 70 ids = ids.remove!(SwapStrategy.unstable)(rm); 71 entries = entries.remove(i); 72 } 73 } 74 return ids; 75 } 76 } 77 78 bool remove(IDType id) 79 { 80 if (locked) 81 assert(false, "Attempted to update cache while locked (deadlock)"); 82 synchronized (this) 83 { 84 auto index = entries.countUntil!(a => __traits(getMember, a, idMember) == id); 85 if (index == -1) 86 return false; 87 entries = entries.remove(index); 88 return true; 89 } 90 } 91 92 alias get = update; 93 94 T update(IDType id, scope void delegate(scope ref T) @safe updater = null, bool put = false) 95 { 96 if (locked) 97 assert(false, "Attempted to update cache while locked (deadlock)"); 98 synchronized (this) 99 { 100 auto index = entries.countUntil!(a => __traits(getMember, a, idMember) == id); 101 if (index == -1) 102 { 103 if (put) 104 { 105 T data; 106 __traits(getMember, data, idMember) = id; 107 updater(data); 108 putUnsafe(data); 109 return data; 110 } 111 else 112 { 113 debug (DumpCache) 114 { 115 import vibe.core.path : NativePath; 116 import vibe.core.file : writeFileUTF8; 117 import vibe.data.json : serializeToJsonString; 118 import vibe.core.log : logDebugV; 119 120 writeFileUTF8(NativePath("debug-" ~ T.stringof ~ "-cache.json"), 121 serializeToJsonString(entries)); 122 logDebugV("Dumped cache to debug-" ~ T.stringof ~ "-cache.json"); 123 } 124 else 125 { 126 import vibe.core.log : logDebugV; 127 128 logDebugV("Cache entries: %(%s, %)", entries.map!("a." ~ idMember)); 129 } 130 throw new Exception("Tried to update non existant cache entry"); 131 } 132 } 133 if (updater) 134 { 135 locked = true; 136 updater(entries[index]); 137 locked = false; 138 } 139 return entries[index]; 140 } 141 } 142 143 T patch(T value, bool put = false) 144 { 145 return update(__traits(getMember, value, idMember), (scope ref v) { 146 foreach (name; FieldNameTuple!T) 147 { 148 static if (is(typeof(__traits(getMember, value, name)) : Nullable!U, U)) 149 { 150 if (!__traits(getMember, value, name).isNull) 151 __traits(getMember, v, name) = __traits(getMember, value, name); 152 } 153 else static if (isArray!(typeof(__traits(getMember, value, name)))) 154 { 155 if (__traits(getMember, value, name).length) 156 __traits(getMember, v, name) = __traits(getMember, value, name); 157 } 158 else static if (__traits(compiles, __traits(getMember, value, name) == null)) 159 { 160 if (__traits(getMember, value, name) != null) 161 __traits(getMember, v, name) = __traits(getMember, value, name); 162 } 163 else 164 __traits(getMember, v, name) = __traits(getMember, value, name); 165 } 166 }, put); 167 } 168 }