diff --git a/core/logic/sm_memtable.h b/core/logic/sm_memtable.h index 8789e68eb..3926b2bc8 100644 --- a/core/logic/sm_memtable.h +++ b/core/logic/sm_memtable.h @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet : * ============================================================================= * SourceMod * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. @@ -120,13 +120,20 @@ public: */ int AddString(const char *string) { - size_t len = strlen(string) + 1; + 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); - strcpy(addr, string); - + memcpy(addr, string, length + 1); return idx; } @@ -139,7 +146,7 @@ public: } /** - * Scraps the string table. For caching purposes, the memory + * Scraps the string table. For caching purposes, the memory * is not freed, however subsequent calls to AddString() will * begin at the first index again. */ diff --git a/core/logic/smn_adt_trie.cpp b/core/logic/smn_adt_trie.cpp index f09e82a21..856202a79 100644 --- a/core/logic/smn_adt_trie.cpp +++ b/core/logic/smn_adt_trie.cpp @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet : * ============================================================================= * SourceMod * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. @@ -31,49 +31,157 @@ #include #include "common_logic.h" -#include +#include +#include +#include +#include "sm_memtable.h" HandleType_t htCellTrie; +HandleType_t htSnapshot; -enum TrieNodeType +enum EntryType { - TrieNode_Cell, - TrieNode_CellArray, - TrieNode_String, + EntryType_Cell, + EntryType_CellArray, + EntryType_String, }; -struct SmartTrieNode +class Entry { - SmartTrieNode() + struct ArrayInfo { - ptr = NULL; - type = TrieNode_Cell; - } - SmartTrieNode(const SmartTrieNode &obj) + size_t length; + size_t maxbytes; + + void *base() { + return this + 1; + } + }; + +public: + Entry() + : control_(0) { - type = obj.type; - ptr = obj.ptr; - data = obj.data; - data_len = obj.data_len; } - SmartTrieNode & operator =(const SmartTrieNode &src) + Entry(ke::Moveable other) { - type = src.type; - ptr = src.ptr; - data = src.data; - data_len = src.data_len; - return *this; + control_ = other->control_; + data_ = other->data_; + other->control_ = 0; } - TrieNodeType type; - cell_t *ptr; - cell_t data; - cell_t data_len; + + ~Entry() + { + free(raw()); + } + + void setCell(cell_t value) { + setType(EntryType_Cell); + data_ = value; + } + void setArray(cell_t *cells, size_t length) { + ArrayInfo *array = ensureArray(length * sizeof(cell_t)); + array->length = length; + memcpy(array->base(), cells, length * sizeof(cell_t)); + setTypeAndPointer(EntryType_CellArray, array); + } + void setString(const char *str) { + size_t length = strlen(str); + ArrayInfo *array = ensureArray(length + 1); + array->length = length; + strcpy((char *)array->base(), str); + setTypeAndPointer(EntryType_String, array); + } + + size_t arrayLength() const { + assert(isArray()); + return raw()->length; + } + cell_t *array() const { + assert(isArray()); + return reinterpret_cast(raw()->base()); + } + char *chars() const { + assert(isString()); + return reinterpret_cast(raw()->base()); + } + cell_t cell() const { + assert(isCell()); + return data_; + } + + bool isCell() const { + return type() == EntryType_Cell; + } + bool isArray() const { + return type() == EntryType_CellArray; + } + bool isString() const { + return type() == EntryType_String; + } + +private: + Entry(const Entry &other) KE_DELETE; + + ArrayInfo *ensureArray(size_t bytes) { + ArrayInfo *array = raw(); + if (array && array->maxbytes >= bytes) + return array; + array = (ArrayInfo *)realloc(array, bytes + sizeof(ArrayInfo)); + if (!array) + { + fprintf(stderr, "Out of memory!\n"); + abort(); + } + array->maxbytes = bytes; + return array; + } + + // Pointer and type are overlaid, so we have some accessors. + ArrayInfo *raw() const { + return reinterpret_cast(control_ & ~uintptr_t(0x3)); + } + void setType(EntryType aType) { + control_ = uintptr_t(raw()) | uintptr_t(aType); + assert(type() == aType); + } + void setTypeAndPointer(EntryType aType, ArrayInfo *ptr) { + // malloc() should guarantee 8-byte alignment at worst + assert((uintptr_t(ptr) & 0x3) == 0); + control_ = uintptr_t(ptr) | uintptr_t(aType); + assert(type() == aType); + } + EntryType type() const { + return (EntryType)(control_ & 0x3); + } + +private: + // Contains the bits for the type, and an array pointer, if one is set. + uintptr_t control_; + + // Contains data for cell-only entries. + cell_t data_; }; -struct CellTrie +struct CellTrie : public ke::Refcounted { - KTrie trie; - cell_t mem_usage; + 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; }; class TrieHelpers : @@ -84,28 +192,35 @@ public: //SMGlobalClass void OnSourceModAllInitialized() { htCellTrie = handlesys->CreateType("Trie", this, 0, NULL, NULL, g_pCoreIdent, NULL); + htSnapshot = handlesys->CreateType("TrieSnapshot", this, 0, NULL, NULL, g_pCoreIdent, NULL); } void OnSourceModShutdown() { + handlesys->RemoveType(htSnapshot, g_pCoreIdent); handlesys->RemoveType(htCellTrie, g_pCoreIdent); } public: //IHandleTypeDispatch - static void DestroySmartTrieNode(SmartTrieNode *pNode) - { - free(pNode->ptr); - } void OnHandleDestroy(HandleType_t type, void *object) { - CellTrie *pTrie = (CellTrie *)object; - - pTrie->trie.run_destructor(DestroySmartTrieNode); - - delete pTrie; + if (type == htCellTrie) + { + CellTrie *pTrie = (CellTrie *)object; + pTrie->Release(); + } else { + TrieSnapshot *snapshot = (TrieSnapshot *)object; + delete snapshot; + } } bool GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize) { - CellTrie *pArray = (CellTrie *)object; - *pSize = sizeof(CellTrie) + pArray->mem_usage + pArray->trie.mem_usage(); + if (type == htCellTrie) + { + CellTrie *pArray = (CellTrie *)object; + *pSize = sizeof(CellTrie) + pArray->map.mem_usage(); + } else { + TrieSnapshot *snapshot = (TrieSnapshot *)object; + *pSize = sizeof(TrieSnapshot) + snapshot->mem_usage(); + } return true; } } s_CellTrieHelpers; @@ -115,8 +230,6 @@ static cell_t CreateTrie(IPluginContext *pContext, const cell_t *params) CellTrie *pTrie = new CellTrie; Handle_t hndl; - pTrie->mem_usage = 0; - if ((hndl = handlesys->CreateHandle(htCellTrie, pTrie, pContext->GetIdentity(), g_pCoreIdent, NULL)) == BAD_HANDLE) { @@ -127,67 +240,13 @@ static cell_t CreateTrie(IPluginContext *pContext, const cell_t *params) return hndl; } -static void UpdateNodeCells(CellTrie *pTrie, SmartTrieNode *pData, const cell_t *cells, cell_t num_cells) -{ - if (num_cells == 1) - { - pData->data = *cells; - pData->type = TrieNode_Cell; - } - else - { - pData->type = TrieNode_CellArray; - if (pData->ptr == NULL) - { - pData->ptr = (cell_t *)malloc(num_cells * sizeof(cell_t)); - pData->data_len = num_cells; - pTrie->mem_usage += (pData->data_len * sizeof(cell_t)); - } - else if (pData->data_len < num_cells) - { - pData->ptr = (cell_t *)realloc(pData->ptr, num_cells * sizeof(cell_t)); - pTrie->mem_usage += (num_cells - pData->data_len) * sizeof(cell_t); - pData->data_len = num_cells; - } - if (num_cells != 0) - { - memcpy(pData->ptr, cells, sizeof(cell_t) * num_cells); - } - pData->data = num_cells; - } -} - -static void UpdateNodeString(CellTrie *pTrie, SmartTrieNode *pData, const char *str) -{ - size_t len = strlen(str); - cell_t num_cells = (len + sizeof(cell_t)) / sizeof(cell_t); - - if (pData->ptr == NULL) - { - pData->ptr = (cell_t *)malloc(num_cells * sizeof(cell_t)); - pData->data_len = num_cells; - pTrie->mem_usage += (pData->data_len * sizeof(cell_t)); - } - else if (pData->data_len < num_cells) - { - pData->ptr = (cell_t *)realloc(pData->ptr, num_cells * sizeof(cell_t)); - pTrie->mem_usage += (num_cells - pData->data_len) * sizeof(cell_t); - pData->data_len = num_cells; - } - - strcpy((char *)pData->ptr, str); - pData->data = len; - pData->type = TrieNode_String; -} - static cell_t SetTrieValue(IPluginContext *pContext, const cell_t *params) { - Handle_t hndl; CellTrie *pTrie; HandleError err; HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); - hndl = params[1]; + Handle_t hndl = params[1]; if ((err = handlesys->ReadHandle(hndl, htCellTrie, &sec, (void **)&pTrie)) != HandleError_None) @@ -198,32 +257,29 @@ static cell_t SetTrieValue(IPluginContext *pContext, const cell_t *params) char *key; pContext->LocalToString(params[2], &key); - SmartTrieNode *pNode; - if ((pNode = pTrie->trie.retrieve(key)) == NULL) + StringHashMap::Insert i = pTrie->map.findForAdd(key); + if (!i.found()) { - SmartTrieNode node; - UpdateNodeCells(pTrie, &node, ¶ms[3], 1); - return pTrie->trie.insert(key, node) ? 1 : 0; + if (!pTrie->map.add(i, key)) + return 0; + i->value.setCell(params[3]); + return 1; } if (!params[4]) - { return 0; - } - - UpdateNodeCells(pTrie, pNode, ¶ms[3], 1); + i->value.setCell(params[3]); return 1; } static cell_t SetTrieArray(IPluginContext *pContext, const cell_t *params) { - Handle_t hndl; CellTrie *pTrie; HandleError err; HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); - hndl = params[1]; + Handle_t hndl = params[1]; if ((err = handlesys->ReadHandle(hndl, htCellTrie, &sec, (void **)&pTrie)) != HandleError_None) @@ -241,37 +297,30 @@ static cell_t SetTrieArray(IPluginContext *pContext, const cell_t *params) pContext->LocalToString(params[2], &key); pContext->LocalToPhysAddr(params[3], &array); - SmartTrieNode *pNode; - if ((pNode = pTrie->trie.retrieve(key)) == NULL) + StringHashMap::Insert i = pTrie->map.findForAdd(key); + if (!i.found()) { - SmartTrieNode node; - UpdateNodeCells(pTrie, &node, array, params[4]); - if (!pTrie->trie.insert(key, node)) - { - free(node.ptr); + if (!pTrie->map.add(i, key)) return 0; - } + i->key = key; + i->value.setArray(array, params[4]); return 1; } if (!params[4]) - { return 0; - } - - UpdateNodeCells(pTrie, pNode, array, params[4]); + i->value.setArray(array, params[4]); return 1; } static cell_t SetTrieString(IPluginContext *pContext, const cell_t *params) { - Handle_t hndl; CellTrie *pTrie; HandleError err; HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); - hndl = params[1]; + Handle_t hndl = params[1]; if ((err = handlesys->ReadHandle(hndl, htCellTrie, &sec, (void **)&pTrie)) != HandleError_None) @@ -283,37 +332,29 @@ static cell_t SetTrieString(IPluginContext *pContext, const cell_t *params) pContext->LocalToString(params[2], &key); pContext->LocalToString(params[3], &val); - SmartTrieNode *pNode; - if ((pNode = pTrie->trie.retrieve(key)) == NULL) + StringHashMap::Insert i = pTrie->map.findForAdd(key); + if (!i.found()) { - SmartTrieNode node; - UpdateNodeString(pTrie, &node, val); - if (!pTrie->trie.insert(key, node)) - { - free(node.ptr); + if (!pTrie->map.add(i, key)) return 0; - } + i->value.setString(val); return 1; } if (!params[4]) - { return 0; - } - - UpdateNodeString(pTrie, pNode, val); + i->value.setString(val); return 1; } static cell_t RemoveFromTrie(IPluginContext *pContext, const cell_t *params) { - Handle_t hndl; CellTrie *pTrie; HandleError err; HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); - hndl = params[1]; + Handle_t hndl = params[1]; if ((err = handlesys->ReadHandle(hndl, htCellTrie, &sec, (void **)&pTrie)) != HandleError_None) @@ -324,16 +365,12 @@ static cell_t RemoveFromTrie(IPluginContext *pContext, const cell_t *params) char *key; pContext->LocalToString(params[2], &key); - SmartTrieNode *pNode; - if ((pNode = pTrie->trie.retrieve(key)) == NULL) - { + StringHashMap::Result r = pTrie->map.find(key); + if (!r.found()) return 0; - } - free(pNode->ptr); - pNode->ptr = NULL; - - return pTrie->trie.remove(key) ? 1 : 0; + pTrie->map.remove(r); + return 1; } static cell_t ClearTrie(IPluginContext *pContext, const cell_t *params) @@ -351,9 +388,7 @@ static cell_t ClearTrie(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); } - pTrie->trie.run_destructor(TrieHelpers::DestroySmartTrieNode); - pTrie->trie.clear(); - + pTrie->map.clear(); return 1; } @@ -377,15 +412,22 @@ static cell_t GetTrieValue(IPluginContext *pContext, const cell_t *params) pContext->LocalToString(params[2], &key); pContext->LocalToPhysAddr(params[3], &pValue); - SmartTrieNode *pNode; - if ((pNode = pTrie->trie.retrieve(key)) == NULL) - { + StringHashMap::Result r = pTrie->map.find(key); + if (!r.found()) return 0; + + if (r->value.isCell()) + { + *pValue = r->value.cell(); + return 1; } - if (pNode->type == TrieNode_Cell) + // Maintain compatibility with an old bug. If an array was set with one + // cell, it was stored internally as a single cell. We now store as an + // actual array, but we make GetTrieValue() still work for this case. + if (r->value.isArray() && r->value.arrayLength() == 1) { - *pValue = pNode->data; + *pValue = r->value.array()[0]; return 1; } @@ -418,34 +460,29 @@ static cell_t GetTrieArray(IPluginContext *pContext, const cell_t *params) pContext->LocalToPhysAddr(params[3], &pValue); pContext->LocalToPhysAddr(params[5], &pSize); - SmartTrieNode *pNode; - if ((pNode = pTrie->trie.retrieve(key)) == NULL - || pNode->type != TrieNode_CellArray) - { - return 0; - } - if (pNode->ptr == NULL) + StringHashMap::Result r = pTrie->map.find(key); + if (!r.found() || !r->value.isArray()) + return 0; + + if (!r->value.array()) { *pSize = 0; return 1; } - if (pNode->data > params[4]) - { - *pSize = params[4]; - } - else if (params[4] != 0) - { - *pSize = pNode->data; - } - else - { + if (!params[4]) return 1; - } - memcpy(pValue, pNode->ptr, sizeof(cell_t) * pSize[0]); + size_t length = r->value.arrayLength(); + cell_t *base = r->value.array(); + if (length > size_t(params[4])) + *pSize = params[4]; + else + *pSize = length; + + memcpy(pValue, base, sizeof(cell_t) * pSize[0]); return 1; } @@ -474,25 +511,14 @@ static cell_t GetTrieString(IPluginContext *pContext, const cell_t *params) pContext->LocalToString(params[2], &key); pContext->LocalToPhysAddr(params[5], &pSize); - SmartTrieNode *pNode; - if ((pNode = pTrie->trie.retrieve(key)) == NULL - || pNode->type != TrieNode_String) - { + StringHashMap::Result r = pTrie->map.find(key); + if (!r.found() || !r->value.isString()) return 0; - } - - if (pNode->ptr == NULL) - { - *pSize = 0; - pContext->StringToLocal(params[3], params[4], ""); - return 1; - } size_t written; - pContext->StringToLocalUTF8(params[3], params[4], (char *)pNode->ptr, &written); + pContext->StringToLocalUTF8(params[3], params[4], r->value.chars(), &written); *pSize = (cell_t)written; - return 1; } @@ -511,7 +537,101 @@ static cell_t GetTrieSize(IPluginContext *pContext, const cell_t *params) return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); } - return pTrie->trie.size(); + return pTrie->map.elements(); +} + +static cell_t CreateTrieSnapshot(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + CellTrie *pTrie; + if ((err = handlesys->ReadHandle(hndl, htCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + TrieSnapshot *snapshot = new TrieSnapshot; + snapshot->length = pTrie->map.elements(); + snapshot->keys = new int[snapshot->length]; + size_t i = 0; + for (StringHashMap::iterator iter = pTrie->map.iter(); !iter.empty(); iter.next(), i++) + snapshot->keys[i] = snapshot->strings.AddString(iter->key.chars(), iter->key.length()); + assert(i == snapshot->length); + + if ((hndl = handlesys->CreateHandle(htSnapshot, snapshot, pContext->GetIdentity(), g_pCoreIdent, NULL)) + == BAD_HANDLE) + { + delete snapshot; + return BAD_HANDLE; + } + + return hndl; +} + +static cell_t TrieSnapshotLength(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + TrieSnapshot *snapshot; + if ((err = handlesys->ReadHandle(hndl, htSnapshot, &sec, (void **)&snapshot)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + return snapshot->length; +} + +static cell_t TrieSnapshotKeyBufferSize(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + TrieSnapshot *snapshot; + if ((err = handlesys->ReadHandle(hndl, htSnapshot, &sec, (void **)&snapshot)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + unsigned index = params[2]; + if (index >= snapshot->length) + return pContext->ThrowNativeError("Invalid index %d", index); + + return strlen(snapshot->strings.GetString(snapshot->keys[index])) + 1; +} + +static cell_t GetTrieSnapshotKey(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + TrieSnapshot *snapshot; + if ((err = handlesys->ReadHandle(hndl, htSnapshot, &sec, (void **)&snapshot)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + unsigned index = params[2]; + if (index >= snapshot->length) + return pContext->ThrowNativeError("Invalid index %d", index); + + size_t written; + const char *str = snapshot->strings.GetString(snapshot->keys[index]); + pContext->StringToLocalUTF8(params[3], params[4], str, &written); + return written; } REGISTER_NATIVES(trieNatives) @@ -526,5 +646,9 @@ REGISTER_NATIVES(trieNatives) {"SetTrieString", SetTrieString}, {"SetTrieValue", SetTrieValue}, {"GetTrieSize", GetTrieSize}, + {"CreateTrieSnapshot", CreateTrieSnapshot}, + {"TrieSnapshotLength", TrieSnapshotLength}, + {"TrieSnapshotKeyBufferSize", TrieSnapshotKeyBufferSize}, + {"GetTrieSnapshotKey", GetTrieSnapshotKey}, {NULL, NULL}, }; diff --git a/core/sm_memtable.h b/core/sm_memtable.h index 8789e68eb..3926b2bc8 100644 --- a/core/sm_memtable.h +++ b/core/sm_memtable.h @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet : * ============================================================================= * SourceMod * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. @@ -120,13 +120,20 @@ public: */ int AddString(const char *string) { - size_t len = strlen(string) + 1; + 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); - strcpy(addr, string); - + memcpy(addr, string, length + 1); return idx; } @@ -139,7 +146,7 @@ public: } /** - * Scraps the string table. For caching purposes, the memory + * Scraps the string table. For caching purposes, the memory * is not freed, however subsequent calls to AddString() will * begin at the first index again. */ diff --git a/plugins/include/adt_trie.inc b/plugins/include/adt_trie.inc index 1f588f67d..beced0199 100644 --- a/plugins/include/adt_trie.inc +++ b/plugins/include/adt_trie.inc @@ -1,5 +1,5 @@ /** - * vim: set ts=4 : + * vim: set ts=4 sw=4 tw=99 noet : * ============================================================================= * SourceMod (C)2004-2008 AlliedModders LLC. All rights reserved. * ============================================================================= @@ -36,35 +36,37 @@ #define _adt_trie_included /** - * Creates a Trie structure. A trie is a data storage object that maps any value to a - * string of text. It features very fast lookup and deletion, but grows very slow for - * insertion once tens of thousands of items are added. + * 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 + * are unique. That is, there is at most one entry in the map for a given key. * - * Keys in Tries are unique. That is, each key may only have one value. Unlike arrays, - * Tries cannot be iterated right now. Since the contents are known to be unique, to - * work around this, you can use ADT Arrays to store a list of keys known to be in a - * Trie. + * Insertion, deletion, and lookup in a hash map are all considered to be fast + * operations, amortized to O(1), or constant time. * - * @return New Trie Handle, which must be freed via CloseHandle(). + * The word "Trie" in this API is historical. As of SourceMod 1.6, tries have + * been internally replaced with hash tables, which have O(1) insertion time + * instead of O(n). + * + * @return New Map Handle, which must be freed via CloseHandle(). */ native Handle:CreateTrie(); /** - * Sets a value in a Trie, either inserting a new entry or replacing an old one. + * Sets a value in a hash map, either inserting a new entry or replacing an old one. * - * @param trie Trie Handle. + * @param map Map Handle. * @param key Key string. * @param value Value to store at this key. * @param replace If false, operation will fail if the key is already set. * @return True on success, false on failure. * @error Invalid Handle. */ -native bool:SetTrieValue(Handle:trie, const String:key[], any:value, bool:replace=true); +native bool:SetTrieValue(Handle:map, const String:key[], any:value, bool:replace=true); /** - * Sets an array value in a Trie, either inserting a new entry or replacing an old one. + * Sets an array value in a Map, either inserting a new entry or replacing an old one. * - * @param trie Trie Handle. + * @param map Map Handle. * @param key Key string. * @param array Array to store. * @param num_items Number of items in the array. @@ -72,36 +74,36 @@ native bool:SetTrieValue(Handle:trie, const String:key[], any:value, bool:replac * @return True on success, false on failure. * @error Invalid Handle. */ -native bool:SetTrieArray(Handle:trie, const String:key[], const any:array[], num_items, bool:replace=true); +native bool:SetTrieArray(Handle:map, const String:key[], const any:array[], num_items, bool:replace=true); /** - * Sets a string value in a Trie, either inserting a new entry or replacing an old one. + * Sets a string value in a Map, either inserting a new entry or replacing an old one. * - * @param trie Trie Handle. + * @param map Map Handle. * @param key Key string. * @param value String to store. * @param replace If false, operation will fail if the key is already set. * @return True on success, false on failure. * @error Invalid Handle. */ -native bool:SetTrieString(Handle:trie, const String:key[], const String:value[], bool:replace=true); +native bool:SetTrieString(Handle:map, const String:key[], const String:value[], bool:replace=true); /** - * Retrieves a value in a Trie. + * Retrieves a value in a Map. * - * @param trie Trie Handle. + * @param map Map Handle. * @param key Key string. * @param value Variable to store value. * @return True on success. False if the key is not set, or the key is set * as an array or string (not a value). * @error Invalid Handle. */ -native bool:GetTrieValue(Handle:trie, const String:key[], &any:value); +native bool:GetTrieValue(Handle:map, const String:key[], &any:value); /** - * Retrieves an array in a Trie. + * Retrieves an array in a Map. * - * @param trie Trie Handle. + * @param map Map Handle. * @param key Key string. * @param array Buffer to store array. * @param max_size Maximum size of array buffer. @@ -110,12 +112,12 @@ native bool:GetTrieValue(Handle:trie, const String:key[], &any:value); * as a value or string (not an array). * @error Invalid Handle. */ -native bool:GetTrieArray(Handle:trie, const String:key[], any:array[], max_size, &size=0); +native bool:GetTrieArray(Handle:map, const String:key[], any:array[], max_size, &size=0); /** - * Retrieves a string in a Trie. + * Retrieves a string in a Map. * - * @param trie Trie Handle. + * @param map Map Handle. * @param key Key string. * @param value Buffer to store value. * @param max_size Maximum size of string buffer. @@ -124,34 +126,75 @@ native bool:GetTrieArray(Handle:trie, const String:key[], any:array[], max_size, * as a value or array (not a string). * @error Invalid Handle. */ -native bool:GetTrieString(Handle:trie, const String:key[], String:value[], max_size, &size=0); +native bool:GetTrieString(Handle:map, const String:key[], String:value[], max_size, &size=0); /** - * Removes a key entry from a Trie. + * Removes a key entry from a Map. * - * @param trie Trie Handle. + * @param map Map Handle. * @param key Key string. * @return True on success, false if the value was never set. * @error Invalid Handle. */ -native RemoveFromTrie(Handle:trie, const String:key[]); +native RemoveFromTrie(Handle:map, const String:key[]); /** - * Clears all entries from a Trie. + * Clears all entries from a Map. * - * @param trie Trie Handle. + * @param map Map Handle. * @error Invalid Handle. */ -native ClearTrie(Handle:trie); +native ClearTrie(Handle:map); /** - * Retrieves the number of elements in a trie. + * Retrieves the number of elements in a map. * - * Note that trie items are not enumerable/iteratable. If you need to - * retrieve the elements in a trie, store its keys in an ADT Array. - * - * @param trie Trie Handle. + * @param map Map Handle. * @return Number of elements in the trie. * @error Invalid Handle. */ -native GetTrieSize(Handle:trie); +native GetTrieSize(Handle:map); + +/** + * 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 map Map Handle. + * @return New Map Snapshot Handle, which must be closed via CloseHandle(). + * @error Invalid Handle. + */ +native Handle:CreateTrieSnapshot(Handle:map); + +/** + * 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 snapshot Map snapshot. + * @return Number of keys. + * @error Invalid Handle. + */ +native TrieSnapshotLength(Handle:snapshot); + +/** + * Returns the buffer size required to store a given key. That is, it returns + * the length of the key plus one. + * + * @param snapshot 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(Handle:snapshot, index); + +/** + * Retrieves the key string of a given key in a map snapshot. + * + * @param snapshot 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 GetTrieSnapshotKey(Handle:snapshot, index, String:buffer[], maxlength); diff --git a/plugins/testsuite/tries.sp b/plugins/testsuite/tries.sp new file mode 100644 index 000000000..12bf8ca91 --- /dev/null +++ b/plugins/testsuite/tries.sp @@ -0,0 +1,162 @@ +// vim: set sts=2 ts=8 sw=2 tw=99 et ft=c : +#include + +public Plugin:myinfo = +{ + name = "Trie test", + author = "AlliedModders LLC", + description = "Trie tests", + version = "1.0.0.0", + url = "http://www.sourcemod.net/" +}; + +public OnPluginStart() +{ + RegServerCmd("test_tries", RunTests); +} + +public Action:RunTests(argc) +{ + new Handle:trie = CreateTrie(); + + for (new i = 0; i < 64; i++) { + new String:buffer[24]; + Format(buffer, sizeof(buffer), "%d", i); + + if (!SetTrieValue(trie, buffer, i)) + ThrowError("set trie to %d failed", i); + + new value; + if (!GetTrieValue(trie, buffer, value)) + ThrowError("get trie %d", i); + if (value != i) + ThrowError("get trie %d == %d", i, i); + } + + // Setting 17 without replace should fail. + new value; + if (SetTrieValue(trie, "17", 999, false)) + ThrowError("set trie 17 should fail"); + if (!GetTrieValue(trie, "17", value) || value != 17) + ThrowError("value at 17 not correct"); + if (!SetTrieValue(trie, "17", 999)) + ThrowError("set trie 17 = 999 should succeed"); + if (!GetTrieValue(trie, "17", value) || value != 999) + ThrowError("value at 17 not correct"); + + // Check size is 64. + if (GetTrieSize(trie) != 64) + ThrowError("trie size not 64"); + + // Check "cat" is not found. + new array[64]; + new String:string[64]; + if (GetTrieValue(trie, "cat", value) || + GetTrieArray(trie, "cat", array, sizeof(array)) || + GetTrieString(trie, "cat", string, sizeof(string))) + { + ThrowError("trie should not have a cat"); + } + + // Check that "17" is not a string or array. + if (GetTrieArray(trie, "17", array, sizeof(array)) || + GetTrieString(trie, "17", string, sizeof(string))) + { + ThrowError("entry 17 should not be an array or string"); + } + + // Strings. + if (!SetTrieString(trie, "17", "hellokitty")) + ThrowError("17 should be string"); + if (!GetTrieString(trie, "17", string, sizeof(string)) || + strcmp(string, "hellokitty") != 0) + { + ThrowError("17 should be hellokitty"); + } + if (GetTrieValue(trie, "17", value) || + GetTrieArray(trie, "17", array, sizeof(array))) + { + ThrowError("entry 17 should not be an array or string"); + } + + // Arrays. + new data[5] = { 93, 1, 2, 3, 4 }; + if (!SetTrieArray(trie, "17", data, 5)) + ThrowError("17 should be string"); + if (!GetTrieArray(trie, "17", array, sizeof(array))) + ThrowError("17 should be hellokitty"); + for (new i = 0; i < 5; i++) { + if (data[i] != array[i]) + ThrowError("17 slot %d should be %d, got %d", i, data[i], array[i]); + } + if (GetTrieValue(trie, "17", value) || + GetTrieString(trie, "17", string, sizeof(string))) + { + ThrowError("entry 17 should not be an array or string"); + } + + if (!SetTrieArray(trie, "17", data, 1)) + ThrowError("couldn't set 17 to 1-entry array"); + // Check that we fixed an old bug where 1-entry arrays where cells + if (!GetTrieArray(trie, "17", array, sizeof(array), value)) + ThrowError("couldn't fetch 1-entry array"); + if (value != 1) + ThrowError("array size mismatch (%d, expected %d)", value, 1); + // Check that we maintained backward compatibility. + if (!GetTrieValue(trie, "17", value)) + ThrowError("backwards compatibility failed"); + if (value != data[0]) + ThrowError("wrong value (%d, expected %d)", value, data[0]); + + // Remove "17". + if (!RemoveFromTrie(trie, "17")) + ThrowError("17 should have been removed"); + if (RemoveFromTrie(trie, "17")) + ThrowError("17 should not exist"); + if (GetTrieValue(trie, "17", value) || + GetTrieArray(trie, "17", array, sizeof(array)) || + GetTrieString(trie, "17", string, sizeof(string))) + { + ThrowError("trie should not have a 17"); + } + + ClearTrie(trie); + + if (GetTrieSize(trie)) + ThrowError("size should be 0"); + + SetTrieString(trie, "adventure", "time!"); + SetTrieString(trie, "butterflies", "bees"); + SetTrieString(trie, "egg", "egg"); + + new Handle:keys = CreateTrieSnapshot(trie); + { + if (TrieSnapshotLength(keys) != 3) + ThrowError("trie snapshot length should be 3"); + + new bool:found[3]; + for (new i = 0; i < TrieSnapshotLength(keys); i++) { + new size = TrieSnapshotKeyBufferSize(keys, i); + new String:buffer[size]; + GetTrieSnapshotKey(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 + ThrowError("unexpected key: %s", buffer); + } + + if (!found[0] || !found[1] || !found[2]) + ThrowError("did not find all keys"); + } + CloseHandle(keys); + + PrintToServer("All tests passed!"); + CloseHandle(trie); + return Plugin_Handled; +} + diff --git a/public/amtl/am-hashmap.h b/public/amtl/am-hashmap.h index bc913cdba..9a2d1ee62 100644 --- a/public/amtl/am-hashmap.h +++ b/public/amtl/am-hashmap.h @@ -60,12 +60,28 @@ class HashMap : public AllocPolicy Entry() { } + Entry(Moveable other) + : key(Moveable(other->key)), + value(Moveable(other->value)) + { + } Entry(const K &aKey, const V &aValue) : key(aKey), value(aValue) - { - } + { } + Entry(const K &aKey, Moveable aValue) + : key(aKey), + value(aValue) + { } + Entry(Moveable aKey, const V &aValue) + : key(aKey), + value(aValue) + { } + Entry(Moveable aKey, Moveable aValue) + : key(aKey), + value(aValue) + { } }; struct Policy @@ -124,6 +140,18 @@ class HashMap : public AllocPolicy bool add(Insert &i, const K &key, const V &value) { return table_.add(i, Entry(key, value)); } + bool add(Insert &i, Moveable key, const V &value) { + return table_.add(i, Entry(key, value)); + } + bool add(Insert &i, const K &key, Moveable value) { + return table_.add(i, Entry(key, value)); + } + bool add(Insert &i, Moveable key, Moveable value) { + return table_.add(i, Entry(key, value)); + } + bool add(Insert &i, Moveable key) { + return table_.add(i, Entry(key, V())); + } // This can be used to avoid compiler constructed temporaries, since AMTL // does not yet support move semantics. If you use this, the key and value @@ -140,6 +168,10 @@ class HashMap : public AllocPolicy table_.clear(); } + size_t elements() const { + return table_.elements(); + } + size_t estimateMemoryUse() const { return table_.estimateMemoryUse(); } diff --git a/public/amtl/am-hashtable.h b/public/amtl/am-hashtable.h index 18e743cbd..1101a2472 100644 --- a/public/amtl/am-hashtable.h +++ b/public/amtl/am-hashtable.h @@ -35,6 +35,7 @@ #include #include "am-allocator-policies.h" #include "am-utility.h" +#include "am-moveable.h" namespace ke { @@ -50,8 +51,16 @@ namespace detail { static const uint32_t kRemovedHash = 1; public: - void setHash(uint32_t hash, const T &t) { + void setHash(uint32_t hash) { hash_ = hash; + } + void construct() { + new (&t_) T(); + } + void construct(const T &t) { + new (&t_) T(t); + } + void construct(Moveable t) { new (&t_) T(t); } uint32_t hash() const { @@ -257,7 +266,8 @@ class HashTable : public AllocPolicy Entry &oldEntry = oldTable[i]; if (oldEntry.isLive()) { Insert p = insertUnique(oldEntry.hash()); - p.entry().setHash(p.hash(), oldEntry.payload()); + p.entry().setHash(p.hash()); + p.entry().construct(Moveable(oldEntry.payload())); } oldEntry.destruct(); } @@ -290,8 +300,8 @@ class HashTable : public AllocPolicy if (e->free()) break; if (e->isLive() && - e->sameHash(hash) && - HashPolicy::matches(key, e->payload())) + e->sameHash(hash) && + HashPolicy::matches(key, e->payload())) { return Result(e); } @@ -318,7 +328,7 @@ class HashTable : public AllocPolicy return Insert(e, hash); } - bool internalAdd(Insert &i, const Payload &payload) { + bool internalAdd(Insert &i) { assert(!i.found()); // If the entry is deleted, just re-use the slot. @@ -345,8 +355,8 @@ class HashTable : public AllocPolicy i = insertUnique(i.hash()); } - i.entry().setHash(i.hash(), payload); nelements_++; + i.entry().setHash(i.hash()); return true; } @@ -423,10 +433,22 @@ class HashTable : public AllocPolicy // The table must not have been mutated in between findForAdd() and add(). // The Insert object is still valid after add() returns, however. bool add(Insert &i, const Payload &payload) { - return internalAdd(i, payload); + if (!internalAdd(i)) + return false; + i.entry().construct(payload); + return true; + } + bool add(Insert &i, Moveable payload) { + if (!internalAdd(i)) + return false; + i.entry().construct(payload); + return true; } bool add(Insert &i) { - return internalAdd(i, Payload()); + if (!internalAdd(i)) + return false; + i.entry().construct(); + return true; } bool checkDensity() { @@ -445,6 +467,10 @@ class HashTable : public AllocPolicy nelements_ = 0; } + size_t elements() const { + return nelements_; + } + size_t estimateMemoryUse() const { return sizeof(Entry) * capacity_; } diff --git a/public/amtl/am-moveable.h b/public/amtl/am-moveable.h new file mode 100644 index 000000000..448fd652b --- /dev/null +++ b/public/amtl/am-moveable.h @@ -0,0 +1,66 @@ +// vim: set sts=8 ts=2 sw=2 tw=99 et: +// +// Copyright (C) 2013, David Anderson and AlliedModders LLC +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// * Neither the name of AlliedModders LLC nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +#ifndef _include_amtl_moveable_h_ +#define _include_amtl_moveable_h_ + +namespace ke { + +// This is a feature in C++11, but since AM projects do not have access to +// C++11 yet, we provide templates to implement move semantics. A class can +// provide a constructor for (ke::Moveable t) which containers will try +// to use. +// +// When implementing a constructor that takes a Moveable, the object being +// moved should be left in a state that is safe, since its destructor will +// be called even though it has been moved. + +template +struct Moveable +{ + public: + explicit Moveable(T &t) + : t_(t) + { + } + + T *operator ->() { + return &t_; + } + operator T &() { + return t_; + } + + private: + T &t_; +}; + +} // namespace ke + +#endif // _include_amtl_moveable_h_ diff --git a/public/amtl/am-string.h b/public/amtl/am-string.h index 76611f1ae..7be795f96 100644 --- a/public/amtl/am-string.h +++ b/public/amtl/am-string.h @@ -33,6 +33,7 @@ #include #include #include +#include namespace ke { @@ -54,6 +55,12 @@ class AString if (other.length_) set(other.chars_, other.length_); } + AString(Moveable other) + : chars_(other->chars_.take()), + length_(other->length_) + { + other->length_ = 0; + } AString &operator =(const char *str) { if (str && str[0]) { diff --git a/public/amtl/am-vector.h b/public/amtl/am-vector.h index 5facd517d..d6b6fc13d 100644 --- a/public/amtl/am-vector.h +++ b/public/amtl/am-vector.h @@ -34,6 +34,7 @@ #include #include #include +#include namespace ke { @@ -163,7 +164,7 @@ class Vector : public AllocPolicy if (newdata == NULL) return false; for (size_t i = 0; i < nitems_; i++) { - new (&newdata[i]) T(data_[i]); + new (&newdata[i]) T(Moveable(data_[i])); data_[i].~T(); } this->free(data_); diff --git a/public/sm_stringhashmap.h b/public/sm_stringhashmap.h index 5d5b305d4..39f9c0b03 100644 --- a/public/sm_stringhashmap.h +++ b/public/sm_stringhashmap.h @@ -47,6 +47,7 @@ #include #include #include +#include #include namespace SourceMod @@ -113,6 +114,7 @@ public: } typedef typename Internal::Result Result; + typedef typename Internal::Insert Insert; typedef typename Internal::iterator iterator; // Some KTrie-like helper functions. @@ -143,7 +145,7 @@ public: bool replace(const char *aKey, const T &value) { CharsAndLength key(aKey); - typename Internal::Insert i = internal_.findForAdd(key); + Insert i = internal_.findForAdd(key); if (!i.found()) { memory_used_ += key.length() + 1; @@ -158,7 +160,7 @@ public: bool insert(const char *aKey, const T &value) { CharsAndLength key(aKey); - typename Internal::Insert i = internal_.findForAdd(key); + Insert i = internal_.findForAdd(key); if (i.found()) return false; if (!internal_.add(i)) @@ -190,14 +192,45 @@ public: internal_.clear(); } - iterator iter() { + iterator iter() + { return internal_.iter(); } - size_t mem_usage() const { + size_t mem_usage() const + { return internal_.estimateMemoryUse() + memory_used_; } + size_t elements() const + { + return internal_.elements(); + } + + + Insert findForAdd(const char *aKey) + { + CharsAndLength key(aKey); + return internal_.findForAdd(key); + } + + // Note that |i->key| must be set after calling this, and the key must + // be the same as used with findForAdd(). It is best to avoid these two + // functions as the combined variants above are safer. + bool add(Insert &i) + { + return internal_.add(i); + } + + // Only value needs to be set after. + bool add(Insert &i, const char *aKey) + { + if (!internal_.add(i)) + return false; + i->key = aKey; + return true; + } + private: Internal internal_; size_t memory_used_;