core/vartable.h
| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | // This program is free software; you can redistribute it and/or modify | ||
| 2 | // it under the terms of the GNU General Public License as published by | ||
| 3 | // the Free Software Foundation; either version 2 of the License, or | ||
| 4 | // (at your option) any later version. | ||
| 5 | // | ||
| 6 | // This program is distributed in the hope that it will be useful, | ||
| 7 | // but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 8 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 9 | // GNU General Public License for more details. | ||
| 10 | // | ||
| 11 | // You should have received a copy of the GNU General Public License | ||
| 12 | // along with this program; if not, write to the Free Software | ||
| 13 | // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA, or visit | ||
| 14 | // http://www.gnu.org/copyleft/gpl.html . | ||
| 15 | // | ||
| 16 | // Linking Avisynth statically or dynamically with other modules is making a | ||
| 17 | // combined work based on Avisynth. Thus, the terms and conditions of the GNU | ||
| 18 | // General Public License cover the whole combination. | ||
| 19 | // | ||
| 20 | // As a special exception, the copyright holders of Avisynth give you | ||
| 21 | // permission to link Avisynth with independent modules that communicate with | ||
| 22 | // Avisynth solely through the interfaces defined in avisynth.h, regardless of the license | ||
| 23 | // terms of these independent modules, and to copy and distribute the | ||
| 24 | // resulting combined work under terms of your choice, provided that | ||
| 25 | // every copy of the combined work is accompanied by a complete copy of | ||
| 26 | // the source code of Avisynth (the version of Avisynth used to produce the | ||
| 27 | // combined work), being distributed under the terms of the GNU General | ||
| 28 | // Public License plus this exception. An independent module is a module | ||
| 29 | // which is not derived from or based on Avisynth, such as 3rd-party filters, | ||
| 30 | // import and export plugins, or graphical user interfaces. | ||
| 31 | |||
| 32 | |||
| 33 | #ifndef AVSCORE_VARTABLE_H | ||
| 34 | #define AVSCORE_VARTABLE_H | ||
| 35 | |||
| 36 | #include "strings.h" | ||
| 37 | #include "avs/alignment.h" | ||
| 38 | #include "avs/minmax.h" | ||
| 39 | #include <avisynth.h> | ||
| 40 | #include <unordered_map> | ||
| 41 | #include <mutex> | ||
| 42 | #include <iostream> | ||
| 43 | #include <cstring> | ||
| 44 | |||
| 45 | struct iequal_to_ascii | ||
| 46 | { | ||
| 47 | 25386 | bool operator()(const char* str1, const char* str2) const | |
| 48 | { | ||
| 49 | 25386 | return streqi(str1, str2); | |
| 50 | } | ||
| 51 | }; | ||
| 52 | |||
| 53 | struct ihash_ascii | ||
| 54 | { | ||
| 55 | 275693 | std::size_t operator()(const char* s) const | |
| 56 | { | ||
| 57 | // NOTE the connection between the hash() and equals() functions! | ||
| 58 | // In order for the hash table to work correctly, if two strings compare | ||
| 59 | // equal, they MUST have the same hash. | ||
| 60 | |||
| 61 | 275693 | size_t hash = 0; | |
| 62 |
2/2✓ Branch 4 → 3 taken 6699860 times.
✓ Branch 4 → 5 taken 275693 times.
|
6975553 | while (*s) |
| 63 | 6699860 | hash = hash * 101 + tolower(*s++); | |
| 64 | |||
| 65 | 275693 | return hash; | |
| 66 | } | ||
| 67 | }; | ||
| 68 | |||
| 69 | // Custom hash function for composite keys (const char* pointer + size_t length) | ||
| 70 | struct CompositeKeyDjb2Hash { | ||
| 71 | 489317 | size_t operator()(const std::pair<const char*, size_t>& key) const { | |
| 72 | 489317 | const char* str = key.first; | |
| 73 | 489317 | size_t len = key.second; | |
| 74 | 489317 | size_t hash = 5381; | |
| 75 | |||
| 76 |
2/2✓ Branch 4 → 3 taken 17968862 times.
✓ Branch 4 → 5 taken 489317 times.
|
18458179 | while (len--) { |
| 77 | 17968862 | hash = ((hash << 5) + hash) + *str; | |
| 78 | 17968862 | ++str; | |
| 79 | } | ||
| 80 | 489317 | return hash; | |
| 81 | } | ||
| 82 | }; | ||
| 83 | |||
| 84 | // Custom equality function for composite keys | ||
| 85 | struct CompositeKeyEqual { | ||
| 86 | 23557 | bool operator()(const std::pair<const char*, size_t>& lhs, const std::pair<const char*, size_t>& rhs) const { | |
| 87 |
2/4✓ Branch 2 → 3 taken 23557 times.
✗ Branch 2 → 5 not taken.
✓ Branch 3 → 4 taken 23557 times.
✗ Branch 3 → 5 not taken.
|
23557 | return (lhs.second == rhs.second) && (std::strncmp(lhs.first, rhs.first, lhs.second) == 0); |
| 88 | } | ||
| 89 | }; | ||
| 90 | |||
| 91 | // This doles out storage space for strings. No space is ever freed | ||
| 92 | // until the class instance is destroyed (which happens when a script | ||
| 93 | // file is closed). | ||
| 94 | class StringDump { | ||
| 95 | enum { BLOCK_SIZE = 32768 }; | ||
| 96 | char* current_block; | ||
| 97 | size_t block_pos, block_size; | ||
| 98 | |||
| 99 | // string cache | ||
| 100 | std::unordered_map<std::pair<const char*, size_t>, const char*, CompositeKeyDjb2Hash, CompositeKeyEqual> cache; | ||
| 101 | |||
| 102 | // Get the pointer to the string (if it exists) | ||
| 103 | 256437 | const char* get_string(const char* keyStr, size_t keyLen) { | |
| 104 |
1/2✓ Branch 3 → 4 taken 256437 times.
✗ Branch 3 → 12 not taken.
|
256437 | auto it = cache.find({ keyStr, keyLen }); |
| 105 |
2/2✓ Branch 6 → 7 taken 23557 times.
✓ Branch 6 → 9 taken 232880 times.
|
256437 | if (it != cache.end()) { |
| 106 | 23557 | return it->second; // Return existing pointer | |
| 107 | } | ||
| 108 | else { | ||
| 109 | 232880 | return nullptr; | |
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | 232880 | void add_string(const char* keyStr, size_t keyLen) { | |
| 114 |
1/2✓ Branch 3 → 4 taken 232880 times.
✗ Branch 3 → 5 not taken.
|
232880 | cache[{keyStr, keyLen}] = keyStr; |
| 115 | 232880 | } | |
| 116 | |||
| 117 | 232880 | void ensure_length(size_t len) | |
| 118 | { | ||
| 119 |
2/2✓ Branch 2 → 3 taken 453 times.
✓ Branch 2 → 6 taken 232427 times.
|
232880 | if (block_pos + len + 1 > block_size) { |
| 120 | 453 | char* new_block = new char[block_size = max(block_size, len + 1 + sizeof(char*))]; | |
| 121 | //_RPT0(0, "StringDump: Allocating new stringblock.\r\n"); | ||
| 122 | 453 | *(char**)new_block = current_block; // beginning of block holds pointer to previous block | |
| 123 | 453 | current_block = new_block; | |
| 124 | 453 | block_pos = sizeof(char*); | |
| 125 | } | ||
| 126 | 232880 | } | |
| 127 | |||
| 128 | public: | ||
| 129 | 453 | StringDump() : current_block(0), block_pos(BLOCK_SIZE), block_size(BLOCK_SIZE) {} | |
| 130 | |||
| 131 | 453 | ~StringDump() { | |
| 132 | _RPT0(0, "StringDump: DeAllocating all stringblocks.\r\n"); | ||
| 133 | 453 | char* p = current_block; | |
| 134 |
2/2✓ Branch 6 → 3 taken 453 times.
✓ Branch 6 → 7 taken 453 times.
|
906 | while (p) { |
| 135 | 453 | char* next = *(char**)p; | |
| 136 |
1/2✓ Branch 3 → 4 taken 453 times.
✗ Branch 3 → 5 not taken.
|
453 | delete[] p; |
| 137 | 453 | p = next; | |
| 138 | } | ||
| 139 | 453 | } | |
| 140 | |||
| 141 | // SaveString with len==-1 can store more than 'int' sized data | ||
| 142 | // up to size_t theoretically even on 32 bits. | ||
| 143 | // Public interface defines 'len' as int, this can we live with. | ||
| 144 | 256437 | char* SaveString(const char* s, int _len = -1, bool escape = false) { | |
| 145 |
2/2✓ Branch 2 → 3 taken 36 times.
✓ Branch 2 → 4 taken 256401 times.
|
256437 | size_t srclen = (_len == -1) ? strlen(s) : _len >= 0 ? static_cast<size_t>(_len) : 0; |
| 146 | size_t len; | ||
| 147 | |||
| 148 | 256437 | std::string ss; | |
| 149 |
1/2✗ Branch 6 → 7 not taken.
✓ Branch 6 → 39 taken 256437 times.
|
256437 | if (escape) { |
| 150 | ✗ | ss.reserve(srclen); // worst case. Note: input string may not be closed by 0x00 | |
| 151 | // PF: no need for WideString conversion, utf8 lower 128 ascii characters are freely searchable w/o conversion | ||
| 152 | ✗ | len = 0; | |
| 153 | ✗ | for (size_t i = 0; s[i] && i<srclen; ++i, ++len) { | |
| 154 | ✗ | if (s[i] == '\\') { | |
| 155 | ✗ | switch (s[i + 1]) { | |
| 156 | ✗ | case 'n': ss += '\n'; ++i; continue; | |
| 157 | ✗ | case 'r': ss += '\r'; ++i; continue; | |
| 158 | ✗ | case 't': ss += '\t'; ++i; continue; | |
| 159 | ✗ | case '0': ss += '\0'; ++i; continue; | |
| 160 | ✗ | case 'a': ss += '\a'; ++i; continue; | |
| 161 | ✗ | case 'f': ss += '\f'; ++i; continue; | |
| 162 | ✗ | case '\\': ss += '\\'; ++i; continue; | |
| 163 | ✗ | case '\"': ss += '\"'; ++i; continue; | |
| 164 | ✗ | case '\'': ss += '\''; ++i; continue; | |
| 165 | ✗ | case 'b': ss += '\b'; ++i; continue; | |
| 166 | ✗ | case 'v': ss += '\v'; ++i; continue; | |
| 167 | } | ||
| 168 | } | ||
| 169 | ✗ | ss += s[i]; | |
| 170 | } | ||
| 171 | ✗ | len = ss.size(); | |
| 172 | ✗ | s = ss.c_str(); | |
| 173 | } | ||
| 174 | else { | ||
| 175 | 256437 | len = srclen; | |
| 176 | } | ||
| 177 | |||
| 178 | // check in cache content | ||
| 179 |
1/2✓ Branch 40 → 41 taken 256437 times.
✗ Branch 40 → 50 not taken.
|
256437 | const char* test_ptr = get_string(s, len); |
| 180 |
2/2✓ Branch 41 → 42 taken 23557 times.
✓ Branch 41 → 43 taken 232880 times.
|
256437 | if(test_ptr != nullptr) // same string found in cache |
| 181 | 23557 | return const_cast<char *>(test_ptr); | |
| 182 | |||
| 183 |
1/2✓ Branch 43 → 44 taken 232880 times.
✗ Branch 43 → 50 not taken.
|
232880 | ensure_length(len); |
| 184 | 232880 | char* result = current_block + block_pos; | |
| 185 | 232880 | memcpy(result, s, len); | |
| 186 | 232880 | result[len] = 0; | |
| 187 | 232880 | block_pos += AlignNumber(len + 1, sizeof(char*)); // Keep word-aligned | |
| 188 | |||
| 189 |
1/2✓ Branch 45 → 46 taken 232880 times.
✗ Branch 45 → 50 not taken.
|
232880 | add_string(result, len); // update cache |
| 190 | |||
| 191 | 232880 | return result; | |
| 192 | 256437 | } | |
| 193 | |||
| 194 | void Clear() { | ||
| 195 | if (current_block) { | ||
| 196 | // deallocate string blocks except the first one | ||
| 197 | while (char* p = *(char**)current_block) { | ||
| 198 | delete[] current_block; | ||
| 199 | current_block = p; | ||
| 200 | } | ||
| 201 | block_pos = sizeof(char*); | ||
| 202 | block_size = BLOCK_SIZE; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | }; | ||
| 206 | |||
| 207 | class VarFrame | ||
| 208 | { | ||
| 209 | typedef std::unordered_map<const char*, AVSValue, ihash_ascii, iequal_to_ascii> ValueMap; | ||
| 210 | ValueMap variables; | ||
| 211 | |||
| 212 | public: | ||
| 213 | 2718 | VarFrame() { | |
| 214 |
1/2✓ Branch 3 → 4 taken 2718 times.
✗ Branch 3 → 5 not taken.
|
2717 | variables.max_load_factor(0.8f); |
| 215 | 2718 | } | |
| 216 | |||
| 217 | // This method will not modify the *val argument if it returns false. | ||
| 218 | 7497 | bool Get(const char* name, AVSValue *val) const | |
| 219 | { | ||
| 220 |
1/2✓ Branch 2 → 3 taken 7497 times.
✗ Branch 2 → 12 not taken.
|
7497 | ValueMap::const_iterator v = variables.find(name); |
| 221 |
2/2✓ Branch 5 → 6 taken 1824 times.
✓ Branch 5 → 9 taken 5673 times.
|
7497 | if (v != variables.end()) |
| 222 | { | ||
| 223 |
1/2✓ Branch 7 → 8 taken 1824 times.
✗ Branch 7 → 12 not taken.
|
1824 | *val = v->second; |
| 224 | 1824 | return true; | |
| 225 | } | ||
| 226 | 5673 | return false; | |
| 227 | } | ||
| 228 | |||
| 229 | 268196 | bool Set(const char* name, const AVSValue& val) | |
| 230 | { | ||
| 231 |
2/4✓ Branch 2 → 3 taken 268196 times.
✗ Branch 2 → 11 not taken.
✓ Branch 3 → 4 taken 268196 times.
✗ Branch 3 → 9 not taken.
|
268196 | std::pair<ValueMap::iterator, bool> ret = variables.insert(ValueMap::value_type(name, val)); |
| 232 |
1/2✓ Branch 6 → 7 taken 268196 times.
✗ Branch 6 → 12 not taken.
|
268196 | ret.first->second = val; |
| 233 | 268196 | return ret.second; | |
| 234 | } | ||
| 235 | |||
| 236 | 453 | void Clear() | |
| 237 | { | ||
| 238 | 453 | variables.clear(); | |
| 239 | 453 | } | |
| 240 | }; | ||
| 241 | |||
| 242 | class VarStringFrame : public VarFrame | ||
| 243 | { | ||
| 244 | StringDump string_dump; | ||
| 245 | public: | ||
| 246 | 256437 | char* SaveString(const char* s, int len = -1, bool escape = false) { | |
| 247 | 256437 | return string_dump.SaveString(s, len, escape); | |
| 248 | } | ||
| 249 | |||
| 250 | 453 | void Clear() | |
| 251 | { | ||
| 252 | //string_dump.Clear(); // do not destroy string (it is destroyed at destructor) | ||
| 253 | 453 | VarFrame::Clear(); | |
| 254 | 453 | } | |
| 255 | }; | ||
| 256 | |||
| 257 | class ConcurrentVarStringFrame : protected VarStringFrame | ||
| 258 | { | ||
| 259 | // avoid write/read concurrency of global variables in runtime scripts in MT | ||
| 260 | mutable std::mutex var_mutex; | ||
| 261 | |||
| 262 | public: | ||
| 263 | // This method will not modify the *val argument if it returns false. | ||
| 264 | 3745 | bool Get(const char* name, AVSValue *val) const | |
| 265 | { | ||
| 266 |
1/2✓ Branch 2 → 3 taken 3745 times.
✗ Branch 2 → 10 not taken.
|
3745 | std::lock_guard<std::mutex> lock(var_mutex); // avoid concurrency for global variables |
| 267 |
1/2✓ Branch 3 → 4 taken 3745 times.
✗ Branch 3 → 8 not taken.
|
7490 | return VarFrame::Get(name, val); |
| 268 | 3745 | } | |
| 269 | |||
| 270 | 268177 | bool Set(const char* name, const AVSValue& val) | |
| 271 | { | ||
| 272 |
1/2✓ Branch 2 → 3 taken 268177 times.
✗ Branch 2 → 10 not taken.
|
268177 | std::lock_guard<std::mutex> lock(var_mutex); // avoid concurrency for global variables |
| 273 |
1/2✓ Branch 3 → 4 taken 268177 times.
✗ Branch 3 → 8 not taken.
|
536354 | return VarFrame::Set(name, val); |
| 274 | 268177 | } | |
| 275 | |||
| 276 | 256437 | char* SaveString(const char* s, int len = -1, bool escape = false) { | |
| 277 |
1/2✓ Branch 2 → 3 taken 256437 times.
✗ Branch 2 → 10 not taken.
|
256437 | std::lock_guard<std::mutex> lock(var_mutex); // avoid concurrency for global variables |
| 278 |
1/2✓ Branch 3 → 4 taken 256437 times.
✗ Branch 3 → 8 not taken.
|
512874 | return VarStringFrame::SaveString(s, len, escape); |
| 279 | 256437 | } | |
| 280 | |||
| 281 | 453 | void Clear() | |
| 282 | { | ||
| 283 |
1/2✓ Branch 2 → 3 taken 453 times.
✗ Branch 2 → 6 not taken.
|
453 | std::lock_guard<std::mutex> lock(var_mutex); // avoid concurrency for global variables |
| 284 | 453 | VarStringFrame::Clear(); | |
| 285 | 453 | } | |
| 286 | }; | ||
| 287 | |||
| 288 | class VarTable | ||
| 289 | { | ||
| 290 | private: | ||
| 291 | ConcurrentVarStringFrame* topFrame; | ||
| 292 | |||
| 293 | std::vector<std::unique_ptr<VarFrame>> stackFrames; | ||
| 294 | std::vector<std::unique_ptr<VarStringFrame>> globalFrames; | ||
| 295 | |||
| 296 | std::vector<std::unique_ptr<VarFrame>> stackPool; | ||
| 297 | std::vector<std::unique_ptr<VarStringFrame>> globalPool; | ||
| 298 | |||
| 299 | public: | ||
| 300 | 2265 | VarTable(ConcurrentVarStringFrame* topFrame) : topFrame(topFrame) | |
| 301 | { | ||
| 302 |
1/2✓ Branch 6 → 7 taken 2265 times.
✗ Branch 6 → 8 not taken.
|
2265 | Push(); |
| 303 | 2265 | } | |
| 304 | |||
| 305 | 453 | void Clear() | |
| 306 | { | ||
| 307 | 453 | stackFrames.clear(); | |
| 308 | 453 | globalFrames.clear(); | |
| 309 | 453 | stackPool.clear(); | |
| 310 | 453 | globalPool.clear(); | |
| 311 | 453 | } | |
| 312 | |||
| 313 | 2265 | void Push() | |
| 314 | { | ||
| 315 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 9 taken 2265 times.
|
2265 | if (stackPool.size() > 0) { |
| 316 | ✗ | stackFrames.emplace_back(std::move(stackPool.back())); | |
| 317 | ✗ | stackPool.pop_back(); | |
| 318 | } | ||
| 319 | else { | ||
| 320 |
4/10✓ Branch 9 → 10 taken 2265 times.
✗ Branch 9 → 19 not taken.
✓ Branch 10 → 11 taken 2265 times.
✗ Branch 10 → 16 not taken.
✓ Branch 11 → 12 taken 2263 times.
✗ Branch 11 → 16 not taken.
✗ Branch 12 → 13 not taken.
✓ Branch 12 → 14 taken 2263 times.
✗ Branch 16 → 17 not taken.
✗ Branch 16 → 18 not taken.
|
2265 | stackFrames.emplace_back(new VarFrame()); |
| 321 | } | ||
| 322 | 2263 | } | |
| 323 | |||
| 324 | ✗ | void Pop() | |
| 325 | { | ||
| 326 | ✗ | assert(stackFrames.size() > 0); | |
| 327 | ✗ | stackFrames.back()->Clear(); | |
| 328 | ✗ | stackPool.emplace_back(std::move(stackFrames.back())); | |
| 329 | ✗ | stackFrames.pop_back(); | |
| 330 | ✗ | } | |
| 331 | |||
| 332 | ✗ | void PushGlobal() | |
| 333 | { | ||
| 334 | ✗ | Push(); | |
| 335 | ✗ | if (globalPool.size() > 0) { | |
| 336 | ✗ | globalFrames.emplace_back(std::move(globalPool.back())); | |
| 337 | ✗ | globalPool.pop_back(); | |
| 338 | } | ||
| 339 | else { | ||
| 340 | ✗ | globalFrames.emplace_back(new VarStringFrame()); | |
| 341 | } | ||
| 342 | ✗ | } | |
| 343 | |||
| 344 | ✗ | void PopGlobal() | |
| 345 | { | ||
| 346 | ✗ | Pop(); | |
| 347 | ✗ | assert(globalFrames.size() > 0); | |
| 348 | ✗ | globalFrames.back()->Clear(); | |
| 349 | ✗ | globalPool.emplace_back(std::move(globalFrames.back())); | |
| 350 | ✗ | globalFrames.pop_back(); | |
| 351 | ✗ | } | |
| 352 | |||
| 353 | 19 | bool Set(const char* name, const AVSValue& val) | |
| 354 | { | ||
| 355 | 19 | return stackFrames.back()->Set(name, val); | |
| 356 | } | ||
| 357 | |||
| 358 | 256852 | bool SetGlobal(const char* name, const AVSValue& val) | |
| 359 | { | ||
| 360 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 256852 times.
|
256852 | if (globalFrames.size() > 0) { |
| 361 | ✗ | return globalFrames.back()->Set(name, val); | |
| 362 | } | ||
| 363 | 256852 | return topFrame->Set(name, val); | |
| 364 | } | ||
| 365 | |||
| 366 | 3752 | bool Get(const char* name, AVSValue *val) const | |
| 367 | { | ||
| 368 |
5/6✓ Branch 3 → 4 taken 3752 times.
✗ Branch 3 → 9 not taken.
✓ Branch 7 → 8 taken 7 times.
✓ Branch 7 → 9 taken 3745 times.
✓ Branch 10 → 11 taken 7 times.
✓ Branch 10 → 12 taken 3745 times.
|
3752 | if (stackFrames.size() > 0 && stackFrames.back()->Get(name, val)) { |
| 369 | 7 | return true; | |
| 370 | } | ||
| 371 |
2/4✓ Branch 20 → 21 taken 3745 times.
✗ Branch 20 → 25 not taken.
✗ Branch 21 → 13 not taken.
✓ Branch 21 → 22 taken 3745 times.
|
3745 | for (auto it = globalFrames.rbegin(); it != globalFrames.rend(); ++it) { |
| 372 | ✗ | if ((**it).Get(name, val)) { | |
| 373 | ✗ | return true; | |
| 374 | } | ||
| 375 | } | ||
| 376 | 3745 | return topFrame->Get(name, val); | |
| 377 | } | ||
| 378 | |||
| 379 | 256437 | char* SaveString(const char* s, int len = -1, bool escape = false) | |
| 380 | { | ||
| 381 |
1/2✗ Branch 3 → 4 not taken.
✓ Branch 3 → 8 taken 256437 times.
|
256437 | if (globalFrames.size() > 0) { |
| 382 | ✗ | return globalFrames.back()->SaveString(s, len, escape); | |
| 383 | } | ||
| 384 | 256437 | return topFrame->SaveString(s, len, escape); | |
| 385 | } | ||
| 386 | }; | ||
| 387 | |||
| 388 | #endif // AVSCORE_VARTABLE_H | ||
| 389 |