diff --git a/amxmodx/meta_api.cpp b/amxmodx/meta_api.cpp index 204f4069..f5fd25f5 100755 --- a/amxmodx/meta_api.cpp +++ b/amxmodx/meta_api.cpp @@ -414,6 +414,7 @@ int C_Spawn(edict_t *pent) VectorHolder.clear(); g_TrieHandles.clear(); + g_TrieSnapshotHandles.clear(); g_DataPackHandles.clear(); char map_pluginsfile_path[256]; diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj b/amxmodx/msvc10/amxmodx_mm.vcxproj index a030557a..5931e690 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj @@ -389,6 +389,7 @@ + diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters index 55bddbf8..089c5375 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters @@ -326,6 +326,9 @@ Header Files + + Header Files + diff --git a/amxmodx/sm_memtable.h b/amxmodx/sm_memtable.h new file mode 100644 index 00000000..d7e2d90d --- /dev/null +++ b/amxmodx/sm_memtable.h @@ -0,0 +1,170 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ +#define _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ + +#include +#include + +class BaseMemTable +{ +public: + BaseMemTable(unsigned int init_size) + { + membase = (unsigned char *)malloc(init_size); + size = init_size; + tail = 0; + } + ~BaseMemTable() + { + free(membase); + membase = NULL; + } +public: + /** + * Allocates 'size' bytes of memory. + * Optionally outputs the address through 'addr'. + * Returns an index >= 0 on success, < 0 on failure. + */ + int CreateMem(unsigned int addsize, void **addr) + { + int idx = (int)tail; + + while (tail + addsize >= size) { + size *= 2; + membase = (unsigned char *)realloc(membase, size); + } + + tail += addsize; + if (addr) + *addr = (void *)&membase[idx]; + + return idx; + } + + /** + * Given an index into the memory table, returns its address. + * Returns NULL if invalid. + */ + void *GetAddress(int index) + { + if (index < 0 || (unsigned int)index >= tail) + return NULL; + return &membase[index]; + } + + /** + * Scraps the memory table. For caching purposes, the memory + * is not freed, however subsequent calls to CreateMem() will + * begin at the first index again. + */ + void Reset() + { + tail = 0; + } + + inline unsigned int GetMemUsage() + { + return size; + } + + inline unsigned int GetActualMemUsed() + { + return tail; + } + +private: + unsigned char *membase; + unsigned int size; + unsigned int tail; +}; + +class BaseStringTable +{ +public: + BaseStringTable(unsigned int init_size) : m_table(init_size) + { + } +public: + /** + * Adds a string to the string table and returns its index. + */ + int AddString(const char *string) + { + return AddString(string, strlen(string)); + } + + /** + * Adds a string to the string table and returns its index. + */ + int AddString(const char *string, size_t length) + { + size_t len = length + 1; + int idx; + char *addr; + + idx = m_table.CreateMem(len, (void **)&addr); + memcpy(addr, string, length + 1); + return idx; + } + + /** + * Given an index into the string table, returns the associated string. + */ + inline const char *GetString(int str) + { + return (const char *)m_table.GetAddress(str); + } + + /** + * Scraps the string table. For caching purposes, the memory + * is not freed, however subsequent calls to AddString() will + * begin at the first index again. + */ + void Reset() + { + m_table.Reset(); + } + + /** + * Returns the parent BaseMemTable that this string table uses. + */ + inline BaseMemTable *GetMemTable() + { + return &m_table; + } +private: + BaseMemTable m_table; +}; + +#endif //_INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ + diff --git a/amxmodx/trie_natives.cpp b/amxmodx/trie_natives.cpp index c9347a3b..a5bac284 100644 --- a/amxmodx/trie_natives.cpp +++ b/amxmodx/trie_natives.cpp @@ -9,6 +9,7 @@ using namespace SourceMod; TrieHandles g_TrieHandles; +TrieHandles g_TrieSnapshotHandles; // native Trie:TrieCreate(); static cell AMX_NATIVE_CALL TrieCreate(AMX *amx, cell *params) @@ -304,6 +305,7 @@ static cell AMX_NATIVE_CALL TrieDeleteKey(AMX *amx, cell *params) return 1; } + //native TrieDestroy(&Trie:handle) static cell AMX_NATIVE_CALL TrieDestroy(AMX *amx, cell *params) { @@ -339,6 +341,108 @@ static cell AMX_NATIVE_CALL TrieGetSize(AMX *amx, cell *params) return t->map.elements(); } +static cell AMX_NATIVE_CALL TrieSnapshotCreate(AMX *amx, cell *params) +{ + CellTrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int index = g_TrieSnapshotHandles.create(); + TrieSnapshot *snapshot = g_TrieSnapshotHandles.lookup(index); + snapshot->length = t->map.elements(); + snapshot->keys = new int[snapshot->length]; + + size_t i = 0; + for (StringHashMap::iterator iter = t->map.iter(); !iter.empty(); iter.next(), i++) + { + snapshot->keys[i] = snapshot->strings.AddString(iter->key.chars(), iter->key.length()); + } + assert(i == snapshot->length); + + return static_cast(index); +} + +static cell AMX_NATIVE_CALL TrieSnapshotLength(AMX *amx, cell *params) +{ + TrieSnapshot *snapshot = g_TrieSnapshotHandles.lookup(params[1]); + + if (snapshot == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid snapshot handle provided (%d)", params[1]); + return 0; + } + + return snapshot->length; +} + +static cell AMX_NATIVE_CALL TrieSnapshotKeyBufferSize(AMX *amx, cell *params) +{ + TrieSnapshot *snapshot = g_TrieSnapshotHandles.lookup(params[1]); + + if (snapshot == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid snapshot handle provided (%d)", params[1]); + return 0; + } + + unsigned index = params[2]; + + if (index >= snapshot->length) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid index %d", index); + return 0; + } + + return strlen(snapshot->strings.GetString(snapshot->keys[index])) + 1; +} + +static cell AMX_NATIVE_CALL TrieSnapshotGetKey(AMX *amx, cell *params) +{ + TrieSnapshot *snapshot = g_TrieSnapshotHandles.lookup(params[1]); + + if (snapshot == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid snapshot handle provided (%d)", params[1]); + return 0; + } + + unsigned index = params[2]; + + if (index >= snapshot->length) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid index %d", index); + return 0; + } + + const char *str = snapshot->strings.GetString(snapshot->keys[index]); + return set_amxstring_utf8(amx, params[3], str, strlen(str), params[4] + 1); +} + +//native TrieSnapshotDestroy(&Snapshot:handle) +static cell AMX_NATIVE_CALL TrieSnapshotDestroy(AMX *amx, cell *params) +{ + cell *ptr = get_amxaddr(amx, params[1]); + + TrieSnapshot *t = g_TrieSnapshotHandles.lookup(*ptr); + + if (t == NULL) + { + return 0; + } + + if (g_TrieSnapshotHandles.destroy(*ptr)) + { + *ptr = 0; + return 1; + } + + return 0; +} + AMX_NATIVE_INFO trie_Natives[] = { { "TrieCreate", TrieCreate }, @@ -355,9 +459,14 @@ AMX_NATIVE_INFO trie_Natives[] = { "TrieDeleteKey", TrieDeleteKey }, { "TrieKeyExists", TrieKeyExists }, { "TrieDestroy", TrieDestroy }, - { "TrieGetSize", TrieGetSize }, + { "TrieSnapshotCreate", TrieSnapshotCreate }, + { "TrieSnapshotLength", TrieSnapshotLength }, + { "TrieSnapshotKeyBufferSize", TrieSnapshotKeyBufferSize }, + { "TrieSnapshotGetKey", TrieSnapshotGetKey }, + { "TrieSnapshotDestroy", TrieSnapshotDestroy }, + { NULL, NULL } }; diff --git a/amxmodx/trie_natives.h b/amxmodx/trie_natives.h index ca2911e4..2d192363 100644 --- a/amxmodx/trie_natives.h +++ b/amxmodx/trie_natives.h @@ -3,6 +3,7 @@ #include "amxmodx.h" #include "sm_stringhashmap.h" +#include "sm_memtable.h" #include "CVector.h" using namespace SourceMod; @@ -135,6 +136,22 @@ struct CellTrie StringHashMap map; }; +struct TrieSnapshot +{ + TrieSnapshot() + : strings(128) + { } + + size_t mem_usage() + { + return length * sizeof(int) + strings.GetMemTable()->GetMemUsage(); + } + + size_t length; + ke::AutoArray keys; + BaseStringTable strings; +}; + template class TrieHandles { @@ -208,6 +225,7 @@ public: extern TrieHandles g_TrieHandles; +extern TrieHandles g_TrieSnapshotHandles; extern AMX_NATIVE_INFO trie_Natives[]; #endif diff --git a/plugins/include/celltrie.inc b/plugins/include/celltrie.inc index c6424290..4b7ca0c3 100644 --- a/plugins/include/celltrie.inc +++ b/plugins/include/celltrie.inc @@ -8,6 +8,11 @@ enum Trie Invalid_Trie = 0 }; +enum Snapshot +{ + Invalid_Snapshot = 0 +}; + /** * Creates a hash map. A hash map is a container that can map strings (called * "keys") to arbitrary values (cells, arrays, or strings). Keys in a hash map @@ -156,3 +161,61 @@ native TrieDestroy(&Trie:handle); * @error Invalid Handle. */ native TrieGetSize(Trie:handle); + +/** + * Creates a snapshot of all keys in the map. If the map is changed after this + * call, the changes are not reflected in the snapshot. Keys are not sorted. + * + * @param handle Map handle. + * + * @return New map snapshot handle, which must be freed via TrieSnapshotDestroy(). + * @error Invalid handle. + */ +native Snapshot:TrieSnapshotCreate(Trie:handle); + +/** + * Returns the number of keys in a map snapshot. Note that this may be + * different from the size of the map, since the map can change after the + * snapshot of its keys was taken. + * + * @param handle Map snapshot. + * + * @return Number of keys. + * @error Invalid Handle. + */ +native TrieSnapshotLength(Snapshot:handle); + +/** + * Returns the buffer size required to store a given key. That is, it returns + * the length of the key plus one. + * + * @param handle Map snapshot. + * @param index Key index (starting from 0). + * + * @return Buffer size required to store the key string. + * @error Invalid Handle or index out of range. + */ +native TrieSnapshotKeyBufferSize(Snapshot:handle, index); + +/** + * Retrieves the key string of a given key in a map snapshot. + * + * @param handle Map snapshot. + * @param index Key index (starting from 0). + * @param buffer String buffer. + * @param maxlength Maximum buffer length. + * + * @return Number of bytes written to the buffer. + * @error Invalid Handle or index out of range. + */ +native TrieSnapshotGetKey(Snapshot:handle, index, buffer[], maxlength); + +/** + * Destroys a Map snapshot + * + * @param handle Map snapshot. + * + * @return True on success, false if the value was never set. + * @error Invalid Handle. + */ +native TrieSnapshotDestroy(&Snapshot:handle); diff --git a/plugins/testsuite/trietest.sma b/plugins/testsuite/trietest.sma index 61ea727d..6a43abaa 100644 --- a/plugins/testsuite/trietest.sma +++ b/plugins/testsuite/trietest.sma @@ -45,7 +45,7 @@ public trietest() ok = false; } } - + for (new i = 0; i < 100; i++) { formatex(key, charsmax(key), "K%dK", i); @@ -61,7 +61,7 @@ public trietest() ok = false; } } - + // Setting K42K without replace should fail. new value; if (TrieSetCell(t, "K42K", 999, false)) { ok = false; server_print("set trie K42K should fail"); } @@ -74,12 +74,12 @@ public trietest() { server_print("Map size mismatch (1), expected: %d got: %d", 100, TrieGetSize(t)); ok = false; } - + if (TrieGetCell(t, "cat", value)) { server_print("trie should not have a cat."); ok = false; } - + // Check that "K42K" is not a string or array. new array[32]; new string[32]; @@ -88,16 +88,16 @@ public trietest() { server_print("entry K42K should not be an array or string"); ok = false; } - + TrieClear(t); - + if (TrieGetSize(t) != 0) { server_print("Map size mismatch (2), expected: %d got: %d", 0, TrieGetSize(t)); ok = false; } - + TrieDestroy(t); - + if (ok) pass("Cell tests"); else @@ -112,7 +112,7 @@ public trietest() fail("Recycle handles"); ok = true; - + for (new i = 0; i < 100; i++) { static val[32]; @@ -120,7 +120,7 @@ public trietest() formatex(val, charsmax(val), "V%dV", i); TrieSetString(t, key, val); } - + for (new i = 0; i < 100; i++) { formatex(key, charsmax(key), "K%dK", i); @@ -128,7 +128,7 @@ public trietest() static exp[32]; formatex(exp, charsmax(exp), "V%dV", i); new size; - + if (!TrieGetString(t, key, val, charsmax(val), size)) { server_print("TrieGetString(%d, '%s', %s) failed", t, key, val); @@ -145,28 +145,28 @@ public trietest() ok = false; } } - + if (TrieGetCell(t, "K42K", value) || TrieGetArray(t, "K42K", array, sizeof(array))) { server_print("entry K42K should not be an array or string"); ok = false; } - + if (TrieGetString(t, "cat", string, charsmax(string))) { server_print("trie should not have a cat."); ok = false; } - + if (ok) pass("String tests"); else fail("String tests"); - + ok = true; new data[5] = { 93, 1, 2, 3, 4 }; - + if (!TrieSetArray(t, "K42K", data, sizeof data)) { server_print("K42K should be a string."); ok = false; @@ -175,7 +175,7 @@ public trietest() { server_print("K42K should be V42V."); ok = false; } - for (new i = 0; i < sizeof data; i++) + for (new i = 0; i < sizeof data; i++) { if (data[i] != array[i]) { @@ -186,7 +186,7 @@ public trietest() TrieGetString(t, "K42K", string, charsmax(string))) { server_print("entry K42K should not be an array or string"); ok = false; - } + } if (!TrieSetArray(t, "K42K", data, 1)) { server_print("couldn't set K42K to 1-entry array"); ok = false; @@ -199,15 +199,15 @@ public trietest() { server_print("array size mismatch (%d, expected %d)", value, 1); ok = false; } - + if (ok) pass("Array tests"); else fail("Array tests"); - - + + ok = true; - + // Remove "K42K". if (!TrieDeleteKey(t, "K42K")) { @@ -223,11 +223,11 @@ public trietest() { server_print("map should not have a K42K"); ok = false; } - + TrieDestroy(t); - + t = TrieCreate(); - + for (new i = 0; i < 1000; i++) { formatex(key, charsmax(key), "!%d!", i); @@ -246,16 +246,60 @@ public trietest() server_print("Key '%s' could not be deleted", key); ok = false; } } - + if (ok) pass("Exists/Delete"); else fail("Exists/Delete"); + ok = true; + TrieClear(t); + + if (TrieGetSize(t)) + { + server_print("size should be 0"); ok = false + } + + TrieSetString(t, "adventure", "time!"); + TrieSetString(t, "butterflies", "bees"); + TrieSetString(t, "egg", "egg"); + + new Snapshot:keys = TrieSnapshotCreate(t); + { + if (TrieSnapshotLength(keys) != 3) + { + server_print("trie snapshot length should be 3"); ok = false; + } + + new bool:found[3], len = TrieSnapshotLength(keys); + new buffer[32]; + for (new i = 0; i < len; i++) + { + new size = TrieSnapshotKeyBufferSize(keys, i); // Just to use it, otherwise you should use charsmax(buffer). + TrieSnapshotGetKey(keys, i, buffer, size); + + if (strcmp(buffer, "adventure") == 0) found[0] = true; + else if (strcmp(buffer, "butterflies") == 0) found[1] = true; + else if (strcmp(buffer, "egg") == 0) found[2] = true; + else { server_print("unexpected key: %s", buffer); ok = false; } + } + + if (!found[0] || !found[1] || !found[2]) + { + server_print("did not find all keys"); ok = false; + } + } + + TrieSnapshotDestroy(keys); TrieDestroy(t); + + if (ok) + pass("Snapshot"); + else + fail("Snapshot"); + done(); - }