From 952d12e7bc555a0048e8df0a6e6ed477c5a8af9d Mon Sep 17 00:00:00 2001 From: KitRifty Date: Wed, 12 Jul 2023 00:12:11 -0700 Subject: [PATCH 1/4] Add new IntHashMap and IntMap natives This is a continuation of #579. [HashMap] Adds new IntHashMap and IntMap natives. This patch makes the following changes * Refactors StringHashMap to a generic HashMap template * Adds the IntHashMap class * Adds new IntMap natives * Adds IntMap tests to the tries test suite [HashMap] Reverted rename of CharsAndLength [HashMap] Use more descriptive template names [HashMap] Removed old-style natives [HashMap] Removed IntHash class [HashMap] Reverted some search & replace errors Co-authored-by: Geoffrey McRae --- core/ConCmdManager.h | 2 +- core/ConVarManager.h | 2 +- core/ConsoleDetours.h | 2 +- core/CoreConfig.h | 2 +- core/HalfLife2.h | 2 +- core/UserMessages.h | 2 +- core/logic/ADTFactory.h | 2 +- core/logic/AdminCache.h | 2 +- core/logic/GameConfigs.h | 2 +- core/logic/Native.h | 2 +- core/logic/PluginSys.h | 2 +- core/logic/ShareSys.h | 2 +- core/logic/Translator.h | 2 +- core/logic/smn_adt_trie.cpp | 525 +++++++++++++++++++- extensions/sdktools/extension.h | 2 +- plugins/include/adt_trie.inc | 119 +++++ plugins/testsuite/tries.sp | 141 ++++++ public/{sm_stringhashmap.h => sm_hashmap.h} | 59 ++- public/sm_namehashset.h | 2 +- 19 files changed, 833 insertions(+), 41 deletions(-) rename public/{sm_stringhashmap.h => sm_hashmap.h} (79%) diff --git a/core/ConCmdManager.h b/core/ConCmdManager.h index a9ec07ed69..f3b882e299 100644 --- a/core/ConCmdManager.h +++ b/core/ConCmdManager.h @@ -38,7 +38,7 @@ #include #include #include -#include +#include #include "sm_globals.h" #include "sourcemm_api.h" diff --git a/core/ConVarManager.h b/core/ConVarManager.h index b541baf9cb..a613aa4331 100644 --- a/core/ConVarManager.h +++ b/core/ConVarManager.h @@ -43,7 +43,7 @@ #include #include "concmd_cleaner.h" #include "PlayerManager.h" -#include +#include using namespace SourceHook; diff --git a/core/ConsoleDetours.h b/core/ConsoleDetours.h index 424576a1f7..7e2c897ee7 100644 --- a/core/ConsoleDetours.h +++ b/core/ConsoleDetours.h @@ -34,7 +34,7 @@ #include "sm_globals.h" #include "sourcemm_api.h" #include -#include +#include namespace SourceMod { class ICommandArgs; diff --git a/core/CoreConfig.h b/core/CoreConfig.h index 461a149316..f722fa4251 100644 --- a/core/CoreConfig.h +++ b/core/CoreConfig.h @@ -36,7 +36,7 @@ #include #include #include -#include +#include using namespace SourceMod; diff --git a/core/HalfLife2.h b/core/HalfLife2.h index b3f9b534ef..f891241c62 100644 --- a/core/HalfLife2.h +++ b/core/HalfLife2.h @@ -38,7 +38,7 @@ #include #include #include -#include +#include #include #include "sm_globals.h" #include "sm_queue.h" diff --git a/core/UserMessages.h b/core/UserMessages.h index 180eee5678..77cc201ba9 100644 --- a/core/UserMessages.h +++ b/core/UserMessages.h @@ -34,7 +34,7 @@ #include #include "sourcemm_api.h" -#include +#include #include "sm_stringutil.h" #include "CellRecipientFilter.h" #include "sm_globals.h" diff --git a/core/logic/ADTFactory.h b/core/logic/ADTFactory.h index bb36faa067..c9491867c1 100644 --- a/core/logic/ADTFactory.h +++ b/core/logic/ADTFactory.h @@ -34,7 +34,7 @@ #include #include "common_logic.h" -#include +#include using namespace SourceMod; diff --git a/core/logic/AdminCache.h b/core/logic/AdminCache.h index 18aa2361ac..daecd9510f 100644 --- a/core/logic/AdminCache.h +++ b/core/logic/AdminCache.h @@ -39,7 +39,7 @@ #include #include #include -#include +#include #include using namespace SourceHook; diff --git a/core/logic/GameConfigs.h b/core/logic/GameConfigs.h index 4dfba8ddf8..7b32ded6c9 100644 --- a/core/logic/GameConfigs.h +++ b/core/logic/GameConfigs.h @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include #include diff --git a/core/logic/Native.h b/core/logic/Native.h index 64d5d065a3..aac9561bee 100644 --- a/core/logic/Native.h +++ b/core/logic/Native.h @@ -36,7 +36,7 @@ #include #include #include -#include +#include #include "common_logic.h" class CNativeOwner; diff --git a/core/logic/PluginSys.h b/core/logic/PluginSys.h index 047a6e1356..1afbf52170 100644 --- a/core/logic/PluginSys.h +++ b/core/logic/PluginSys.h @@ -47,7 +47,7 @@ #include #include "common_logic.h" #include -#include +#include #include #include "ITranslator.h" #include "IGameConfigs.h" diff --git a/core/logic/ShareSys.h b/core/logic/ShareSys.h index 20fc957967..4e5a4547ea 100644 --- a/core/logic/ShareSys.h +++ b/core/logic/ShareSys.h @@ -38,7 +38,7 @@ #include #include #include -#include +#include #include #include "common_logic.h" #include "Native.h" diff --git a/core/logic/Translator.h b/core/logic/Translator.h index 53c91fdcd7..5cd57dfbbf 100644 --- a/core/logic/Translator.h +++ b/core/logic/Translator.h @@ -33,7 +33,7 @@ #define _INCLUDE_SOURCEMOD_TRANSLATOR_H_ #include "common_logic.h" -#include +#include #include #include #include "sm_memtable.h" diff --git a/core/logic/smn_adt_trie.cpp b/core/logic/smn_adt_trie.cpp index 8d1f7d6f45..ff1d7a9a18 100644 --- a/core/logic/smn_adt_trie.cpp +++ b/core/logic/smn_adt_trie.cpp @@ -35,12 +35,14 @@ #include "common_logic.h" #include -#include +#include #include "sm_memtable.h" #include HandleType_t htCellTrie; HandleType_t htSnapshot; +HandleType_t htIntCellTrie; +HandleType_t htIntSnapshot; enum EntryType { @@ -171,6 +173,11 @@ struct CellTrie StringHashMap map; }; +struct IntCellTrie +{ + IntHashMap map; +}; + struct TrieSnapshot { TrieSnapshot() @@ -187,6 +194,19 @@ struct TrieSnapshot BaseStringTable strings; }; +struct IntTrieSnapshot +{ + IntTrieSnapshot() {} + + size_t mem_usage() + { + return length * sizeof(int); + } + + size_t length; + std::unique_ptr keys; +}; + class TrieHelpers : public SMGlobalClass, public IHandleTypeDispatch @@ -196,11 +216,15 @@ class TrieHelpers : { htCellTrie = handlesys->CreateType("Trie", this, 0, NULL, NULL, g_pCoreIdent, NULL); htSnapshot = handlesys->CreateType("TrieSnapshot", this, 0, NULL, NULL, g_pCoreIdent, NULL); + htIntCellTrie = handlesys->CreateType("IntTrie", this, 0, NULL, NULL, g_pCoreIdent, NULL); + htIntSnapshot = handlesys->CreateType("IntTrieSnapshot", this, 0, NULL, NULL, g_pCoreIdent, NULL); } void OnSourceModShutdown() { handlesys->RemoveType(htSnapshot, g_pCoreIdent); handlesys->RemoveType(htCellTrie, g_pCoreIdent); + handlesys->RemoveType(htIntSnapshot, g_pCoreIdent); + handlesys->RemoveType(htIntCellTrie, g_pCoreIdent); } public: //IHandleTypeDispatch void OnHandleDestroy(HandleType_t type, void *object) @@ -208,10 +232,21 @@ class TrieHelpers : if (type == htCellTrie) { delete (CellTrie *)object; - } else { + } + else if (type == htSnapshot) + { TrieSnapshot *snapshot = (TrieSnapshot *)object; delete snapshot; } + else if (type == htIntCellTrie) + { + delete (IntCellTrie *)object; + } + else if (type == htIntSnapshot) + { + IntTrieSnapshot *snapshot = (IntTrieSnapshot *)object; + delete snapshot; + } } bool GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize) { @@ -219,11 +254,28 @@ class TrieHelpers : { CellTrie *pArray = (CellTrie *)object; *pSize = sizeof(CellTrie) + pArray->map.mem_usage(); - } else { + return true; + } + else if (type == htSnapshot) + { TrieSnapshot *snapshot = (TrieSnapshot *)object; *pSize = sizeof(TrieSnapshot) + snapshot->mem_usage(); + return true; + } + else if (type == htIntCellTrie) + { + IntCellTrie *pArray = (IntCellTrie *)object; + *pSize = sizeof(IntCellTrie) + pArray->map.mem_usage(); + return true; + } + else if (type == htIntSnapshot) + { + IntTrieSnapshot *snapshot = (IntTrieSnapshot *)object; + *pSize = sizeof(IntTrieSnapshot) + snapshot->mem_usage(); + return true; } - return true; + + return false; } } s_CellTrieHelpers; @@ -242,6 +294,21 @@ static cell_t CreateTrie(IPluginContext *pContext, const cell_t *params) return hndl; } +static cell_t CreateIntTrie(IPluginContext *pContext, const cell_t *params) +{ + IntCellTrie *pTrie = new IntCellTrie; + Handle_t hndl; + + if ((hndl = handlesys->CreateHandle(htIntCellTrie, pTrie, pContext->GetIdentity(), g_pCoreIdent, NULL)) + == BAD_HANDLE) + { + delete pTrie; + return BAD_HANDLE; + } + + return hndl; +} + static cell_t SetTrieValue(IPluginContext *pContext, const cell_t *params) { CellTrie *pTrie; @@ -275,6 +342,38 @@ static cell_t SetTrieValue(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t SetIntTrieValue(IPluginContext *pContext, const cell_t *params) +{ + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + int32_t key = params[2]; + + IntHashMap::Insert i = pTrie->map.findForAdd(key); + if (!i.found()) + { + if (!pTrie->map.add(i, key)) + return 0; + i->value.setCell(params[3]); + return 1; + } + + if (!params[4]) + return 0; + + i->value.setCell(params[3]); + return 1; +} + static cell_t SetTrieArray(IPluginContext *pContext, const cell_t *params) { CellTrie *pTrie; @@ -316,6 +415,46 @@ static cell_t SetTrieArray(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t SetIntTrieArray(IPluginContext *pContext, const cell_t *params) +{ + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + if (params[4] < 0) + { + return pContext->ThrowNativeError("Invalid array size: %d", params[4]); + } + + int32_t key = params[2]; + cell_t *array; + pContext->LocalToPhysAddr(params[3], &array); + + IntHashMap::Insert i = pTrie->map.findForAdd(key); + if (!i.found()) + { + if (!pTrie->map.add(i, key)) + return 0; + i->key = key; + i->value.setArray(array, params[4]); + return 1; + } + + if (!params[5]) + return 0; + + i->value.setArray(array, params[4]); + return 1; +} + static cell_t SetTrieString(IPluginContext *pContext, const cell_t *params) { CellTrie *pTrie; @@ -350,6 +489,40 @@ static cell_t SetTrieString(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t SetIntTrieString(IPluginContext *pContext, const cell_t *params) +{ + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + int32_t key = params[2]; + char *val; + pContext->LocalToString(params[3], &val); + + IntHashMap::Insert i = pTrie->map.findForAdd(key); + if (!i.found()) + { + if (!pTrie->map.add(i, key)) + return 0; + i->value.setString(val); + return 1; + } + + if (!params[4]) + return 0; + + i->value.setString(val); + return 1; +} + static cell_t ContainsKeyInTrie(IPluginContext *pContext, const cell_t *params) { CellTrie *pTrie; @@ -371,6 +544,26 @@ static cell_t ContainsKeyInTrie(IPluginContext *pContext, const cell_t *params) return r.found() ? 1 : 0; } +static cell_t ContainsKeyInIntTrie(IPluginContext *pContext, const cell_t *params) +{ + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + int32_t key = params[2]; + + IntHashMap::Result r = pTrie->map.find(key); + + return r.found() ? 1 : 0; +} + static cell_t RemoveFromTrie(IPluginContext *pContext, const cell_t *params) { CellTrie *pTrie; @@ -396,6 +589,30 @@ static cell_t RemoveFromTrie(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t RemoveFromIntTrie(IPluginContext *pContext, const cell_t *params) +{ + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + int32_t key = params[2]; + + IntHashMap::Result r = pTrie->map.find(key); + if (!r.found()) + return 0; + + pTrie->map.remove(r); + return 1; +} + static cell_t ClearTrie(IPluginContext *pContext, const cell_t *params) { Handle_t hndl; @@ -415,6 +632,25 @@ static cell_t ClearTrie(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t ClearIntTrie(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl; + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + pTrie->map.clear(); + return 1; +} + static cell_t GetTrieValue(IPluginContext *pContext, const cell_t *params) { Handle_t hndl; @@ -457,6 +693,47 @@ static cell_t GetTrieValue(IPluginContext *pContext, const cell_t *params) return 0; } +static cell_t GetIntTrieValue(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl; + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + int32_t key = params[2]; + cell_t *pValue; + pContext->LocalToPhysAddr(params[3], &pValue); + + IntHashMap::Result r = pTrie->map.find(key); + if (!r.found()) + return 0; + + if (r->value.isCell()) + { + *pValue = r->value.cell(); + return 1; + } + + // 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 = r->value.array()[0]; + return 1; + } + + return 0; +} + static cell_t GetTrieArray(IPluginContext *pContext, const cell_t *params) { Handle_t hndl; @@ -509,6 +786,56 @@ static cell_t GetTrieArray(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t GetIntTrieArray(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl; + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + if (params[4] < 0) + { + return pContext->ThrowNativeError("Invalid array size: %d", params[4]); + } + + int32_t key = params[2]; + cell_t *pValue, *pSize; + pContext->LocalToPhysAddr(params[3], &pValue); + pContext->LocalToPhysAddr(params[5], &pSize); + + IntHashMap::Result r = pTrie->map.find(key); + if (!r.found() || !r->value.isArray()) + return 0; + + if (!r->value.array()) + { + *pSize = 0; + return 1; + } + + if (!params[4]) + return 1; + + 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; +} + static cell_t GetTrieString(IPluginContext *pContext, const cell_t *params) { Handle_t hndl; @@ -545,6 +872,41 @@ static cell_t GetTrieString(IPluginContext *pContext, const cell_t *params) return 1; } +static cell_t GetIntTrieString(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl; + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + if (params[4] < 0) + { + return pContext->ThrowNativeError("Invalid buffer size: %d", params[4]); + } + + int32_t key = params[2]; + cell_t *pSize; + pContext->LocalToPhysAddr(params[5], &pSize); + + IntHashMap::Result r = pTrie->map.find(key); + if (!r.found() || !r->value.isString()) + return 0; + + size_t written; + pContext->StringToLocalUTF8(params[3], params[4], r->value.c_str(), &written); + + *pSize = (cell_t)written; + return 1; +} + static cell_t GetTrieSize(IPluginContext *pContext, const cell_t *params) { Handle_t hndl; @@ -563,6 +925,24 @@ static cell_t GetTrieSize(IPluginContext *pContext, const cell_t *params) return pTrie->map.elements(); } +static cell_t GetIntTrieSize(IPluginContext *pContext, const cell_t *params) +{ + Handle_t hndl; + IntCellTrie *pTrie; + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + hndl = params[1]; + + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + return pTrie->map.elements(); +} + static cell_t CreateTrieSnapshot(IPluginContext *pContext, const cell_t *params) { HandleError err; @@ -595,6 +975,38 @@ static cell_t CreateTrieSnapshot(IPluginContext *pContext, const cell_t *params) return hndl; } +static cell_t CreateIntTrieSnapshot(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + IntCellTrie *pTrie; + if ((err = handlesys->ReadHandle(hndl, htIntCellTrie, &sec, (void **)&pTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", hndl, err); + } + + IntTrieSnapshot *snapshot = new IntTrieSnapshot; + snapshot->length = pTrie->map.elements(); + snapshot->keys = std::make_unique(snapshot->length); + size_t i = 0; + for (IntHashMap::iterator iter = pTrie->map.iter(); !iter.empty(); iter.next(), i++) + snapshot->keys[i] = iter->key; + assert(i == snapshot->length); + + if ((hndl = handlesys->CreateHandle(htIntSnapshot, 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; @@ -612,6 +1024,23 @@ static cell_t TrieSnapshotLength(IPluginContext *pContext, const cell_t *params) return snapshot->length; } +static cell_t IntTrieSnapshotLength(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + IntTrieSnapshot *snapshot; + if ((err = handlesys->ReadHandle(hndl, htIntSnapshot, &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; @@ -657,6 +1086,27 @@ static cell_t GetTrieSnapshotKey(IPluginContext *pContext, const cell_t *params) return written; } +static cell_t GetIntTrieSnapshotKey(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + Handle_t hndl = params[1]; + + IntTrieSnapshot *snapshot; + if ((err = handlesys->ReadHandle(hndl, htIntSnapshot, &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 snapshot->keys[index]; +} + static cell_t CloneTrie(IPluginContext *pContext, const cell_t *params) { HandleError err; @@ -707,6 +1157,56 @@ static cell_t CloneTrie(IPluginContext *pContext, const cell_t *params) return hndl; } +static cell_t CloneIntTrie(IPluginContext *pContext, const cell_t *params) +{ + HandleError err; + HandleSecurity sec = HandleSecurity(pContext->GetIdentity(), g_pCoreIdent); + + IntCellTrie *pOldTrie; + if ((err = handlesys->ReadHandle(params[1], htIntCellTrie, &sec, (void **)&pOldTrie)) + != HandleError_None) + { + return pContext->ThrowNativeError("Invalid Handle %x (error %d)", params[1], err); + } + + IntCellTrie *pNewTrie = new IntCellTrie; + Handle_t hndl = handlesys->CreateHandle(htIntCellTrie, pNewTrie, pContext->GetIdentity(), g_pCoreIdent, NULL); + if (!hndl) + { + delete pNewTrie; + return hndl; + } + + for (IntHashMap::iterator it = pOldTrie->map.iter(); !it.empty(); it.next()) + { + int32_t key = it->key; + IntHashMap::Insert insert = pNewTrie->map.findForAdd(key); + if (pNewTrie->map.add(insert, key)) + { + IntHashMap::Result result = pOldTrie->map.find(key); + if (result->value.isCell()) + { + insert->value.setCell(result->value.cell()); + } + else if (result->value.isString()) + { + insert->value.setString(result->value.c_str()); + } + else if (result->value.isArray()) + { + insert->value.setArray(result->value.array(), result->value.arrayLength()); + } + else + { + handlesys->FreeHandle(hndl, NULL); + return pContext->ThrowNativeError("Unhandled data type encountered, file a bug and reference pr #852"); + } + } + } + + return hndl; +} + REGISTER_NATIVES(trieNatives) { {"ClearTrie", ClearTrie}, @@ -740,9 +1240,26 @@ REGISTER_NATIVES(trieNatives) {"StringMap.Snapshot", CreateTrieSnapshot}, {"StringMap.Clone", CloneTrie}, + {"IntMap.IntMap", CreateIntTrie}, + {"IntMap.Clear", ClearIntTrie}, + {"IntMap.GetArray", GetIntTrieArray}, + {"IntMap.GetString", GetIntTrieString}, + {"IntMap.GetValue", GetIntTrieValue}, + {"IntMap.ContainsKey", ContainsKeyInIntTrie}, + {"IntMap.Remove", RemoveFromIntTrie}, + {"IntMap.SetArray", SetIntTrieArray}, + {"IntMap.SetString", SetIntTrieString}, + {"IntMap.SetValue", SetIntTrieValue}, + {"IntMap.Size.get", GetIntTrieSize}, + {"IntMap.Snapshot", CreateIntTrieSnapshot}, + {"IntMap.Clone", CloneIntTrie}, + {"StringMapSnapshot.Length.get", TrieSnapshotLength}, {"StringMapSnapshot.KeyBufferSize", TrieSnapshotKeyBufferSize}, {"StringMapSnapshot.GetKey", GetTrieSnapshotKey}, + {"IntMapSnapshot.Length.get", IntTrieSnapshotLength}, + {"IntMapSnapshot.GetKey", GetIntTrieSnapshotKey}, + {NULL, NULL}, }; diff --git a/extensions/sdktools/extension.h b/extensions/sdktools/extension.h index 877ebb95cf..c985d0d4d7 100644 --- a/extensions/sdktools/extension.h +++ b/extensions/sdktools/extension.h @@ -53,7 +53,7 @@ #include #if SOURCE_ENGINE == SE_CSGO #include -#include +#include #endif #include "SoundEmitterSystem/isoundemittersystembase.h" diff --git a/plugins/include/adt_trie.inc b/plugins/include/adt_trie.inc index 5b4b94e6aa..edb89ed670 100644 --- a/plugins/include/adt_trie.inc +++ b/plugins/include/adt_trie.inc @@ -167,6 +167,125 @@ methodmap StringMapSnapshot < Handle public native int GetKey(int index, char[] buffer, int maxlength); }; +methodmap IntMap < Handle +{ + // Creates a hash map. A hash map is a container that can map integers (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. + // + // Insertion, deletion, and lookup in a hash map are all considered to be fast + // operations, amortized to O(1), or constant time. + // + // 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). + // + // The IntMap must be freed via delete or CloseHandle(). + public native IntMap(); + + // Clones a hash map, returning a new handle with the same size and data. + // This should NOT be confused with CloneHandle. This is a completely new + // handle with the same data but no relation to the original. It should be + // closed when no longer needed with delete or CloseHandle(). + // + // @return New handle to the cloned string map + public native IntMap Clone(); + + // Sets a value in a hash map, either inserting a new entry or replacing an old one. + // + // @param key Key integer. + // @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. + public native bool SetValue(const int key, any value, bool replace=true); + + // Sets an array value in a Map, either inserting a new entry or replacing an old one. + // + // @param key Key integer. + // @param array Array to store. + // @param num_items Number of items in the array. + // @param replace If false, operation will fail if the key is already set. + // @return True on success, false on failure. + public native bool SetArray(const int key, const any[] array, int num_items, bool replace=true); + + // Sets a string value in a Map, either inserting a new entry or replacing an old one. + // + // @param key Key integer. + // @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. + public native bool SetString(const int key, const char[] value, bool replace=true); + + // Retrieves a value in a Map. + // + // @param key Key integer. + // @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). + public native bool GetValue(const int key, any &value); + + // Retrieves an array in a Map. + // + // @param key Key integer. + // @param array Buffer to store array. + // @param max_size Maximum size of array buffer. + // @param size Optional parameter to store the number of elements written to the buffer. + // @return True on success. False if the key is not set, or the key is set + // as a value or string (not an array). + public native bool GetArray(const int key, any[] array, int max_size, int &size=0); + + // Retrieves a string in a Map. + // + // @param key Key integer. + // @param value Buffer to store value. + // @param max_size Maximum size of string buffer. + // @param size Optional parameter to store the number of bytes written to the buffer. + // @return True on success. False if the key is not set, or the key is set + // as a value or array (not a string). + public native bool GetString(const int key, char[] value, int max_size, int &size=0); + + // Checks whether a key is present in a Map. + // + // @param key Key integer. + // @return True if the key has been found, else false. + public native bool ContainsKey(const int key); + + // Removes a key entry from a Map. + // + // @param key Key integer. + // @return True on success, false if the value was never set. + public native bool Remove(const int key); + + // Clears all entries from a Map. + public native void Clear(); + + // Create a snapshot of the map's keys. See IntMapSnapshot. + public native IntMapSnapshot Snapshot(); + + // Retrieves the number of elements in a map. + property int Size { + public native get(); + } +}; + +// A IntMapSnapshot is created via IntMap.Snapshot(). It captures the +// keys on a map so they can be read. Snapshots must be freed with delete or +// CloseHandle(). +methodmap IntMapSnapshot < Handle +{ + // Returns the number of keys in the map snapshot. + property int Length { + public native get(); + } + + // Retrieves the key integer of a given key in a map snapshot. + // + // @param index Key index (starting from 0). + // @return The key integer + // @error Index out of range. + public native int GetKey(int index); +}; + /** * 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 diff --git a/plugins/testsuite/tries.sp b/plugins/testsuite/tries.sp index 05a914f073..646fa99915 100644 --- a/plugins/testsuite/tries.sp +++ b/plugins/testsuite/tries.sp @@ -13,6 +13,7 @@ public Plugin:myinfo = public OnPluginStart() { RegServerCmd("test_maps", RunTests); + RegServerCmd("test_int_maps", RunIntTest); } public Action:RunTests(argc) @@ -161,3 +162,143 @@ public Action:RunTests(argc) return Plugin_Handled; } +public Action:RunIntTests(argc) +{ + IntMap map = new IntMap(); + + for (new i = 0; i < 64; i++) { + if (!map.SetValue(i, i)) + ThrowError("set map to %d failed", i); + + new value; + if (!map.GetValue(buffer, value)) + ThrowError("get map %d", i); + if (value != i) + ThrowError("get map %d == %d", i, i); + } + + // Setting 17 without replace should fail. + new value; + if (map.SetValue(17, 999, false)) + ThrowError("set map 17 should fail"); + if (!map.GetValue(17, value) || value != 17) + ThrowError("value at 17 not correct"); + if (!map.SetValue(17, 999)) + ThrowError("set map 17 = 999 should succeed"); + if (!map.GetValue(17, value) || value != 999) + ThrowError("value at 17 not correct"); + + // Check size is 64. + if (map.Size != 64) + ThrowError("map size not 64"); + + // Check 100 is not found. + int array[64]; + char string[64]; + if (map.GetValue(100, value) || + map.GetArray(100, array, sizeof(array)) || + map.GetString(100, string, sizeof(string))) + { + ThrowError("map should not have 100"); + } + + // Check that 17 is not a string or array. + if (map.GetArray(17, array, sizeof(array)) || + map.GetString(17, string, sizeof(string))) + { + ThrowError("entry 17 should not be an array or string"); + } + + // Strings. + if (!map.SetString(17, "hellokitty")) + ThrowError("17 should be string"); + if (!map.GetString(17, string, sizeof(string)) || + strcmp(string, "hellokitty") != 0) + { + ThrowError("17 should be hellokitty"); + } + if (map.GetValue(17, value) || + map.GetArray("17", array, sizeof(array))) + { + ThrowError("entry 17 should not be an array or string"); + } + + // Arrays. + new data[5] = { 93, 1, 2, 3, 4 }; + if (!map.SetArray(17, data, 5)) + ThrowError("17 should be string"); + if (!map.GetArray(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 (map.GetValue(17, value) || + map.GetString(17, string, sizeof(string))) + { + ThrowError("entry 17 should not be an array or string"); + } + + if (!map.SetArray(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 (!map.GetArray(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 (!map.GetValue(17, value)) + ThrowError("backwards compatibility failed"); + if (value != data[0]) + ThrowError("wrong value (%d, expected %d)", value, data[0]); + + // Remove "17". + if (!map.Remove(17)) + ThrowError("17 should have been removed"); + if (map.Remove(17)) + ThrowError("17 should not exist"); + if (map.GetValue(17, value) || + map.GetArray(17, array, sizeof(array)) || + map.GetString(17, string, sizeof(string))) + { + ThrowError("map should not have a 17"); + } + + map.Clear(); + + if (map.Size) + ThrowError("size should be 0"); + + map.SetString(42, "time!"); + map.SetString(84, "bees"); + map.SetString(126, "egg"); + + IntMapSnapshot keys = map.Snapshot(); + { + if (keys.Length != 3) + ThrowError("map snapshot length should be 3"); + + bool found[3]; + for (new i = 0; i < keys.Length; i++) { + decl key = keys.GetKey(i); + + if (key == 42) + found[0] = true; + else if (key == 84) + found[1] = true; + else if (key == 126) + found[2] = true; + else + ThrowError("unexpected key: %d", key); + } + + if (!found[0] || !found[1] || !found[2]) + ThrowError("did not find all keys"); + } + delete keys; + + PrintToServer("All tests passed!"); + + delete map; + return Plugin_Handled; +} \ No newline at end of file diff --git a/public/sm_stringhashmap.h b/public/sm_hashmap.h similarity index 79% rename from public/sm_stringhashmap.h rename to public/sm_hashmap.h index e1280695f4..a5d9724b91 100644 --- a/public/sm_stringhashmap.h +++ b/public/sm_hashmap.h @@ -98,16 +98,25 @@ namespace detail return key.hash(); } }; + + struct IntHashMapPolicy + { + static inline bool matches(const int32_t lookup, const int32_t compare) { + return lookup == compare; + } + static inline uint32_t hash(const int32_t key) { + return ke::HashInt32(key); + } + }; } -template -class StringHashMap +template +class HashMap { - typedef detail::CharsAndLength CharsAndLength; - typedef ke::HashMap Internal; + typedef ke::HashMap Internal; public: - StringHashMap() + HashMap() : internal_(ke::SystemAllocatorPolicy()), memory_used_(0) { @@ -120,9 +129,9 @@ class StringHashMap typedef typename Internal::iterator iterator; // Some KTrie-like helper functions. - bool retrieve(const char *aKey, T *aResult = NULL) + bool retrieve(const KeyLookupType &aKey, T *aResult = NULL) { - CharsAndLength key(aKey); + ContainerType key(aKey); Result r = internal_.find(key); if (!r.found()) return false; @@ -131,9 +140,9 @@ class StringHashMap return true; } - bool retrieve(const char *aKey, T **aResult) + bool retrieve(const KeyLookupType &aKey, T **aResult) { - CharsAndLength key(aKey); + ContainerType key(aKey); Result r = internal_.find(key); if (!r.found()) return false; @@ -141,23 +150,23 @@ class StringHashMap return true; } - Result find(const char *aKey) + Result find(const KeyLookupType &aKey) { - CharsAndLength key(aKey); + ContainerType key(aKey); return internal_.find(key); } - bool contains(const char *aKey) + bool contains(const KeyLookupType &aKey) { - CharsAndLength key(aKey); + ContainerType key(aKey); Result r = internal_.find(key); return r.found(); } template - bool replace(const char *aKey, UV &&value) + bool replace(const KeyLookupType &aKey, UV &&value) { - CharsAndLength key(aKey); + ContainerType key(aKey); Insert i = internal_.findForAdd(key); if (!i.found()) { @@ -170,9 +179,9 @@ class StringHashMap } template - bool insert(const char *aKey, UV &&value) + bool insert(const KeyLookupType &aKey, UV &&value) { - CharsAndLength key(aKey); + ContainerType key(aKey); Insert i = internal_.findForAdd(key); if (i.found()) return false; @@ -182,9 +191,9 @@ class StringHashMap return true; } - bool remove(const char *aKey) + bool remove(const KeyLookupType &aKey) { - CharsAndLength key(aKey); + ContainerType key(aKey); Result r = internal_.find(key); if (!r.found()) return false; @@ -219,9 +228,9 @@ class StringHashMap } - Insert findForAdd(const char *aKey) + Insert findForAdd(const KeyLookupType &aKey) { - CharsAndLength key(aKey); + ContainerType key(aKey); return internal_.findForAdd(key); } @@ -234,7 +243,7 @@ class StringHashMap } // Only value needs to be set after. - bool add(Insert &i, const char *aKey) + bool add(Insert &i, const KeyLookupType &aKey) { if (!internal_.add(i, aKey)) return false; @@ -246,6 +255,12 @@ class StringHashMap size_t memory_used_; }; +template +using StringHashMap = HashMap; + +template +using IntHashMap = HashMap; + } #endif // _include_sourcemod_hashtable_h_ diff --git a/public/sm_namehashset.h b/public/sm_namehashset.h index e71b556f1e..b4bb0f76fe 100644 --- a/public/sm_namehashset.h +++ b/public/sm_namehashset.h @@ -41,7 +41,7 @@ #include #include #include -#include "sm_stringhashmap.h" +#include "sm_hashmap.h" namespace SourceMod { From ae70e45f94fac6e57b7908666c464ea655a8681b Mon Sep 17 00:00:00 2001 From: KitRifty Date: Wed, 12 Jul 2023 08:44:41 -0700 Subject: [PATCH 2/4] Fix spelling mistake + include --- extensions/dhooks/signatures.h | 2 +- plugins/include/adt_trie.inc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/extensions/dhooks/signatures.h b/extensions/dhooks/signatures.h index de9ce7cd0d..2a1a2c61ad 100644 --- a/extensions/dhooks/signatures.h +++ b/extensions/dhooks/signatures.h @@ -36,7 +36,7 @@ #include "util.h" #include #include -#include +#include struct ArgumentInfo { ArgumentInfo() : name() diff --git a/plugins/include/adt_trie.inc b/plugins/include/adt_trie.inc index edb89ed670..19a5e65da3 100644 --- a/plugins/include/adt_trie.inc +++ b/plugins/include/adt_trie.inc @@ -188,7 +188,7 @@ methodmap IntMap < Handle // handle with the same data but no relation to the original. It should be // closed when no longer needed with delete or CloseHandle(). // - // @return New handle to the cloned string map + // @return New handle to the cloned hash map public native IntMap Clone(); // Sets a value in a hash map, either inserting a new entry or replacing an old one. From 5c94e6c264e0ce4a7e35bce3fe999f563f4acf36 Mon Sep 17 00:00:00 2001 From: KitRifty Date: Wed, 12 Jul 2023 09:07:18 -0700 Subject: [PATCH 3/4] Fix tries test --- plugins/testsuite/tries.sp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/testsuite/tries.sp b/plugins/testsuite/tries.sp index 646fa99915..37d8ae4866 100644 --- a/plugins/testsuite/tries.sp +++ b/plugins/testsuite/tries.sp @@ -13,7 +13,7 @@ public Plugin:myinfo = public OnPluginStart() { RegServerCmd("test_maps", RunTests); - RegServerCmd("test_int_maps", RunIntTest); + RegServerCmd("test_int_maps", RunIntTests); } public Action:RunTests(argc) @@ -171,7 +171,7 @@ public Action:RunIntTests(argc) ThrowError("set map to %d failed", i); new value; - if (!map.GetValue(buffer, value)) + if (!map.GetValue(i, value)) ThrowError("get map %d", i); if (value != i) ThrowError("get map %d == %d", i, i); @@ -218,7 +218,7 @@ public Action:RunIntTests(argc) ThrowError("17 should be hellokitty"); } if (map.GetValue(17, value) || - map.GetArray("17", array, sizeof(array))) + map.GetArray(17, array, sizeof(array))) { ThrowError("entry 17 should not be an array or string"); } From 401484d5b90f52edcf789bae1a35b34c15ea813d Mon Sep 17 00:00:00 2001 From: KitRifty Date: Wed, 12 Jul 2023 10:05:41 -0700 Subject: [PATCH 4/4] Update tests with clone + ContainsKey --- plugins/testsuite/tries.sp | 56 ++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/plugins/testsuite/tries.sp b/plugins/testsuite/tries.sp index 37d8ae4866..687a5089dc 100644 --- a/plugins/testsuite/tries.sp +++ b/plugins/testsuite/tries.sp @@ -170,6 +170,9 @@ public Action:RunIntTests(argc) if (!map.SetValue(i, i)) ThrowError("set map to %d failed", i); + if (!map.ContainsKey(i)) + ThrowError("map contains %d failed", i) + new value; if (!map.GetValue(i, value)) ThrowError("get map %d", i); @@ -195,7 +198,8 @@ public Action:RunIntTests(argc) // Check 100 is not found. int array[64]; char string[64]; - if (map.GetValue(100, value) || + if (map.ContainsKey(100) || + map.GetValue(100, value) || map.GetArray(100, array, sizeof(array)) || map.GetString(100, string, sizeof(string))) { @@ -226,9 +230,9 @@ public Action:RunIntTests(argc) // Arrays. new data[5] = { 93, 1, 2, 3, 4 }; if (!map.SetArray(17, data, 5)) - ThrowError("17 should be string"); + ThrowError("couldn't set 17 to 5-entry array"); if (!map.GetArray(17, array, sizeof(array))) - ThrowError("17 should be hellokitty"); + ThrowError("couldn't fetch 5-entry array"); 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]); @@ -236,7 +240,7 @@ public Action:RunIntTests(argc) if (map.GetValue(17, value) || map.GetString(17, string, sizeof(string))) { - ThrowError("entry 17 should not be an array or string"); + ThrowError("entry 17 should not be a value or string"); } if (!map.SetArray(17, data, 1)) @@ -257,7 +261,8 @@ public Action:RunIntTests(argc) ThrowError("17 should have been removed"); if (map.Remove(17)) ThrowError("17 should not exist"); - if (map.GetValue(17, value) || + if (map.ContainsKey(17) || + map.GetValue(17, value) || map.GetArray(17, array, sizeof(array)) || map.GetString(17, string, sizeof(string))) { @@ -297,6 +302,47 @@ public Action:RunIntTests(argc) } delete keys; + map.SetValue(10240, 6744); + map.SetValue(8, 13); + + new cloneData[5] = { 12, 23, 55, 1, 2 }; + new cloneArr[5]; + map.SetArray(9102, cloneData, 5); + + IntMap clone = map.Clone(); + + if (clone.Size != map.Size) + ThrowError("cloned map size mismatch (%d, expected %d)", clone.Size, map.Size); + + if (!clone.GetString(42, string, sizeof(string))) + ThrowError("cloned map entry 42 should be a string"); + if (strcmp(string, "time!") != 0) + ThrowError("cloned map entry 42 should be \"time!\""); + if (!clone.GetString(84, string, sizeof(string))) + ThrowError("cloned map entry 84 should be a string"); + if (strcmp(string, "bees") != 0) + ThrowError("cloned map entry 84 should be \"bees\""); + if (!clone.GetString(126, string, sizeof(string))) + ThrowError("cloned map entry 126 should be a string"); + if (strcmp(string, "egg") != 0) + ThrowError("cloned map entry 126 should be \"egg\""); + if (!clone.GetValue(10240, value)) + ThrowError("cloned map entry 10240 should be a value"); + if (value != 6744) + ThrowError("cloned map entry 10240 should be 6744") + if (!clone.GetValue(8, value)) + ThrowError("cloned map entry 8 should be a value"); + if (value != 13) + ThrowError("cloned map entry 8 should be 13") + if (!clone.GetArray(9102, cloneArr, 5)) + ThrowError("cloned map entry 9102 should be an array"); + for (new i = 0; i < 5; i++) { + if (cloneData[i] != cloneArr[i]) + ThrowError("cloned map entry 9102 slot %d should be %d, got %d", i, cloneData[i], cloneArr[i]); + } + + delete clone; + PrintToServer("All tests passed!"); delete map;