diff --git a/amxmodx/Makefile b/amxmodx/Makefile index 8d1a5823..c4020bc5 100755 --- a/amxmodx/Makefile +++ b/amxmodx/Makefile @@ -20,7 +20,8 @@ OBJECTS = meta_api.cpp CFile.cpp CVault.cpp vault.cpp float.cpp file.cpp modules amxxfile.cpp CLang.cpp md5.cpp emsg.cpp CForward.cpp CPlugin.cpp CModule.cpp \ CMenu.cpp util.cpp amx.cpp amxdbg.cpp natives.cpp newmenus.cpp debugger.cpp \ optimizer.cpp format.cpp messages.cpp libraries.cpp vector.cpp sorting.cpp \ - amxmod_compat.cpp nongpl_matches.cpp CFlagManager.cpp datastructs.cpp + amxmod_compat.cpp nongpl_matches.cpp CFlagManager.cpp datastructs.cpp \ + trie_natives.cpp LINK = -lgcc -static-libgcc diff --git a/amxmodx/meta_api.cpp b/amxmodx/meta_api.cpp index d9757853..dd058b0a 100755 --- a/amxmodx/meta_api.cpp +++ b/amxmodx/meta_api.cpp @@ -51,7 +51,7 @@ #include "datastructs.h" #include "CFlagManager.h" #include "svn_version.h" - +#include "trie_natives.h" plugin_info_t Plugin_info = { @@ -412,6 +412,7 @@ int C_Spawn(edict_t *pent) }; VectorHolder.clear(); + g_TrieHandles.clear(); char map_pluginsfile_path[256]; char prefixed_map_pluginsfile[256]; char configs_dir[256]; diff --git a/amxmodx/modules.cpp b/amxmodx/modules.cpp index 11d6a4dd..4bcd4ae2 100755 --- a/amxmodx/modules.cpp +++ b/amxmodx/modules.cpp @@ -49,6 +49,7 @@ #include "libraries.h" #include "messages.h" #include "amxmod_compat.h" +#include "trie_natives.h" CList g_modules; CList g_loadedscripts; @@ -579,6 +580,7 @@ int set_amxnatives(AMX* amx, char error[128]) amx_Register(amx, vector_Natives, -1); amx_Register(amx, g_SortNatives, -1); amx_Register(amx, g_DataStructNatives, -1); + amx_Register(amx, trie_Natives, -1); if (amx->flags & AMX_FLAG_OLDFILE) { diff --git a/amxmodx/trie_natives.cpp b/amxmodx/trie_natives.cpp new file mode 100644 index 00000000..1359c626 --- /dev/null +++ b/amxmodx/trie_natives.cpp @@ -0,0 +1,316 @@ +#include +#include +#include + +#include "amxmodx.h" +#include "sm_trie_tpl.h" +#include "trie_natives.h" + +#ifndef NDEBUG +size_t trie_free_count = 0; +size_t trie_malloc_count = 0; +#endif + +TrieHandles g_TrieHandles; +typedef KTrie celltrie; + +void triedata_dtor(TrieData *ptr) +{ + ptr->freeCells(); +} +// native Trie:TrieCreate(); +static cell AMX_NATIVE_CALL TrieCreate(AMX *amx, cell *params) +{ + return static_cast(g_TrieHandles.create()); +} + +// native Trie::TrieClear(Trie:handle); +static cell AMX_NATIVE_CALL TrieClear(AMX *amx, cell *params) +{ + celltrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + return 0; + } + t->run_destructor(triedata_dtor); + t->clear(); + return 1; +} +// native TrieSetCell(Trie:handle, const key[], any:value); +static cell AMX_NATIVE_CALL TrieSetCell(AMX *amx, cell *params) +{ + celltrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + return 0; + } + + TrieData *td = NULL; + int len; + const char *key = get_amxstring(amx, params[2], 0, len); + + if ((td = t->retrieve(key)) == NULL) + { + TrieData dummy; + t->insert(key, dummy); + + td = t->retrieve(key); + + // should never, ever happen + if (td == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Couldn't KTrie::retrieve(), handle: %d", params[1]); + return 0; + } + } + + td->setCell(params[3]); + + return 1; +} +// native TrieSetString(Trie:handle, const key[], const data[]); +static cell AMX_NATIVE_CALL TrieSetString(AMX *amx, cell *params) +{ + celltrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + return 0; + } + + TrieData *td = NULL; + int len; + const char *key = get_amxstring(amx, params[2], 0, len); + + if ((td = t->retrieve(key)) == NULL) + { + TrieData dummy; + t->insert(key, dummy); + td = t->retrieve(key); + + // should never, ever happen + if (td == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Couldn't KTrie::retrieve(), handle: %d", params[1]); + return 0; + } + + } + + td->setString(get_amxaddr(amx, params[3])); + return 1; +} +// native TrieSetArray(Trie:handle, const key[], const any:buffer[], buffsize) +static cell AMX_NATIVE_CALL TrieSetArray(AMX *amx, cell *params) +{ + celltrie *t = g_TrieHandles.lookup(params[2]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + return 0; + } + + TrieData *td = NULL; + int len; + const char *key = get_amxstring(amx, params[2], 0, len); + + if ((td = t->retrieve(key)) == NULL) + { + TrieData dummy; + t->insert(key, dummy); + td = t->retrieve(key); + + // should never, ever happen + if (td == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Couldn't KTrie::retrieve(), handle: %d", params[1]); + return 0; + } + + } + + td->setArray(get_amxaddr(amx, params[3]), params[4]); + + return 1; +} +// native bool:TrieGetCell(Trie:handle, const key[], &any:value); +static cell AMX_NATIVE_CALL TrieGetCell(AMX *amx, cell *params) +{ + celltrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + return 0; + } + + + TrieData *td = NULL; + int len; + const char *key = get_amxstring(amx, params[2], 0, len); + + if ((td = t->retrieve(key)) == NULL) + { + return 0; + } + cell *ptr = get_amxaddr(amx, params[3]); + if (!td->getCell(ptr)) + { + return 0; + } + return 1; +} +// native bool:TrieGetString(Trie:handle, const key[], buff[], len); +static cell AMX_NATIVE_CALL TrieGetString(AMX *amx, cell *params) +{ + celltrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + return 0; + } + + + TrieData *td = NULL; + int len; + const char *key = get_amxstring(amx, params[2], 0, len); + + if ((td = t->retrieve(key)) == NULL) + { + return 0; + } + cell *ptr = get_amxaddr(amx, params[3]); + if (!td->getString(ptr, params[4])) + { + return 0; + } + return 1; +} +// native bool:TrieGetArray(Trie:handle, const key[], any:buff[], len); +static cell AMX_NATIVE_CALL TrieGetArray(AMX *amx, cell *params) +{ + celltrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + return 0; + } + + + TrieData *td = NULL; + int len; + const char *key = get_amxstring(amx, params[2], 0, len); + + if ((td = t->retrieve(key)) == NULL) + { + return 0; + } + cell *ptr = get_amxaddr(amx, params[3]); + if (!td->getArray(ptr, params[4])) + { + return 0; + } + return 1; +} +// native bool:TrieKeyExists(Trie:handle, const key[]); +static cell AMX_NATIVE_CALL TrieKeyExists(AMX *amx, cell *params) +{ + celltrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + return 0; + } + + + int len; + const char *key = get_amxstring(amx, params[2], 0, len); + return t->retrieve(key) != NULL ? 1 : 0; +} + +// native bool:TrieDeleteKey(Trie:handle, const key[]); +static cell AMX_NATIVE_CALL TrieDeleteKey(AMX *amx, cell *params) +{ + celltrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + return 0; + } + + + int len; + const char *key = get_amxstring(amx, params[2], 0, len); + TrieData *td = t->retrieve(key); + + if (td != NULL) + { + td->freeCells(); + } + return t->remove(key) ? 1 : 0; +} +//native TrieDestroy(&Trie:handle) +static cell AMX_NATIVE_CALL TrieDestroy(AMX *amx, cell *params) +{ + cell *ptr = get_amxaddr(amx, params[1]); + + celltrie *t = g_TrieHandles.lookup(*ptr); + + if (t == NULL) + { + return 0; + } + t->run_destructor(triedata_dtor); + if (g_TrieHandles.destroy(*ptr)) + { + *ptr = 0; + return 1; + } + return 0; + +} +#ifndef NDEBUG +static cell AMX_NATIVE_CALL TrieMallocCount(AMX *amx, cell *params) +{ + return trie_malloc_count; +} +static cell AMX_NATIVE_CALL TrieFreeCount(AMX *amx, cell *params) +{ + return trie_free_count; +} +#endif +AMX_NATIVE_INFO trie_Natives[] = +{ + { "TrieCreate", TrieCreate }, + { "TrieClear", TrieClear }, + + { "TrieSetCell", TrieSetCell }, + { "TrieSetString", TrieSetString }, + { "TrieSetArray", TrieSetArray }, + + { "TrieGetCell", TrieGetCell }, + { "TrieGetString", TrieGetString }, + { "TrieGetArray", TrieGetArray }, + + { "TrieDeleteKey", TrieDeleteKey }, + { "TrieKeyExists", TrieKeyExists }, + { "TrieDestroy", TrieDestroy }, + +#ifndef NDEBUG + { "TrieMallocCount", TrieMallocCount }, + { "TrieFreeCount", TrieFreeCount }, +#endif + + { NULL, NULL } +}; + diff --git a/amxmodx/trie_natives.h b/amxmodx/trie_natives.h new file mode 100644 index 00000000..2bbc3412 --- /dev/null +++ b/amxmodx/trie_natives.h @@ -0,0 +1,211 @@ +#ifndef _TRIE_NATIVES_H_ +#define _TRIE_NATIVES_H_ + +#include "amxmodx.h" +#include "sm_trie_tpl.h" +#include "CVector.h" + +#define TRIE_DATA_UNSET 0 +#define TRIE_DATA_CELL 1 +#define TRIE_DATA_STRING 2 +#define TRIE_DATA_ARRAY 3 + +#ifndef NDEBUG +extern size_t trie_malloc_count; +extern size_t trie_free_count; +#endif + +class TrieData +{ +private: + cell *m_data; + cell m_cell; + cell m_cellcount; + int m_type; + + void needCells(cell cellcount) + { + if (m_cellcount < cellcount) + { + if (m_data != NULL) + { + free(m_data); +#ifndef NDEBUG + trie_free_count++; +#endif + } + size_t neededbytes = cellcount * sizeof(cell); + m_data = static_cast(malloc(neededbytes)); + +#ifndef NDEBUG + trie_malloc_count++; +#endif + m_cellcount = cellcount; + } + } +public: + void freeCells() + { + if (m_data) + { +#ifndef NDEBUG + trie_free_count++; +#endif + free(m_data); + m_data = NULL; + } + m_cellcount = 0; + } + TrieData() : m_data(NULL), m_cell(0), m_cellcount(0), m_type(TRIE_DATA_UNSET) { } + TrieData(const TrieData &src) : m_data(src.m_data), + m_cell(src.m_cell), + m_cellcount(src.m_cellcount), + m_type(src.m_type) { } + ~TrieData() { } + + int getType() { return m_type; } + + void setCell(cell value) + { + freeCells(); + + m_cell = value; + m_type = TRIE_DATA_CELL; + } + void setString(cell *value) + { + cell len = 0; + + cell *p = value; + + while (*p++ != 0) + { + len++; + } + len += 1; // zero terminator + needCells(len); + memcpy(m_data, value, sizeof(cell) * len); + + m_type = TRIE_DATA_STRING; + } + void setArray(cell *value, cell size) + { + if (size <= 0) + return; + + needCells(size); + memcpy(m_data, value, sizeof(cell) * size); + + m_type = TRIE_DATA_ARRAY; + } + bool getCell(cell *out) + { + if (m_type == TRIE_DATA_CELL) + { + *out = m_cell; + return true; + } + + return false; + } + bool getString(cell *out, cell max) + { + if (m_type == TRIE_DATA_STRING && max >= 0) + { + memcpy(out, m_data, (max > m_cellcount ? m_cellcount : max) * sizeof(cell)); + return true; + } + return false; + } + bool getArray(cell *out, cell max) + { + if (m_type == TRIE_DATA_ARRAY && max >= 0) + { + memcpy(out, m_data, (max > m_cellcount ? m_cellcount : max) * sizeof(cell)); + return true; + } + return false; + } + void clear() + { + freeCells(); + m_type = TRIE_DATA_UNSET; + } +}; + +class TrieHandles +{ +private: + CVector< KTrie< TrieData > *> m_tries; + +public: + TrieHandles() { } + ~TrieHandles() + { + this->clear(); + } + + void clear() + { + for (size_t i = 0; i < m_tries.size(); i++) + { + if (m_tries[i] != NULL) + { + delete m_tries[i]; + } + } + + m_tries.clear(); + } + KTrie *lookup(int handle) + { + handle--; + + if (handle < 0 || handle >= static_cast(m_tries.size())) + { + return NULL; + } + + return m_tries[handle]; + } + int create() + { + for (size_t i = 0; i < m_tries.size(); i++) + { + if (m_tries[i] == NULL) + { + // reuse handle + m_tries[i] = new KTrie; + + return static_cast(i) + 1; + } + } + m_tries.push_back(new KTrie); + return m_tries.size(); + } + bool destroy(int handle) + { + handle--; + + if (handle < 0 || handle >= static_cast(m_tries.size())) + { + return false; + } + + if (m_tries[handle] == NULL) + { + return false; + } + delete m_tries[handle]; + m_tries[handle] = NULL; + + return true; + } +}; + + +extern TrieHandles g_TrieHandles; +extern AMX_NATIVE_INFO trie_Natives[]; + +#endif + diff --git a/plugins/include/amxmodx.inc b/plugins/include/amxmodx.inc index bc5ff4ad..a2af76a5 100755 --- a/plugins/include/amxmodx.inc +++ b/plugins/include/amxmodx.inc @@ -22,6 +22,7 @@ #include #include #include +#include #include /* Function is called just after server activation. diff --git a/plugins/include/celltrie.inc b/plugins/include/celltrie.inc new file mode 100644 index 00000000..2300c6e9 --- /dev/null +++ b/plugins/include/celltrie.inc @@ -0,0 +1,26 @@ +#if defined _celltrie_included +#endinput +#endif +#define _celltrie_included + +enum Trie +{ + Invalid_Trie = 0 +}; + + +native Trie:TrieCreate(); +native TrieClear(Trie:handle); + +native TrieSetCell(Trie:handle, const key[], any:value); +native TrieSetString(Trie:handle, const key[], const value[]); +native TrieSetArray(Trie:handle, const key[], const any:buffer[], size); + +native bool:TrieGetCell(Trie:handle, const key[], &any:value); +native bool:TrieGetString(Trie:handle, const key[], output[], outputsize); +native bool:TrieGetArray(Trie:handle, const key[], any:output[], outputsize); + +native bool:TrieDeleteKey(Trie:handle, const key[]); +native bool:TrieKeyExists(Trie:handle, const key[]); +native TrieDestroy(&Trie:handle); + diff --git a/plugins/testsuite/arraytest.sma b/plugins/testsuite/arraytest.sma index 9af73355..609e6d71 100644 --- a/plugins/testsuite/arraytest.sma +++ b/plugins/testsuite/arraytest.sma @@ -102,6 +102,9 @@ public arraytest1() new Float:f; new Array:a=ArrayCreate(1); + if (a == Invalid_Array) + { + } for (new i=0; i<1000; i++) { f=float(i); diff --git a/plugins/testsuite/trietest.sma b/plugins/testsuite/trietest.sma new file mode 100644 index 00000000..42f122ac --- /dev/null +++ b/plugins/testsuite/trietest.sma @@ -0,0 +1,169 @@ +#include + + +// These natives are only available in a debug build of amxmodx +native TrieFreeCount(); +native TrieMallocCount(); + +new failcount = 0; +new passcount = 0; +public plugin_init() +{ + register_plugin("Trie Test", AMXX_VERSION_STR, "AMXX Dev Team"); + register_srvcmd("trietest", "trietest"); +} + +stock fail(const testname[]) +{ + server_print("[FAIL] %s", testname); + + failcount++; +} +stock pass(const testname[]) +{ + server_print("[PASS] %s", testname); + + passcount++; +} +stock done() +{ + server_print("Finished. %d tests, %d failed", failcount + passcount, failcount); +} +stock check_frees() +{ + if (TrieMallocCount() != TrieFreeCount()) + fail("free count == malloc count"); + + else + pass("free count == malloc count"); + + server_print("malloc count: %d free count: %d", TrieMallocCount(), TrieFreeCount()); +} +public trietest() +{ + failcount = 0; + passcount = 0; + + new bool:ok = true; + new Trie:t = TrieCreate(); + + new Trie:oldhandle = t; // Makes sure that the trie handle system recycles old handles + + new key[32]; + for (new i = 0; i < 100; i++) + { + formatex(key, charsmax(key), "K%dK", i); + TrieSetCell(t, key, i); + } + + for (new i = 0; i < 100; i++) + { + formatex(key, charsmax(key), "K%dK", i); + new val; + if (!TrieGetCell(t, key, val)) + { + server_print("TrieGetCell(%d, '%s', %d) failed", t, key, val); + ok = false; + } + + else if (val != i) + { + server_print("val mismatch, expected: %d got: %d", i, val); + ok = false; + } + + } + if (ok) + pass("Cell tests"); + + else + fail("Cell tests"); + + TrieClear(t); + TrieDestroy(t); + + t = TrieCreate(); + + if (t == oldhandle) + pass("Recycle handles"); + + else + fail("Recycle handles"); + + ok = true; + for (new i = 0; i < 100; i++) + { + static val[32]; + formatex(key, charsmax(key), "K%dK", i); + 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); + static val[32]; + static exp[32]; + formatex(exp, charsmax(exp), "V%dV", i); + if (!TrieGetString(t, key, val, charsmax(val))) + { + server_print("TrieGetString(%d, '%s', %s) failed", t, key, val); + ok = false; + } + + else if (!equal(val, exp)) + { + server_print("val mismatch, key: '%s' expected: '%s' got: '%s'", key, exp, val); + ok = false; + } + + } + if (ok) + pass("String tests"); + + else + fail("String tests"); + + TrieDestroy(t); + + check_frees(); + + t = TrieCreate(); + ok = true; + for (new i = 0; i < 1000; i++) + { + formatex(key, charsmax(key), "!%d!", i); + TrieSetString(t, key, key); + } + for (new i = 0; i < 1000; i++) + { + formatex(key, charsmax(key), "!%d!", i); + + if (!TrieKeyExists(t, key)) + { + ok = false; + server_print("Key '%s' does not exist", key); + } + else + { + if (!TrieDeleteKey(t, key)) + { + server_print("Key '%s' could not be deleted", key); + ok = false; + } + } + } + if (ok) + pass("Exists/Delete"); + + else + fail("Exists/Delete"); + + check_frees(); + TrieClear(t); + TrieDestroy(t); + check_frees(); + done(); + +} +