From f3aee6d7661701dd294ffeb43979f8e2640d6357 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sat, 3 May 2014 13:00:21 +0200 Subject: [PATCH 1/7] Add AMTL files --- AMBuildScript | 1 + amxmodx/Makefile | 4 +- amxmodx/msvc10/amxmodx_mm.vcxproj | 9 +- public/amtl/am-allocator-policies.h | 65 +++ public/amtl/am-atomics.h | 102 ++++ public/amtl/am-hashmap.h | 196 ++++++++ public/amtl/am-hashset.h | 129 +++++ public/amtl/am-hashtable.h | 631 ++++++++++++++++++++++++ public/amtl/am-inlinelist.h | 183 +++++++ public/amtl/am-linkedlist.h | 309 ++++++++++++ public/amtl/am-moveable.h | 73 +++ public/amtl/am-refcounting-threadsafe.h | 68 +++ public/amtl/am-refcounting.h | 302 ++++++++++++ public/amtl/am-string.h | 134 +++++ public/amtl/am-thread-posix.h | 213 ++++++++ public/amtl/am-thread-utils.h | 265 ++++++++++ public/amtl/am-thread-windows.h | 161 ++++++ public/amtl/am-utility.h | 346 +++++++++++++ public/amtl/am-vector.h | 239 +++++++++ 19 files changed, 3424 insertions(+), 6 deletions(-) create mode 100644 public/amtl/am-allocator-policies.h create mode 100644 public/amtl/am-atomics.h create mode 100644 public/amtl/am-hashmap.h create mode 100644 public/amtl/am-hashset.h create mode 100644 public/amtl/am-hashtable.h create mode 100644 public/amtl/am-inlinelist.h create mode 100644 public/amtl/am-linkedlist.h create mode 100644 public/amtl/am-moveable.h create mode 100644 public/amtl/am-refcounting-threadsafe.h create mode 100644 public/amtl/am-refcounting.h create mode 100644 public/amtl/am-string.h create mode 100644 public/amtl/am-thread-posix.h create mode 100644 public/amtl/am-thread-utils.h create mode 100644 public/amtl/am-thread-windows.h create mode 100644 public/amtl/am-utility.h create mode 100644 public/amtl/am-vector.h diff --git a/AMBuildScript b/AMBuildScript index 7bf83a7a..aa9d91e3 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -220,6 +220,7 @@ class AMXXConfig(object): ] cfg.includes += [os.path.join(builder.buildPath, 'includes')] + cfg.includes += [os.path.join(builder.sourcePath, 'public', 'amtl')] return # diff --git a/amxmodx/Makefile b/amxmodx/Makefile index 71581592..5c0422ec 100755 --- a/amxmodx/Makefile +++ b/amxmodx/Makefile @@ -6,7 +6,7 @@ ########################################### HLSDK = ../../hlsdk -MM_ROOT = ../../metamod/metamod +MM_ROOT = ../../metamod-am/metamod ##################################### ### EDIT BELOW FOR OTHER PROJECTS ### @@ -36,7 +36,7 @@ CPP_OSX = clang LINK = -Lzlib -INCLUDE = -I. -I$(HLSDK) -I$(HLSDK)/common -I$(HLSDK)/dlls -I$(HLSDK)/engine -I$(HLSDK)/game_shared \ +INCLUDE = -I. -I../public/amtl -I$(HLSDK) -I$(HLSDK)/common -I$(HLSDK)/dlls -I$(HLSDK)/engine -I$(HLSDK)/game_shared \ -I$(HLSDK)/public -I$(MM_ROOT) ################################################ diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj b/amxmodx/msvc10/amxmodx_mm.vcxproj index 814224ba..a030557a 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj @@ -92,7 +92,7 @@ Disabled - ..\;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) + ..\;..\..\public\amtl;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;PAWN_CELL_SIZE=32;ASM32;JIT;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebug @@ -143,7 +143,7 @@ true Speed true - ..\;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) + ..\;..\..\public\amtl;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;JIT;ASM32;PAWN_CELL_SIZE=32;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) false true @@ -192,7 +192,7 @@ Disabled - ..\;$(METAMOD)\metamod;$(HLSDK)\multiplayer\common;$(HLSDK)\multiplayer\engine;$(HLSDK)\multiplayer\dlls;$(HLSDK)\multiplayer\pm_shared;%(AdditionalIncludeDirectories) + ..\;..\..\public\amtl;$(METAMOD)\metamod;$(HLSDK)\multiplayer\common;$(HLSDK)\multiplayer\engine;$(HLSDK)\multiplayer\dlls;$(HLSDK)\multiplayer\pm_shared;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;PAWN_CELL_SIZE=32;ASM32;JIT;BINLOG_ENABLED;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebug @@ -243,7 +243,7 @@ true Speed true - ..\;$(METAMOD)\metamod;$(HLSDK)\multiplayer\common;$(HLSDK)\multiplayer\engine;$(HLSDK)\multiplayer\dlls;$(HLSDK)\multiplayer\pm_shared;%(AdditionalIncludeDirectories) + ..\;..\..\public\amtl;$(METAMOD)\metamod;$(HLSDK)\multiplayer\common;$(HLSDK)\multiplayer\engine;$(HLSDK)\multiplayer\dlls;$(HLSDK)\multiplayer\pm_shared;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;JIT;ASM32;PAWN_CELL_SIZE=32;BINLOG_ENABLED;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) false true @@ -389,6 +389,7 @@ + diff --git a/public/amtl/am-allocator-policies.h b/public/amtl/am-allocator-policies.h new file mode 100644 index 00000000..5f9712bf --- /dev/null +++ b/public/amtl/am-allocator-policies.h @@ -0,0 +1,65 @@ +// 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_allocatorpolicies_h_ +#define _include_amtl_allocatorpolicies_h_ + +#include +#include + +namespace ke { + +// The default system allocator policy will crash on out-of-memory. +class SystemAllocatorPolicy +{ + public: + void reportOutOfMemory() { + fprintf(stderr, "OUT OF MEMORY\n"); + abort(); + } + void reportAllocationOverflow() { + fprintf(stderr, "OUT OF MEMORY\n"); + abort(); + } + + public: + void free(void *memory) { + ::free(memory); + } + void *malloc(size_t bytes) { + void *ptr = ::malloc(bytes); + if (!ptr) + reportOutOfMemory(); + return ptr; + } +}; + +} + +#endif // _include_amtl_allocatorpolicies_h_ diff --git a/public/amtl/am-atomics.h b/public/amtl/am-atomics.h new file mode 100644 index 00000000..e32d1df9 --- /dev/null +++ b/public/amtl/am-atomics.h @@ -0,0 +1,102 @@ +// 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_atomics_h_ +#define _include_amtl_atomics_h_ + +#include + +namespace ke { + +#if defined(_MSC_VER) +extern "C" { + long __cdecl _InterlockedIncrement(long volatile *dest); + long __cdecl _InterlockedDecrement(long volatile *dest); +} +# pragma intrinsic(_InterlockedIncrement) +# pragma intrinsic(_InterlockedDecrement) +#endif + +template +struct AtomicOps; + +template <> +struct AtomicOps<4> +{ +#if defined(_MSC_VER) + typedef long Type; + + static Type Increment(Type *ptr) { + return _InterlockedIncrement(ptr); + } + static Type Decrement(Type *ptr) { + return _InterlockedDecrement(ptr); + }; +#elif defined(__GNUC__) + typedef int Type; + + // x86/x64 notes: When using GCC < 4.8, this will compile to a spinlock. + // On 4.8+, or when using Clang, we'll get the more optimal "lock addl" + // variant. + static Type Increment(Type *ptr) { + return __sync_add_and_fetch(ptr, 1); + } + static Type Decrement(Type *ptr) { + return __sync_sub_and_fetch(ptr, 1); + } +#endif +}; + +class AtomicRefCount +{ + typedef AtomicOps Ops; + + public: + AtomicRefCount(uintptr_t value) + : value_(value) + { + } + + void increment() { + Ops::Increment(&value_); + } + + // Return false if all references are gone. + bool decrement() { + return Ops::Decrement(&value_) != 0; + } + + private: + Ops::Type value_; +}; + +} + +#endif // _include_amtl_atomics_h_ + diff --git a/public/amtl/am-hashmap.h b/public/amtl/am-hashmap.h new file mode 100644 index 00000000..9a2d1ee6 --- /dev/null +++ b/public/amtl/am-hashmap.h @@ -0,0 +1,196 @@ +// 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_hashmap_h_ +#define _include_amtl_hashmap_h_ + +#include + +namespace ke { + +// Template parameters: +// +// K - Key type. +// V - Value type. +// HashPolicy - A struct with a hash and comparator function for each lookup type: +// static uint32_t hash(const Type &value); +// static bool matches(const Type &value, const K &key); +// +// All types that match a given key, must compute the same hash. +// +// Note that like HashTable, a HashMap is not usable until init() has been called. +template +class HashMap : public AllocPolicy +{ + private: + struct Entry + { + K key; + V value; + + 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 + { + typedef Entry Payload; + + template + static uint32_t hash(const Lookup &key) { + return HashPolicy::hash(key); + } + + template + static bool matches(const Lookup &key, const Payload &payload) { + return HashPolicy::matches(key, payload.key); + } + }; + + typedef HashTable Internal; + + public: + HashMap(AllocPolicy ap = AllocPolicy()) + : table_(ap) + { + } + + // capacity must be a power of two. + bool init(size_t capacity = 16) { + return table_.init(capacity); + } + + typedef typename Internal::Result Result; + typedef typename Internal::Insert Insert; + typedef typename Internal::iterator iterator; + + template + Result find(const Lookup &key) { + return table_.find(key); + } + + template + Insert findForAdd(const Lookup &key) { + return table_.findForAdd(key); + } + + template + void removeIfExists(const Lookup &key) { + return table_.remove(key); + } + + void remove(Result &r) { + table_.remove(r); + } + + // The map 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 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 + // must be set after. + bool add(Insert &i) { + return table_.add(i); + } + + iterator iter() { + return iterator(&table_); + } + + void clear() { + table_.clear(); + } + + size_t elements() const { + return table_.elements(); + } + + size_t estimateMemoryUse() const { + return table_.estimateMemoryUse(); + } + + private: + Internal table_; +}; + +template +struct PointerPolicy +{ + static inline uint32_t hash(T *p) { + return HashPointer(p); + } + static inline bool matches(T *p1, T *p2) { + return p1 == p2; + } +}; + +} + +#endif // _include_amtl_hashmap_h_ diff --git a/public/amtl/am-hashset.h b/public/amtl/am-hashset.h new file mode 100644 index 00000000..13f8eb8f --- /dev/null +++ b/public/amtl/am-hashset.h @@ -0,0 +1,129 @@ +// 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_hashmap_h_ +#define _include_amtl_hashmap_h_ + +#include + +namespace ke { + +// Template parameters: +// +// K - Key type. +// HashPolicy - A struct with a hash and comparator function for each lookup type: +// static uint32_t hash(const Type &value); +// static bool matches(const Type &value, const K &key); +// +// Like HashMap and HashTable, init() must be called to construct the set. +template +class HashSet : public AllocPolicy +{ + struct Policy { + typedef K Payload; + + template + static uint32_t hash(const Lookup &key) { + return HashPolicy::hash(key); + } + + template + static bool matches(const Lookup &key, const Payload &payload) { + return HashPolicy::matches(key, payload); + } + }; + + typedef HashTable Internal; + + public: + HashSet(AllocPolicy ap = AllocPolicy()) + : table_(ap) + { + } + + // capacity must be a power of two. + bool init(size_t capacity = 16) { + return table_.init(capacity); + } + + typedef typename Internal::Result Result; + typedef typename Internal::Insert Insert; + + template + Result find(const Lookup &key) { + return table_.find(key); + } + + template + Insert findForAdd(const Lookup &key) { + return table_.findForAdd(key); + } + + template + void removeIfExists(const Lookup &key) { + return table_.remove(key); + } + + void remove(Result &r) { + table_.remove(r); + } + + // The map 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 K &key) { + return table_.add(i, key); + } + bool add(Insert &i, Moveable key) { + return table_.add(i, key); + } + + // 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 + // must be set after. + bool add(Insert &i) { + return table_.add(i); + } + + void clear() { + table_.clear(); + } + + size_t estimateMemoryUse() const { + return table_.estimateMemoryUse(); + } + + private: + Internal table_; +}; + +} + +#endif // _include_amtl_hashset_h_ diff --git a/public/amtl/am-hashtable.h b/public/amtl/am-hashtable.h new file mode 100644 index 00000000..1101a247 --- /dev/null +++ b/public/amtl/am-hashtable.h @@ -0,0 +1,631 @@ +// 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_KEIMA_HASHTABLE_H_ +#define _INCLUDE_KEIMA_HASHTABLE_H_ + +#include +#include +#include +#include "am-allocator-policies.h" +#include "am-utility.h" +#include "am-moveable.h" + +namespace ke { + +namespace detail { + template + class HashTableEntry + { + uint32_t hash_; + T t_; + + public: + static const uint32_t kFreeHash = 0; + static const uint32_t kRemovedHash = 1; + + public: + 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 { + return hash_; + } + void setRemoved() { + destruct(); + hash_ = kRemovedHash; + } + void setFree() { + destruct(); + hash_ = kFreeHash; + } + void initialize() { + hash_ = kFreeHash; + } + void destruct() { + if (isLive()) + t_.~T(); + } + bool removed() const { + return hash_ == kRemovedHash; + } + bool free() const { + return hash_ == kFreeHash; + } + bool isLive() const { + return hash_ > kRemovedHash; + } + T &payload() { + assert(isLive()); + return t_; + } + bool sameHash(uint32_t hash) const { + return hash_ == hash; + } + + private: + HashTableEntry(const HashTableEntry &other) KE_DELETE; + HashTableEntry &operator =(const HashTableEntry &other) KE_DELETE; + }; +} + +// The HashPolicy for the table must have the following members: +// +// Payload +// static uint32_t hash(const LookupType &key); +// static bool matches(const LookupType &key, const Payload &other); +// +// Payload must be a type, and LookupType is any type that lookups will be +// performed with (these functions can be overloaded). Example: +// +// struct Policy { +// typedef KeyValuePair Payload; +// static uint32 hash(const Key &key) { +// ... +// } +// static bool matches(const Key &key, const KeyValuePair &pair) { +// ... +// } +// }; +// +// Note that the table is not usable until init() has been called. +// +template +class HashTable : public AllocPolicy +{ + friend class iterator; + + typedef typename HashPolicy::Payload Payload; + typedef detail::HashTableEntry Entry; + + private: + static const uint32_t kMinCapacity = 16; + static const uint32_t kMaxCapacity = INT_MAX / sizeof(Entry); + + template + uint32_t computeHash(const Key &key) { + // Multiply by golden ratio. + uint32_t hash = HashPolicy::hash(key) * 0x9E3779B9; + if (hash == Entry::kFreeHash || hash == Entry::kRemovedHash) + hash += 2; + return hash; + } + + Entry *createTable(uint32_t capacity) { + assert(capacity <= kMaxCapacity); + + Entry *table = (Entry *)this->malloc(capacity * sizeof(Entry)); + if (!table) + return NULL; + + for (size_t i = 0; i < capacity; i++) + table[i].initialize(); + + return table; + } + + public: + class Result + { + friend class HashTable; + + Entry *entry_; + + Entry &entry() { + return *entry_; + } + + public: + Result(Entry *entry) + : entry_(entry) + { } + + Payload * operator ->() { + return &entry_->payload(); + } + Payload & operator *() { + return entry_->payload(); + } + + bool found() const { + return entry_->isLive(); + } + }; + + class Insert : public Result + { + uint32_t hash_; + + public: + Insert(Entry *entry, uint32_t hash) + : Result(entry), + hash_(hash) + { + } + + uint32_t hash() const { + return hash_; + } + }; + + private: + class Probulator { + uint32_t hash_; + uint32_t capacity_; + + public: + Probulator(uint32_t hash, uint32_t capacity) + : hash_(hash), + capacity_(capacity) + { + assert(IsPowerOfTwo(capacity_)); + } + + uint32_t entry() const { + return hash_ & (capacity_ - 1); + } + uint32_t next() { + hash_++; + return entry(); + } + }; + + bool underloaded() const { + // Check if the table is underloaded: < 25% entries used. + return (capacity_ > kMinCapacity) && (nelements_ + ndeleted_ < capacity_ / 4); + } + bool overloaded() const { + // Grow if the table is overloaded: > 75% entries used. + return (nelements_ + ndeleted_) > ((capacity_ / 2) + (capacity_ / 4)); + } + + bool shrink() { + if ((capacity_ >> 1) < minCapacity_) + return true; + return changeCapacity(capacity_ >> 1); + } + + bool grow() { + if (capacity_ >= kMaxCapacity) { + this->reportAllocationOverflow(); + return false; + } + return changeCapacity(capacity_ << 1); + } + + bool changeCapacity(uint32_t newCapacity) { + assert(newCapacity <= kMaxCapacity); + + Entry *newTable = createTable(newCapacity); + if (!newTable) + return false; + + Entry *oldTable = table_; + uint32_t oldCapacity = capacity_; + + table_ = newTable; + capacity_ = newCapacity; + ndeleted_ = 0; + + for (uint32_t i = 0; i < oldCapacity; i++) { + Entry &oldEntry = oldTable[i]; + if (oldEntry.isLive()) { + Insert p = insertUnique(oldEntry.hash()); + p.entry().setHash(p.hash()); + p.entry().construct(Moveable(oldEntry.payload())); + } + oldEntry.destruct(); + } + this->free(oldTable); + + return true; + } + + // For use when the key is known to be unique. + Insert insertUnique(uint32_t hash) { + Probulator probulator(hash, capacity_); + + Entry *e = &table_[probulator.entry()]; + for (;;) { + if (e->free() || e->removed()) + break; + e = &table_[probulator.next()]; + } + + return Insert(e, hash); + } + + template + Result lookup(const Key &key) { + uint32_t hash = computeHash(key); + Probulator probulator(hash, capacity_); + + Entry *e = &table_[probulator.entry()]; + for (;;) { + if (e->free()) + break; + if (e->isLive() && + e->sameHash(hash) && + HashPolicy::matches(key, e->payload())) + { + return Result(e); + } + e = &table_[probulator.next()]; + } + + return Result(e); + } + + template + Insert lookupForAdd(const Key &key) { + uint32_t hash = computeHash(key); + Probulator probulator(hash, capacity_); + + Entry *e = &table_[probulator.entry()]; + for (;;) { + if (!e->isLive()) + break; + if (e->sameHash(hash) && HashPolicy::matches(key, e->payload())) + break; + e = &table_[probulator.next()]; + } + + return Insert(e, hash); + } + + bool internalAdd(Insert &i) { + assert(!i.found()); + + // If the entry is deleted, just re-use the slot. + if (i.entry().removed()) { + ndeleted_--; + } else { + // Otherwise, see if we're at max capacity. + if (nelements_ == kMaxCapacity) { + this->reportAllocationOverflow(); + return false; + } + + // Check if the table is over or underloaded. The table is always at + // least 25% free, so this check is enough to guarantee one free slot. + // (Without one free slot, insertion search could infinite loop.) + uint32_t oldCapacity = capacity_; + if (!checkDensity()) + return false; + + // If the table changed size, we need to find a new insertion point. + // Note that a removed entry is impossible: either we caught it above, + // or we just resized and no entries are removed. + if (capacity_ != oldCapacity) + i = insertUnique(i.hash()); + } + + nelements_++; + i.entry().setHash(i.hash()); + return true; + } + + void removeEntry(Entry &e) { + assert(e.isLive()); + e.setRemoved(); + ndeleted_++; + nelements_--; + } + + public: + HashTable(AllocPolicy ap = AllocPolicy()) + : AllocPolicy(ap), + capacity_(0), + nelements_(0), + ndeleted_(0), + table_(NULL), + minCapacity_(kMinCapacity) + { + } + + ~HashTable() + { + for (uint32_t i = 0; i < capacity_; i++) + table_[i].destruct(); + this->free(table_); + } + + bool init(uint32_t capacity = 0) { + if (capacity < kMinCapacity) { + capacity = kMinCapacity; + } else if (capacity > kMaxCapacity) { + this->reportAllocationOverflow(); + return false; + } + + minCapacity_ = capacity; + + assert(IsPowerOfTwo(capacity)); + capacity_ = capacity; + + table_ = createTable(capacity_); + if (!table_) + return false; + + return true; + } + + // The Result object must not be used past mutating table operations. + template + Result find(const Key &key) { + return lookup(key); + } + + // The Insert object must not be used past mutating table operations. + template + Insert findForAdd(const Key &key) { + return lookupForAdd(key); + } + + template + void removeIfExists(const Key &key) { + Result r = find(key); + if (!r.found()) + return; + remove(r); + } + + void remove(Result &r) { + assert(r.found()); + removeEntry(r.entry()); + } + + // 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) { + 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) { + if (!internalAdd(i)) + return false; + i.entry().construct(); + return true; + } + + bool checkDensity() { + if (underloaded()) + return shrink(); + if (overloaded()) + return grow(); + return true; + } + + void clear() { + for (size_t i = 0; i < capacity_; i++) { + table_[i].setFree(); + } + ndeleted_ = 0; + nelements_ = 0; + } + + size_t elements() const { + return nelements_; + } + + size_t estimateMemoryUse() const { + return sizeof(Entry) * capacity_; + } + + public: + // It is illegal to mutate a HashTable during iteration. + class iterator + { + public: + iterator(HashTable *table) + : table_(table), + i_(table->table_), + end_(table->table_ + table->capacity_) + { + while (i_ < end_ && !i_->isLive()) + i_++; + } + + bool empty() const { + return i_ == end_; + } + + void erase() { + assert(!empty()); + table_->removeEntry(*i_); + } + + Payload *operator ->() const { + return &i_->payload(); + } + Payload &operator *() const { + return i_->payload(); + } + + void next() { + do { + i_++; + } while (i_ < end_ && !i_->isLive()); + } + + private: + HashTable *table_; + Entry *i_; + Entry *end_; + }; + + private: + HashTable(const HashTable &other) KE_DELETE; + HashTable &operator =(const HashTable &other) KE_DELETE; + + private: + uint32_t capacity_; + uint32_t nelements_; + uint32_t ndeleted_; + Entry *table_; + uint32_t minCapacity_; +}; + +// Bob Jenkin's one-at-a-time hash function[1]. +// +// [1] http://burtleburtle.net/bob/hash/doobs.html +class CharacterStreamHasher +{ + uint32_t hash; + + public: + CharacterStreamHasher() + : hash(0) + { } + + void add(char c) { + hash += c; + hash += (hash << 10); + hash ^= (hash >> 6); + } + + void add(const char *s, size_t length) { + for (size_t i = 0; i < length; i++) + add(s[i]); + } + + uint32_t result() { + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; + } +}; + +static inline uint32_t +HashCharSequence(const char *s, size_t length) +{ + CharacterStreamHasher hasher; + hasher.add(s, length); + return hasher.result(); +} + +static inline uint32_t +FastHashCharSequence(const char *s, size_t length) +{ + uint32_t hash = 0; + for (size_t i = 0; i < length; i++) + hash = s[i] + (hash << 6) + (hash << 16) - hash; + return hash; +} + +// From http://burtleburtle.net/bob/hash/integer.html +static inline uint32_t +HashInt32(int32_t a) +{ + a = (a ^ 61) ^ (a >> 16); + a = a + (a << 3); + a = a ^ (a >> 4); + a = a * 0x27d4eb2d; + a = a ^ (a >> 15); + return a; +} + +// From http://www.cris.com/~Ttwang/tech/inthash.htm +static inline uint32_t +HashInt64(int64_t key) +{ + key = (~key) + (key << 18); // key = (key << 18) - key - 1; + key = key ^ (uint64_t(key) >> 31); + key = key * 21; // key = (key + (key << 2)) + (key << 4); + key = key ^ (uint64_t(key) >> 11); + key = key + (key << 6); + key = key ^ (uint64_t(key) >> 22); + return uint32_t(key); +} + +template +static inline uint32_t +HashInteger(uintptr_t value); + +template <> +inline uint32_t +HashInteger<4>(uintptr_t value) +{ + return HashInt32(value); +} + +template <> +inline uint32_t +HashInteger<8>(uintptr_t value) +{ + return HashInt64(value); +} + +static inline uint32_t +HashPointer(void *ptr) +{ + return HashInteger(reinterpret_cast(ptr)); +} + +} // namespace ke + +#endif // _INCLUDE_KEIMA_HASHTABLE_H_ diff --git a/public/amtl/am-inlinelist.h b/public/amtl/am-inlinelist.h new file mode 100644 index 00000000..99c37793 --- /dev/null +++ b/public/amtl/am-inlinelist.h @@ -0,0 +1,183 @@ +// 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_inline_list_h_ +#define _include_amtl_inline_list_h_ + +#include +#include + +namespace ke { + +template class InlineList; + +// Objects can recursively inherit from InlineListNode in order to have +// membership in an InlineList. +template +class InlineListNode +{ + friend class InlineList; + + public: + InlineListNode() + : next_(NULL), + prev_(NULL) + { + } + + InlineListNode(InlineListNode *next, InlineListNode *prev) + : next_(next), + prev_(prev) + { + } + + protected: + InlineListNode *next_; + InlineListNode *prev_; +}; + +// An InlineList is a linked list that threads link pointers through objects, +// rather than allocating node memory. A node can be in at most one list at +// any time. +// +// Since InlineLists are designed to be very cheap, there is no requirement +// that elements be removed from a list once the list is destructed. However, +// for as long as the list is alive, all of its contained nodes must also +// be alive. +template +class InlineList +{ + typedef InlineListNode Node; + + Node head_; + + // Work around a clang bug where we can't initialize with &head_ in the ctor. + inline Node *head() { + return &head_; + } + + public: + InlineList() + : head_(head(), head()) + { + } + + ~InlineList() + { +#if !defined(NDEBUG) + // Remove all items to clear their next/prev fields. + while (begin() != end()) + remove(*begin()); +#endif + } + + public: + class iterator + { + friend class InlineList; + Node *iter_; + + public: + iterator(Node *iter) + : iter_(iter) + { + } + + iterator & operator ++() { + iter_ = iter_->next; + return *this; + } + iterator operator ++(int) { + iterator old(*this); + iter_ = iter_->next_; + return old; + } + T * operator *() { + return static_cast(iter_); + } + T * operator ->() { + return static_cast(iter_); + } + bool operator !=(const iterator &where) const { + return iter_ != where.iter_; + } + bool operator ==(const iterator &where) const { + return iter_ == where.iter_; + } + }; + + iterator begin() { + return iterator(head_.next_); + } + + iterator end() { + return iterator(&head_); + } + + iterator erase(iterator &at) { + iterator next = at; + next++; + + remove(at.iter_); + + // Iterator is no longer valid. + at.iter_ = NULL; + + return next; + } + + bool empty() const { + return head_.next_ == &head_; + } + + void remove(Node *t) { + t->prev_->next_ = t->next_; + t->next_->prev_ = t->prev_; + +#if !defined(NDEBUG) + t->next_ = NULL; + t->prev_ = NULL; +#endif + } + + void append(Node *t) { + assert(!t->next_); + assert(!t->prev_); + + t->prev_ = head_.prev_; + t->next_ = &head_; + head_.prev_->next_ = t; + head_.prev_ = t; + } +}; + +} + +#endif // _include_amtl_inline_list_h_ + diff --git a/public/amtl/am-linkedlist.h b/public/amtl/am-linkedlist.h new file mode 100644 index 00000000..488e97a2 --- /dev/null +++ b/public/amtl/am-linkedlist.h @@ -0,0 +1,309 @@ +// 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_linkedlist_h_ +#define _include_amtl_linkedlist_h_ + +#include +#include +#include +#include +#include + +namespace ke { + +// LinkedList, analagous to std::list or SourceHook::List. Since it performs a +// malloc() and free() on every contained node, it should be avoided unless +// absolutely necessary, or for when allocation performance is not a factor. It +// is provided here to safely port old AlliedModders code to AMTL. +// +// In order to use a circular chain, LinkedList's allocation size includes +// exactly one T. If T is very large, LinkedList should be allocated on the +// heap, to avoid using the stack. +template +class LinkedList : public AllocPolicy +{ + public: + friend class iterator; + + class Node + { + public: + Node(const T &o) + : obj(o) + { + } + Node(Moveable o) + : obj(o) + { + } + + T obj; + Node *next; + Node *prev; + }; + +public: + LinkedList(AllocPolicy = AllocPolicy()) + : length_(0) + { + head()->prev = head(); + head()->next = head(); + } + ~LinkedList() { + clear(); + } + + bool append(const T &obj) { + return insertBefore(end(), obj) != end(); + } + bool append(Moveable obj) { + return insertBefore(end(), obj) != end(); + } + + bool prepend(const T &obj) { + return insertBefore(begin(), obj) != begin(); + } + bool prepend(Moveable obj) { + return insertBefore(begin(), obj) != begin(); + } + + size_t length() const { + return length_; + } + + void clear() { + Node *node = head()->next; + Node *temp; + head()->next = head(); + head()->prev = head(); + + // Iterate through the nodes until we find the sentinel again. + while (node != head()) { + temp = node->next; + freeNode(node); + node = temp; + } + length_ = 0; + } + + bool empty() const { + return (length_ == 0); + } + + T &front() { + assert(!empty()); + return head()->next->obj; + } + T &back() { + assert(!empty()); + return head()->prev->obj; + } + + private: + const Node *head() const { + return sentinel_.address(); + } + Node *head() { + return sentinel_.address(); + } + + Node *allocNode(const T &obj) { + Node *node = (Node *)this->malloc(sizeof(Node)); + if (!node) + return NULL; + new (node) Node(obj); + return node; + } + Node *allocNode(Moveable obj) { + Node *node = (Node *)this->malloc(sizeof(Node)); + if (!node) + return NULL; + new (node) Node(obj); + return node; + } + + void freeNode(Node *node) { + node->obj.~T(); + this->free(node); + } + + private: + StorageBuffer sentinel_; + size_t length_; + + public: + class iterator + { + friend class LinkedList; + + public: + iterator() + : this_(NULL) + { + } + iterator(const LinkedList &src) + : this_(src.head()) + { + } + iterator(Node *n) + : this_(n) + { + } + iterator(const iterator &where) + : this_(where.this_) + { + } + + iterator &operator --() { + if (this_) + this_ = this_->prev; + return *this; + } + iterator operator --(int) { + iterator old(*this); + if (this_) + this_ = this_->prev; + return old; + } + iterator &operator ++() { + if (this_) + this_ = this_->next; + return *this; + } + iterator operator ++(int) { + iterator old(*this); + if (this_) + this_ = this_->next; + return old; + } + + const T &operator * () const { + return this_->obj; + } + T &operator * () { + return this_->obj; + } + T *operator ->() { + return &this_->obj; + } + const T *operator ->() const { + return &(this_->obj); + } + + bool operator !=(const iterator &where) const { + return (this_ != where.this_); + } + bool operator ==(const iterator &where) const { + return (this_ == where.this_); + } + + operator bool() { + return !!this_; + } + + private: + Node *this_; + }; + + private: + // Insert obj right before where. + iterator insert(iterator where, Node *node) { + if (!node) + return where; + + Node *pWhereNode = where.this_; + + pWhereNode->prev->next = node; + node->prev = pWhereNode->prev; + pWhereNode->prev = node; + node->next = pWhereNode; + + length_++; + return iterator(node); + } + + public: + iterator begin() { + return iterator(head()->next); + } + iterator end() { + return iterator(head()); + } + iterator erase(iterator where) { + Node *pNode = where.this_; + iterator iter(where); + iter++; + + pNode->prev->next = pNode->next; + pNode->next->prev = pNode->prev; + + freeNode(pNode); + length_--; + + return iter; + } + iterator insertBefore(iterator where, const T &obj) { + return insert(where, allocNode(obj)); + } + iterator insertBefore(iterator where, Moveable obj) { + return insert(where, allocNode(obj)); + } + + public: + // Removes one instance of |obj| from the list, if found. + void remove(const T &obj) { + for (iterator b = begin(); b != end(); b++) { + if (*b == obj) { + erase(b); + break; + } + } + } + + template + iterator find(const U &equ) { + for (iterator iter = begin(); iter != end(); iter++) { + if (*iter == equ) + return iter; + } + return end(); + } + + + private: + // These are disallowed because they basically violate the failure handling + // model for AllocPolicies and are also likely to have abysmal performance. + LinkedList &operator =(const LinkedList &other) KE_DELETE; + LinkedList(const LinkedList &other) KE_DELETE; +}; + +} // namespace ke + +#endif //_INCLUDE_CSDM_LIST_H diff --git a/public/amtl/am-moveable.h b/public/amtl/am-moveable.h new file mode 100644 index 00000000..e500dbc7 --- /dev/null +++ b/public/amtl/am-moveable.h @@ -0,0 +1,73 @@ +// 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_; +}; + +template +static inline Moveable +Move(T &t) +{ + return Moveable(t); +} + +} // namespace ke + +#endif // _include_amtl_moveable_h_ diff --git a/public/amtl/am-refcounting-threadsafe.h b/public/amtl/am-refcounting-threadsafe.h new file mode 100644 index 00000000..a37b39d4 --- /dev/null +++ b/public/amtl/am-refcounting-threadsafe.h @@ -0,0 +1,68 @@ +// 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_ts_refcounting_h_ +#define _include_amtl_ts_refcounting_h_ + +#include +#include + +namespace ke { + +template +class RefcountedThreadsafe +{ + public: + RefcountedThreadsafe() + : refcount_(1) + { + } + + void AddRef() { + refcount_.increment(); + } + bool Release() { + if (!refcount_.decrement()) { + delete static_cast(this); + return false; + } + return true; + } + + protected: + ~RefcountedThreadsafe() { + } + + private: + AtomicRefCount refcount_; +}; + +} // namespace ke + +#endif // _include_amtl_ts_refcounting_h_ diff --git a/public/amtl/am-refcounting.h b/public/amtl/am-refcounting.h new file mode 100644 index 00000000..71a6f554 --- /dev/null +++ b/public/amtl/am-refcounting.h @@ -0,0 +1,302 @@ +// vim: set sts=8 ts=4 sw=4 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_refcounting_h_ +#define _include_amtl_refcounting_h_ + +#include +#include + +namespace ke { + +template class Ref; + +// Holds a refcounted T without addrefing it. This is similar to PassRef<> +// below, but is intended only for freshly allocated objects which start +// with reference count 1, and we don't want to add an extra ref just by +// assigning to PassRef<> or Ref<>. +template +class Newborn +{ + public: + Newborn(T *t) + : thing_(t) + { + } + + T *release() const { + return ReturnAndVoid(thing_); + } + + private: + mutable T *thing_; +}; + +template +static inline Newborn +NoAddRef(T *t) +{ + return Newborn(t); +} + +// When returning a value, we'd rather not be needlessly changing the refcount, +// so we have a special type to use for returns. +template +class PassRef +{ + public: + PassRef(T *thing) + : thing_(thing) + { + AddRef(); + } + PassRef() + : thing_(NULL) + { + } + + PassRef(const Newborn &other) + : thing_(other.release()) + { + // Don't addref, newborn means already addref'd. + } + + template + inline PassRef(const Ref &other); + + PassRef(const PassRef &other) + : thing_(other.release()) + { + } + template + PassRef(const PassRef &other) + : thing_(other.release()) + { + } + ~PassRef() + { + Release(); + } + + operator T &() { + return *thing_; + } + operator T *() const { + return thing_; + } + T *operator ->() const { + return operator *(); + } + T *operator *() const { + return thing_; + } + bool operator !() const { + return !thing_; + } + + T *release() const { + return ReturnAndVoid(thing_); + } + + template + PassRef &operator =(const PassRef &other) { + Release(); + thing_ = other.release(); + return *this; + } + + private: + // Disallowed operators. + PassRef &operator =(T *other); + PassRef &operator =(Newborn &other); + + void AddRef() { + if (thing_) + thing_->AddRef(); + } + void Release() { + if (thing_) + thing_->Release(); + } + + private: + mutable T *thing_; +}; + +// Classes which are refcounted should inherit from this. +template +class Refcounted +{ + public: + Refcounted() + : refcount_(1) + { + } + + void AddRef() { + refcount_++; + } + void Release() { + assert(refcount_ > 0); + if (--refcount_ == 0) + delete static_cast(this); + } + + protected: + ~Refcounted() { + } + + private: + uintptr_t refcount_; +}; + +// Simple class for automatic refcounting. +template +class Ref +{ + public: + Ref(T *thing) + : thing_(thing) + { + AddRef(); + } + + Ref() + : thing_(NULL) + { + } + + Ref(const Ref &other) + : thing_(other.thing_) + { + AddRef(); + } + Ref(Moveable other) + : thing_(other->thing_) + { + other->thing_ = NULL; + } + template + Ref(const Ref &other) + : thing_(*other) + { + AddRef(); + } + Ref(const PassRef &other) + : thing_(other.release()) + { + } + template + Ref(const PassRef &other) + : thing_(other.release()) + { + } + Ref(const Newborn &other) + : thing_(other.release()) + { + } + ~Ref() + { + Release(); + } + + T *operator ->() const { + return operator *(); + } + T *operator *() const { + return thing_; + } + operator T *() { + return thing_; + } + bool operator !() const { + return !thing_; + } + + template + Ref &operator =(S *thing) { + Release(); + thing_ = thing; + AddRef(); + return *this; + } + + template + Ref &operator =(const PassRef &other) { + Release(); + thing_ = other.release(); + return *this; + } + + template + Ref &operator =(const Newborn &other) { + Release(); + thing_ = other.release(); + return *this; + } + + Ref &operator =(const Ref &other) { + Release(); + thing_ = other.thing_; + AddRef(); + return *this; + } + + Ref &operator =(Moveable other) { + Release(); + thing_ = other->thing_; + other->thing_ = NULL; + return *this; + } + + private: + void AddRef() { + if (thing_) + thing_->AddRef(); + } + void Release() { + if (thing_) + thing_->Release(); + } + + protected: + T *thing_; +}; + +template template +PassRef::PassRef(const Ref &other) + : thing_(*other) +{ + AddRef(); +} + +} // namespace ke + +#endif // _include_amtl_refcounting_h_ + diff --git a/public/amtl/am-string.h b/public/amtl/am-string.h new file mode 100644 index 00000000..074339fa --- /dev/null +++ b/public/amtl/am-string.h @@ -0,0 +1,134 @@ +// 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_string_h_ +#define _include_amtl_string_h_ + +#include +#include +#include +#include + +namespace ke { + +// ASCII string. +class AString +{ + public: + AString() + : length_(0) + { + } + + explicit AString(const char *str) { + set(str, strlen(str)); + } + AString(const char *str, size_t length) { + set(str, length); + } + AString(const AString &other) { + if (other.length_) + set(other.chars_, other.length_); + else + length_ = 0; + } + AString(Moveable other) + : chars_(other->chars_.take()), + length_(other->length_) + { + other->length_ = 0; + } + + AString &operator =(const char *str) { + if (str && str[0]) { + set(str, strlen(str)); + } else { + chars_ = NULL; + length_ = 0; + } + return *this; + } + AString &operator =(const AString &other) { + if (other.length_) { + set(other.chars_, other.length_); + } else { + chars_ = NULL; + length_ = 0; + } + return *this; + } + AString &operator =(Moveable other) { + chars_ = other->chars_.take(); + length_ = other->length_; + other->length_ = 0; + return *this; + } + + int compare(const char *str) const { + return strcmp(chars(), str); + } + int compare(const AString &other) const { + return strcmp(chars(), other.chars()); + } + bool operator ==(const AString &other) const { + return other.length() == length() && + memcmp(other.chars(), chars(), length()) == 0; + } + + char operator [](size_t index) const { + assert(index < length()); + return chars()[index]; + } + + size_t length() const { + return length_; + } + + const char *chars() const { + if (!chars_) + return ""; + return chars_; + } + + private: + void set(const char *str, size_t length) { + chars_ = new char[length + 1]; + length_ = length; + memcpy(chars_, str, length); + chars_[length] = '\0'; + } + + private: + AutoArray chars_; + size_t length_; +}; + +} + +#endif // _include_amtl_string_h_ diff --git a/public/amtl/am-thread-posix.h b/public/amtl/am-thread-posix.h new file mode 100644 index 00000000..bc654261 --- /dev/null +++ b/public/amtl/am-thread-posix.h @@ -0,0 +1,213 @@ +// 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_thread_posix_h_ +#define _include_amtl_thread_posix_h_ + +#include +#include +#include +#include +#if defined(__linux__) +# include +#endif +#if defined(__APPLE__) +# include +#endif + +namespace ke { + +class Mutex : public Lockable +{ + public: + Mutex() { +#if !defined(NDEBUG) + int rv = +#endif + pthread_mutex_init(&mutex_, NULL); + assert(rv == 0); + } + ~Mutex() { + pthread_mutex_destroy(&mutex_); + } + + bool DoTryLock() KE_OVERRIDE { + return pthread_mutex_trylock(&mutex_) == 0; + } + + void DoLock() KE_OVERRIDE { + pthread_mutex_lock(&mutex_); + } + + void DoUnlock() KE_OVERRIDE { + pthread_mutex_unlock(&mutex_); + } + + pthread_mutex_t *raw() { + return &mutex_; + } + + private: + pthread_mutex_t mutex_; +}; + +// Currently, this class only supports single-listener CVs. +class ConditionVariable : public Lockable +{ + public: + ConditionVariable() { +#if !defined(NDEBUG) + int rv = +#endif + pthread_cond_init(&cv_, NULL); + assert(rv == 0); + } + ~ConditionVariable() { + pthread_cond_destroy(&cv_); + } + + bool DoTryLock() KE_OVERRIDE { + return mutex_.DoTryLock(); + } + void DoLock() KE_OVERRIDE { + mutex_.DoLock(); + } + void DoUnlock() KE_OVERRIDE { + mutex_.DoUnlock(); + } + + void Notify() { + AssertCurrentThreadOwns(); + pthread_cond_signal(&cv_); + } + + WaitResult Wait(size_t timeout_ms) { + AssertCurrentThreadOwns(); + +#if defined(__linux__) + struct timespec ts; + if (clock_gettime(CLOCK_REALTIME, &ts) == -1) + return Wait_Error; +#else + struct timeval tv; + gettimeofday(&tv, NULL); + + struct timespec ts; + ts.tv_sec = tv.tv_sec; + ts.tv_nsec = tv.tv_usec * 1000; +#endif + + ts.tv_sec += timeout_ms / 1000; + ts.tv_nsec += (timeout_ms % 1000) * 1000000; + if (ts.tv_nsec >= 1000000000) { + ts.tv_sec++; + ts.tv_nsec -= 1000000000; + } + + DebugSetUnlocked(); + int rv = pthread_cond_timedwait(&cv_, mutex_.raw(), &ts); + DebugSetLocked(); + + if (rv == ETIMEDOUT) + return Wait_Timeout; + if (rv == 0) + return Wait_Signaled; + return Wait_Error; + } + + WaitResult Wait() { + AssertCurrentThreadOwns(); + + DebugSetUnlocked(); + int rv = pthread_cond_wait(&cv_, mutex_.raw()); + DebugSetLocked(); + + if (rv == 0) + return Wait_Signaled; + return Wait_Error; + } + + private: + Mutex mutex_; + pthread_cond_t cv_; +}; + +class Thread +{ + struct ThreadData { + IRunnable *run; + char name[17]; + }; + public: + Thread(IRunnable *run, const char *name = NULL) { + ThreadData *data = new ThreadData; + data->run = run; + snprintf(data->name, sizeof(data->name), "%s", name ? name : ""); + + initialized_ = (pthread_create(&thread_, NULL, Main, data) == 0); + if (!initialized_) + delete data; + } + + bool Succeeded() const { + return initialized_; + } + + void Join() { + if (!Succeeded()) + return; + pthread_join(thread_, NULL); + } + + private: + static void *Main(void *arg) { + AutoPtr data((ThreadData *)arg); + + if (data->name[0]) { +#if defined(__linux__) + prctl(PR_SET_NAME, (unsigned long)data->name); +#elif defined(__APPLE__) + int (*fn)(const char *) = (int (*)(const char *))dlsym(RTLD_DEFAULT, "pthread_setname_np"); + if (fn) + fn(data->name); +#endif + } + data->run->Run(); + return NULL; + } + + private: + bool initialized_; + pthread_t thread_; +}; + +} // namespace ke + +#endif // _include_amtl_thread_posix_h_ + diff --git a/public/amtl/am-thread-utils.h b/public/amtl/am-thread-utils.h new file mode 100644 index 00000000..98db2616 --- /dev/null +++ b/public/amtl/am-thread-utils.h @@ -0,0 +1,265 @@ +// 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_threads_ +#define _include_amtl_threads_ + +#include +#if defined(_MSC_VER) +# include +# include +#else +# include +#endif +#include + +// Thread primitives for SourcePawn. +// +// Linking Requirements: +// +// OS X: None (-lpthread and -ldl are optional) +// Windows: None +// Linux: -lpthread -lrt required +// +// -- Mutexes -- +// +// A Lockable is a mutual exclusion primitive. It can be owned by at most one +// thread at a time, and ownership blocks any other thread from taking taking +// ownership. Ownership must be acquired and released on the same thread. +// Lockables are not re-entrant. +// +// While a few classes support the Lockable interface, the simplest Lockable +// object that can be instantiated is a Mutex. +// +// -- Condition Variables -- +// +// A ConditionVariable provides mutually exclusive access based on a +// condition ocurring. CVs provide two capabilities: Wait(), which will block +// until the condition is triggered, and Notify(), which signals any blocking +// thread that the condition has occurred. +// +// Condition variables have an underlying mutex lock. This lock must be +// acquired before calling Wait() or Notify(). It is automatically released +// once Wait begins blocking. This operation is atomic with respect to other +// threads and the mutex. For example, it is not possible for the lock to be +// acquired by another thread in between unlocking and blocking. Since Notify +// also requires the lock to be acquired, there is no risk of an event +// accidentally dissipating into thin air because it was sent before the other +// thread began blocking. +// +// When Wait() returns, the lock is automatically re-acquired. This operation +// is NOT atomic. In between waking up and re-acquiring the lock, another +// thread may steal the lock and issue another event. Applications must +// account for this. For example, a message pump should check that there are +// no messages left to process before blocking again. +// +// Likewise, it is also not defined whether a Signal() will have any effect +// while a thread is not waiting on the monitor. This is yet another reason +// the above paragraph is so important - applications should, under a lock of +// the condition variable - check for state changes before waiting. +// +// -- Threads -- +// +// A Thread object, when created, spawns a new thread with the given callback +// (the callbacks must implement IRunnable). Threads have one method of +// interest, Join(), which will block until the thread's execution finishes. +// Deleting a thread object will free any operating system resources associated +// with that thread, if the thread has finished executing. +// +// Threads can fail to spawn; make sure to check Succeeded(). +// + +namespace ke { + +// Abstraction for getting a unique thread identifier. Debug-only. +#if defined(_MSC_VER) +typedef DWORD ThreadId; + +static inline ThreadId GetCurrentThreadId() +{ + return ::GetCurrentThreadId(); +} +#else +typedef pthread_t ThreadId; + +static inline ThreadId GetCurrentThreadId() +{ + return pthread_self(); +} +#endif + +// Classes which use non-reentrant, same-thread lock/unlock semantics should +// inherit from this and implement DoLock/DoUnlock. +class Lockable +{ + public: + Lockable() + { +#if !defined(NDEBUG) + owner_ = 0; +#endif + } + virtual ~Lockable() { + } + + bool TryLock() { + if (DoTryLock()) { + DebugSetLocked(); + return true; + } + return false; + } + + void Lock() { + assert(Owner() != GetCurrentThreadId()); + DoLock(); + DebugSetLocked(); + } + + void Unlock() { + assert(Owner() == GetCurrentThreadId()); + DebugSetUnlocked(); + DoUnlock(); + } + + void AssertCurrentThreadOwns() const { + assert(Owner() == GetCurrentThreadId()); + } +#if !defined(NDEBUG) + bool Locked() const { + return owner_ != 0; + } + ThreadId Owner() const { + return owner_; + } +#endif + + virtual bool DoTryLock() = 0; + virtual void DoLock() = 0; + virtual void DoUnlock() = 0; + + protected: + void DebugSetUnlocked() { +#if !defined(NDEBUG) + owner_ = 0; +#endif + } + void DebugSetLocked() { +#if !defined(NDEBUG) + owner_ = GetCurrentThreadId(); +#endif + } + + protected: +#if !defined(NDEBUG) + ThreadId owner_; +#endif +}; + +// RAII for automatically locking and unlocking an object. +class AutoLock +{ + public: + AutoLock(Lockable *lock) + : lock_(lock) + { + lock_->Lock(); + } + ~AutoLock() { + lock_->Unlock(); + } + + private: + Lockable *lock_; +}; + +class AutoTryLock +{ + public: + AutoTryLock(Lockable *lock) + { + lock_ = lock->TryLock() ? lock : NULL; + } + ~AutoTryLock() { + if (lock_) + lock_->Unlock(); + } + + private: + Lockable *lock_; +}; + +// RAII for automatically unlocking and relocking an object. +class AutoUnlock +{ + public: + AutoUnlock(Lockable *lock) + : lock_(lock) + { + lock_->Unlock(); + } + ~AutoUnlock() { + lock_->Lock(); + } + + private: + Lockable *lock_; +}; + +enum WaitResult { + // Woke up because something happened. + Wait_Signaled, + + // Woke up because nothing happened and a timeout was specified. + Wait_Timeout, + + // Woke up, but because of an error. + Wait_Error +}; + +// This must be implemented in order to spawn a new thread. +class IRunnable +{ + public: + virtual ~IRunnable() { + } + + virtual void Run() = 0; +}; + +} // namespace ke + +// Include the actual thread implementations. +#if defined(_MSC_VER) +# include "am-thread-windows.h" +#else +# include "am-thread-posix.h" +#endif + +#endif // _include_amtl_threads_ diff --git a/public/amtl/am-thread-windows.h b/public/amtl/am-thread-windows.h new file mode 100644 index 00000000..c4e34ed2 --- /dev/null +++ b/public/amtl/am-thread-windows.h @@ -0,0 +1,161 @@ +// 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_thread_windows_h_ +#define _include_amtl_thread_windows_h_ + +#include + +namespace ke { + +class CriticalSection : public Lockable +{ + public: + CriticalSection() { + InitializeCriticalSection(&cs_); + } + ~CriticalSection() { + DeleteCriticalSection(&cs_); + } + + bool DoTryLock() KE_OVERRIDE { + return !!TryEnterCriticalSection(&cs_); + } + void DoLock() KE_OVERRIDE { + EnterCriticalSection(&cs_); + } + + void DoUnlock() KE_OVERRIDE { + LeaveCriticalSection(&cs_); + } + + private: + CRITICAL_SECTION cs_; +}; + +typedef CriticalSection Mutex; + +// Currently, this class only supports single-listener CVs. +class ConditionVariable : public Lockable +{ + public: + ConditionVariable() { + event_ = CreateEvent(NULL, FALSE, FALSE, NULL); + } + ~ConditionVariable() { + CloseHandle(event_); + } + + bool DoTryLock() KE_OVERRIDE { + return cs_.DoTryLock(); + } + void DoLock() KE_OVERRIDE { + cs_.DoLock(); + } + void DoUnlock() KE_OVERRIDE { + cs_.DoUnlock(); + } + + void Notify() { + AssertCurrentThreadOwns(); + SetEvent(event_); + } + + WaitResult Wait(size_t timeout_ms) { + // This will assert if the lock has not been acquired. We don't need to be + // atomic here, like pthread_cond_wait, because the event bit will stick + // until reset by a wait function. + Unlock(); + DWORD rv = WaitForSingleObject(event_, timeout_ms); + Lock(); + + if (rv == WAIT_TIMEOUT) + return Wait_Timeout; + if (rv == WAIT_FAILED) + return Wait_Error; + return Wait_Signaled; + } + + WaitResult Wait() { + return Wait(INFINITE); + } + + private: + CriticalSection cs_; + HANDLE event_; +}; + +class Thread +{ + public: + Thread(IRunnable *run, const char *name = NULL) { + thread_ = CreateThread(NULL, 0, Main, run, 0, NULL); + } + ~Thread() { + if (!thread_) + return; + CloseHandle(thread_); + } + + bool Succeeded() const { + return !!thread_; + } + + void Join() { + if (!Succeeded()) + return; + WaitForSingleObject(thread_, INFINITE); + } + + HANDLE handle() const { + return thread_; + } + + private: + static DWORD WINAPI Main(LPVOID arg) { + ((IRunnable *)arg)->Run(); + return 0; + } + +#pragma pack(push, 8) + struct ThreadNameInfo { + DWORD dwType; + LPCSTR szName; + DWORD dwThreadID; + DWORD dwFlags; + }; +#pragma pack(pop) + + private: + HANDLE thread_; +}; + +} // namespace ke + +#endif // _include_amtl_thread_windows_h_ diff --git a/public/amtl/am-utility.h b/public/amtl/am-utility.h new file mode 100644 index 00000000..afa0d44a --- /dev/null +++ b/public/amtl/am-utility.h @@ -0,0 +1,346 @@ +// 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_utility_h_ +#define _include_amtl_utility_h_ + +#include +#include +#include +//#include +#if defined(_MSC_VER) +# include +#endif +#include + +#define KE_32BIT + +#if defined(_MSC_VER) +# pragma warning(disable:4355) +#endif + +namespace ke { + +static const size_t kMallocAlignment = sizeof(void *) * 2; + +static const size_t kKB = 1024; +static const size_t kMB = 1024 * kKB; +static const size_t kGB = 1024 * kMB; + +typedef unsigned char * Address; + +template T +ReturnAndVoid(T &t) +{ + T saved = t; + t = T(); + return saved; +} + +#if __cplusplus >= 201103L +# define KE_CXX11 +#endif + +#if defined(KE_CXX11) +# define KE_DELETE = delete +# define KE_OVERRIDE = override +#else +# define KE_DELETE +# define KE_OVERRIDE +#endif + +// Wrapper that automatically deletes its contents. The pointer can be taken +// to avoid destruction. +template +class AutoPtr +{ + T *t_; + + public: + AutoPtr() + : t_(NULL) + { + } + AutoPtr(T *t) + : t_(t) + { + } + AutoPtr(Moveable > other) + { + t_ = other->t_; + other->t_ = NULL; + } + ~AutoPtr() { + delete t_; + } + T *take() { + return ReturnAndVoid(t_); + } + T *operator *() const { + return t_; + } + T *operator ->() const { + return t_; + } + operator T *() const { + return t_; + } + T *operator =(T *t) { + delete t_; + t_ = t; + return t_; + } + T *operator =(Moveable > other) { + delete t_; + t_ = other->t_; + other->t_ = NULL; + return t_; + } + bool operator !() const { + return !t_; + } + + private: + AutoPtr(const AutoPtr &other) KE_DELETE; + AutoPtr &operator =(const AutoPtr &other) KE_DELETE; +}; + +// Wrapper that automatically deletes its contents. The pointer can be taken +// to avoid destruction. +template +class AutoArray +{ + T *t_; + + public: + AutoArray() + : t_(NULL) + { + } + explicit AutoArray(T *t) + : t_(t) + { + } + ~AutoArray() { + delete [] t_; + } + T *take() { + return ReturnAndVoid(t_); + } + T *operator *() const { + return t_; + } + T &operator [](size_t index) { + return t_[index]; + } + const T &operator [](size_t index) const { + return t_[index]; + } + operator T *() const { + return t_; + } + void operator =(T *t) { + delete [] t_; + t_ = t; + } + bool operator !() const { + return !t_; + } +}; + +static inline size_t +Log2(size_t number) +{ + assert(number != 0); + +#ifdef _MSC_VER + unsigned long rval; +# ifdef _M_IX86 + _BitScanReverse(&rval, number); +# elif _M_X64 + _BitScanReverse64(&rval, number); +# endif + return rval; +#else + size_t bit; + asm("bsr %1, %0\n" + : "=r" (bit) + : "rm" (number)); + return bit; +#endif +} + +static inline size_t +FindRightmostBit(size_t number) +{ + assert(number != 0); + +#ifdef _MSC_VER + unsigned long rval; +# ifdef _M_IX86 + _BitScanForward(&rval, number); +# elif _M_X64 + _BitScanForward64(&rval, number); +# endif + return rval; +#else + size_t bit; + asm("bsf %1, %0\n" + : "=r" (bit) + : "rm" (number)); + return bit; +#endif +} + +static inline bool +IsPowerOfTwo(size_t value) +{ + if (value == 0) + return false; + return !(value & (value - 1)); +} + +static inline size_t +Align(size_t count, size_t alignment) +{ + assert(IsPowerOfTwo(alignment)); + return count + (alignment - (count % alignment)) % alignment; +} + +static inline bool +IsUint32AddSafe(unsigned a, unsigned b) +{ + if (!a || !b) + return true; + size_t log2_a = Log2(a); + size_t log2_b = Log2(b); + return (log2_a < sizeof(unsigned) * 8) && + (log2_b < sizeof(unsigned) * 8); +} + +static inline bool +IsUintPtrAddSafe(size_t a, size_t b) +{ + if (!a || !b) + return true; + size_t log2_a = Log2(a); + size_t log2_b = Log2(b); + return (log2_a < sizeof(size_t) * 8) && + (log2_b < sizeof(size_t) * 8); +} + +static inline bool +IsUint32MultiplySafe(unsigned a, unsigned b) +{ + if (a <= 1 || b <= 1) + return true; + + size_t log2_a = Log2(a); + size_t log2_b = Log2(b); + return log2_a + log2_b <= sizeof(unsigned) * 8; +} + +static inline bool +IsUintPtrMultiplySafe(size_t a, size_t b) +{ + if (a <= 1 || b <= 1) + return true; + + size_t log2_a = Log2(a); + size_t log2_b = Log2(b); + return log2_a + log2_b <= sizeof(size_t) * 8; +} + +#define ARRAY_LENGTH(array) (sizeof(array) / sizeof(array[0])) +#define STATIC_ASSERT(cond) extern int static_assert_f(int a[(cond) ? 1 : -1]) + +#define IS_ALIGNED(addr, alignment) (!(uintptr_t(addr) & ((alignment) - 1))) + +template +static inline bool +IsAligned(T addr, size_t alignment) +{ + assert(IsPowerOfTwo(alignment)); + return !(uintptr_t(addr) & (alignment - 1)); +} + +static inline Address +AlignedBase(Address addr, size_t alignment) +{ + assert(IsPowerOfTwo(alignment)); + return Address(uintptr_t(addr) & ~(alignment - 1)); +} + +template static inline T +Min(const T &t1, const T &t2) +{ + return t1 < t2 ? t1 : t2; +} + +template static inline T +Max(const T &t1, const T &t2) +{ + return t1 > t2 ? t1 : t2; +} + +template +class StorageBuffer +{ + public: + T *address() { + return reinterpret_cast(buffer_); + } + const T *address() const { + return reinterpret_cast(buffer_); + } + + private: + union { + char buffer_[sizeof(T)]; + uint64_t aligned_; + }; +}; + +#if defined(_MSC_VER) +# define KE_SIZET_FMT "%Iu" +#elif defined(__GNUC__) +# define KE_SIZET_FMT "%zu" +#else +# error "Implement format specifier string" +#endif + +#if defined(__GNUC__) +# define KE_CRITICAL_LIKELY(x) __builtin_expect(!!(x), 1) +#else +# define KE_CRITICAL_LIKELY(x) x +#endif + +} + +#endif // _include_amtl_utility_h_ diff --git a/public/amtl/am-vector.h b/public/amtl/am-vector.h new file mode 100644 index 00000000..2b0b8feb --- /dev/null +++ b/public/amtl/am-vector.h @@ -0,0 +1,239 @@ +// 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_KEIMA_TPL_CPP_VECTOR_H_ +#define _INCLUDE_KEIMA_TPL_CPP_VECTOR_H_ + +#include +#include +#include +#include +#include + +namespace ke { + +template +class Vector : public AllocPolicy +{ + public: + Vector(AllocPolicy = AllocPolicy()) + : data_(NULL), + nitems_(0), + maxsize_(0) + { + } + + Vector(Moveable > other) { + data_ = other->data_; + nitems_ = other->nitems_; + maxsize_ = other->maxsize_; + other->reset(); + } + + ~Vector() { + zap(); + } + + bool append(const T &item) { + if (!growIfNeeded(1)) + return false; + new (&data_[nitems_]) T(item); + nitems_++; + return true; + } + bool append(Moveable item) { + if (!growIfNeeded(1)) + return false; + new (&data_[nitems_]) T(item); + nitems_++; + return true; + } + void infallibleAppend(const T &item) { + assert(growIfNeeded(1)); + new (&data_[nitems_]) T(item); + nitems_++; + } + void infallibleAppend(Moveable item) { + assert(growIfNeeded(1)); + new (&data_[nitems_]) T(item); + nitems_++; + } + + // Shift all elements including |at| up by one, and insert |item| at the + // given position. If |at| is one greater than the last usable index, + // i.e. |at == length()|, then this is the same as append(). No other + // invalid indexes are allowed. + // + // This is a linear-time operation. + bool insert(size_t at, const T &item) { + if (at == length()) + return append(item); + if (!moveUp(at)) + return false; + new (&data_[at]) T(item); + return true; + } + bool insert(size_t at, Moveable item) { + if (at == length()) + return append(item); + if (!moveUp(at)) + return false; + new (&data_[at]) T(item); + return true; + } + + // Shift all elements at the given position down, removing the given + // element. This is a linear-time operation. + void remove(size_t at) { + for (size_t i = at; i < length() - 1; i++) + data_[i] = Moveable(data_[i + 1]); + pop(); + } + + T popCopy() { + T t = at(length() - 1); + pop(); + return t; + } + void pop() { + assert(nitems_); + data_[nitems_ - 1].~T(); + nitems_--; + } + bool empty() const { + return length() == 0; + } + size_t length() const { + return nitems_; + } + T& at(size_t i) { + assert(i < length()); + return data_[i]; + } + const T& at(size_t i) const { + assert(i < length()); + return data_[i]; + } + T& operator [](size_t i) { + return at(i); + } + const T& operator [](size_t i) const { + return at(i); + } + void clear() { + nitems_ = 0; + } + const T &back() const { + return at(length() - 1); + } + T &back() { + return at(length() - 1); + } + + T *buffer() const { + return data_; + } + + bool ensure(size_t desired) { + if (desired <= length()) + return true; + + return growIfNeeded(desired - length()); + } + + private: + // These are disallowed because they basically violate the failure handling + // model for AllocPolicies and are also likely to have abysmal performance. + Vector(const Vector &other) KE_DELETE; + Vector &operator =(const Vector &other) KE_DELETE; + + private: + void zap() { + for (size_t i = 0; i < nitems_; i++) + data_[i].~T(); + this->free(data_); + } + void reset() { + data_ = NULL; + nitems_ = 0; + maxsize_ = 0; + } + + bool moveUp(size_t at) { + assert(at < nitems_); + if (!append(Moveable(data_[nitems_ - 1]))) + return false; + + for (size_t i = nitems_ - 2; i > at; i--) + data_[i] = Moveable(data_[i - 1]); + return true; + } + + bool growIfNeeded(size_t needed) + { + if (!IsUintPtrAddSafe(nitems_, needed)) { + this->reportAllocationOverflow(); + return false; + } + if (nitems_ + needed < maxsize_) + return true; + + size_t new_maxsize = maxsize_ ? maxsize_ : 8; + while (nitems_ + needed > new_maxsize) { + if (!IsUintPtrMultiplySafe(new_maxsize, 2)) { + this->reportAllocationOverflow(); + return false; + } + new_maxsize *= 2; + } + + T* newdata = (T*)this->malloc(sizeof(T) * new_maxsize); + if (newdata == NULL) + return false; + for (size_t i = 0; i < nitems_; i++) { + new (&newdata[i]) T(Moveable(data_[i])); + data_[i].~T(); + } + this->free(data_); + + data_ = newdata; + maxsize_ = new_maxsize; + return true; + } + + private: + T* data_; + size_t nitems_; + size_t maxsize_; +}; + +} + +#endif /* _INCLUDE_KEIMA_TPL_CPP_VECTOR_H_ */ + From d933783629034ed1f6398206a471303a838feb1e Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sat, 3 May 2014 13:22:48 +0200 Subject: [PATCH 2/7] Switch trie_natives off KTrie --- amxmodx/msvc10/amxmodx_mm.vcxproj.filters | 3 + amxmodx/sm_stringhashmap.h | 241 +++++++++++++++++++ amxmodx/trie_natives.cpp | 188 +++++++-------- amxmodx/trie_natives.h | 273 +++++++++++----------- plugins/include/celltrie.inc | 122 +++++++++- 5 files changed, 576 insertions(+), 251 deletions(-) create mode 100644 amxmodx/sm_stringhashmap.h diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters index 88fe2a64..55bddbf8 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters @@ -323,6 +323,9 @@ Header Files + + Header Files + diff --git a/amxmodx/sm_stringhashmap.h b/amxmodx/sm_stringhashmap.h new file mode 100644 index 00000000..39f9c0b0 --- /dev/null +++ b/amxmodx/sm_stringhashmap.h @@ -0,0 +1,241 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _include_sourcemod_hashtable_h_ +#define _include_sourcemod_hashtable_h_ + +/** + * @file sm_stringhashmap.h + * + * @brief Generic Key -> Value map class, based on a hash table. The Key, in + * this case, is always an ASCII string, and the value type is a template + * parameter. This class is intended as a drop-in replacement for KTrie + * (though the retrieve() signature has been improved). + * + * If your Value type already contains the key string, consider using + * NameHashSet instead. + */ + +#include +#include +#include +#include +#include + +namespace SourceMod +{ + +namespace detail +{ + class CharsAndLength + { + public: + CharsAndLength(const char *str) + : str_(str), + length_(0) + { + int c; + uint32_t hash = 0; + while ((c = *str++)) + hash = c + (hash << 6) + (hash << 16) - hash; + hash_ = hash; + length_ = str - str_ - 1; + } + + uint32_t hash() const { + return hash_; + } + const char *chars() const { + return str_; + } + size_t length() const { + return length_; + } + + private: + const char *str_; + size_t length_; + uint32_t hash_; + }; + + struct StringHashMapPolicy + { + static inline bool matches(const CharsAndLength &lookup, const ke::AString &key) { + return lookup.length() == key.length() && + memcmp(lookup.chars(), key.chars(), key.length()) == 0; + } + static inline uint32_t hash(const CharsAndLength &key) { + return key.hash(); + } + }; +} + +template +class StringHashMap +{ + typedef detail::CharsAndLength CharsAndLength; + typedef ke::HashMap Internal; + +public: + StringHashMap() + : internal_(ke::SystemAllocatorPolicy()), + memory_used_(0) + { + if (!internal_.init()) + internal_.reportOutOfMemory(); + } + + typedef typename Internal::Result Result; + typedef typename Internal::Insert Insert; + typedef typename Internal::iterator iterator; + + // Some KTrie-like helper functions. + bool retrieve(const char *aKey, T *aResult = NULL) + { + CharsAndLength key(aKey); + Result r = internal_.find(key); + if (!r.found()) + return false; + if (aResult) + *aResult = r->value; + return true; + } + + Result find(const char *aKey) + { + CharsAndLength key(aKey); + return internal_.find(key); + } + + bool contains(const char *aKey) + { + CharsAndLength key(aKey); + Result r = internal_.find(key); + return r.found(); + } + + bool replace(const char *aKey, const T &value) + { + CharsAndLength key(aKey); + Insert i = internal_.findForAdd(key); + if (!i.found()) + { + memory_used_ += key.length() + 1; + if (!internal_.add(i)) + return false; + i->key = aKey; + } + i->value = value; + return true; + } + + bool insert(const char *aKey, const T &value) + { + CharsAndLength key(aKey); + Insert i = internal_.findForAdd(key); + if (i.found()) + return false; + if (!internal_.add(i)) + return false; + memory_used_ += key.length() + 1; + i->key = aKey; + i->value = value; + return true; + } + + bool remove(const char *aKey) + { + CharsAndLength key(aKey); + Result r = internal_.find(key); + if (!r.found()) + return false; + memory_used_ -= key.length() + 1; + internal_.remove(r); + return true; + } + + void remove(Result &r) + { + internal_.remove(r); + } + + void clear() + { + internal_.clear(); + } + + iterator iter() + { + return internal_.iter(); + } + + 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_; +}; + +} + +#endif // _include_sourcemod_hashtable_h_ diff --git a/amxmodx/trie_natives.cpp b/amxmodx/trie_natives.cpp index c4d301ce..e7a885eb 100644 --- a/amxmodx/trie_natives.cpp +++ b/amxmodx/trie_natives.cpp @@ -6,18 +6,10 @@ #include "sm_trie_tpl.h" #include "trie_natives.h" -#ifndef NDEBUG -size_t trie_free_count = 0; -size_t trie_malloc_count = 0; -#endif +using namespace SourceMod; -TrieHandles g_TrieHandles; -typedef KTrie celltrie; +TrieHandles g_TrieHandles; -void triedata_dtor(TrieData *ptr) -{ - ptr->freeCells(); -} // native Trie:TrieCreate(); static cell AMX_NATIVE_CALL TrieCreate(AMX *amx, cell *params) { @@ -27,268 +19,249 @@ static cell AMX_NATIVE_CALL TrieCreate(AMX *amx, cell *params) // native Trie::TrieClear(Trie:handle); static cell AMX_NATIVE_CALL TrieClear(AMX *amx, cell *params) { - celltrie *t = g_TrieHandles.lookup(params[1]); + CellTrie *t = g_TrieHandles.lookup(params[1]); if (t == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); return 0; } - t->run_destructor(triedata_dtor); - t->clear(); + t->map.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]); + CellTrie *t = g_TrieHandles.lookup(params[1]); if (t == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid map 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) + StringHashMap::Insert i = t->map.findForAdd(key); + if (!i.found()) { - TrieData dummy; - t->insert(key, dummy); - - td = t->retrieve(key); - - // should never, ever happen - if (td == NULL) + if (!t->map.add(i, key)) { - LogError(amx, AMX_ERR_NATIVE, "Couldn't KTrie::retrieve(), handle: %d", params[1]); return 0; } } - - td->setCell(params[3]); + + i->value.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]); + CellTrie *t = g_TrieHandles.lookup(params[1]); if (t == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); return 0; } - TrieData *td = NULL; int len; const char *key = get_amxstring(amx, params[2], 0, len); + const char *value = get_amxstring(amx, params[3], 1, len); - if ((td = t->retrieve(key)) == NULL) + StringHashMap::Insert i = t->map.findForAdd(key); + if (!i.found()) { - TrieData dummy; - t->insert(key, dummy); - td = t->retrieve(key); - - // should never, ever happen - if (td == NULL) + if (!t->map.add(i, key)) { - LogError(amx, AMX_ERR_NATIVE, "Couldn't KTrie::retrieve(), handle: %d", params[1]); return 0; } - } - - td->setString(get_amxaddr(amx, params[3])); + + i->value.setString(value); + 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[1]); + CellTrie *t = g_TrieHandles.lookup(params[1]); if (t == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid map 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) + StringHashMap::Insert i = t->map.findForAdd(key); + if (!i.found()) { - TrieData dummy; - t->insert(key, dummy); - td = t->retrieve(key); - - // should never, ever happen - if (td == NULL) + if (!t->map.add(i, key)) { - LogError(amx, AMX_ERR_NATIVE, "Couldn't KTrie::retrieve(), handle: %d", params[1]); return 0; } - } - td->setArray(get_amxaddr(amx, params[3]), params[4]); + i->value.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]); + CellTrie *t = g_TrieHandles.lookup(params[1]); if (t == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid map 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) + StringHashMap::Result r = t->map.find(key); + + if (!r.found()) { return 0; } + cell *ptr = get_amxaddr(amx, params[3]); - if (!td->getCell(ptr)) + + if (r->value.isCell()) { - return 0; + *ptr = r->value.cell_(); + return 1; } + 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]); + CellTrie *t = g_TrieHandles.lookup(params[1]); if (t == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid map 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])) + StringHashMap::Result r = t->map.find(key); + if (!r.found() || !r->value.isString()) { return 0; } + + set_amxstring_utf8(amx, params[3], r->value.chars(), r->value.arrayLength(), params[4] + 1); // + EOS + 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]); + CellTrie *t = g_TrieHandles.lookup(params[1]); if (t == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid map 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])) + size_t size = params[4]; + + StringHashMap::Result r = t->map.find(key); + if (!r.found() || !r->value.isArray()) { return 0; } + + size_t length = r->value.arrayLength(); + cell *base = r->value.array(); + + if (length <= size) + { + size = length; + } + + memcpy(ptr, base, sizeof(cell)* size); 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]); + CellTrie *t = g_TrieHandles.lookup(params[1]); if (t == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid map 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; + + return static_cast(t->map.contains(key)); } // native bool:TrieDeleteKey(Trie:handle, const key[]); static cell AMX_NATIVE_CALL TrieDeleteKey(AMX *amx, cell *params) { - celltrie *t = g_TrieHandles.lookup(params[1]); + CellTrie *t = g_TrieHandles.lookup(params[1]); if (t == NULL) { - LogError(amx, AMX_ERR_NATIVE, "Invalid trie handle provided (%d)", params[1]); + LogError(amx, AMX_ERR_NATIVE, "Invalid map 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) + StringHashMap::Result r = t->map.find(key); + if (!r.found()) { - td->freeCells(); + return 0; } - return t->remove(key) ? 1 : 0; + + t->map.remove(r); + + return 1; } //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); + 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; + 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 }, @@ -306,11 +279,6 @@ AMX_NATIVE_INFO trie_Natives[] = { "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 index fe7d7345..b575ee68 100644 --- a/amxmodx/trie_natives.h +++ b/amxmodx/trie_natives.h @@ -2,150 +2,145 @@ #define _TRIE_NATIVES_H_ #include "amxmodx.h" -#include "sm_trie_tpl.h" +#include "sm_stringhashmap.h" +#include #include "CVector.h" -#define TRIE_DATA_UNSET 0 -#define TRIE_DATA_CELL 1 -#define TRIE_DATA_STRING 2 -#define TRIE_DATA_ARRAY 3 +using namespace SourceMod; -#ifndef NDEBUG -extern size_t trie_malloc_count; -extern size_t trie_free_count; -#endif - -class TrieData +enum EntryType { -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) - { - int len = (max > m_cellcount) ? m_cellcount : max; - memcpy(out, m_data, len * sizeof(cell)); - - /* Don't truncate a multi-byte character */ - if (m_data[len - 1] & 1 << 7) - { - len -= UTIL_CheckValidChar(m_data + len - 1); - out[len] = '\0'; - } - - 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; - } + EntryType_Cell, + EntryType_CellArray, + EntryType_String, }; +class Entry +{ + struct ArrayInfo + { + size_t length; + size_t maxbytes; + + void *base() { + return this + 1; + } + }; + +public: + Entry() + : control_(0) + { + } + Entry(ke::Moveable other) + { + control_ = other->control_; + data_ = other->data_; + other->control_ = 0; + } + ~Entry() + { + free(raw()); + } + + void setCell(cell value) { + setType(EntryType_Cell); + data_ = value; + } + void setArray(cell *cells, size_t length) { + ArrayInfo *array = ensureArray(length * sizeof(cell)); + array->length = length; + memcpy(array->base(), cells, length * sizeof(cell)); + 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 *array() const { + //assert(isArray()); + return reinterpret_cast(raw()->base()); + } + char *chars() const { + //assert(isString()); + return reinterpret_cast(raw()->base()); + } + cell 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 data_; +}; + +struct CellTrie : public ke::Refcounted +{ + StringHashMap map; +}; + +template class TrieHandles { private: - CVector< KTrie< TrieData > *> m_tries; + CVector m_tries; public: TrieHandles() { } @@ -166,7 +161,7 @@ public: m_tries.clear(); } - KTrie *lookup(int handle) + T *lookup(int handle) { handle--; @@ -184,12 +179,12 @@ public: if (m_tries[i] == NULL) { // reuse handle - m_tries[i] = new KTrie; + m_tries[i] = new T; return static_cast(i) + 1; } } - m_tries.push_back(new KTrie); + m_tries.push_back(new T); return m_tries.size(); } bool destroy(int handle) @@ -213,7 +208,7 @@ public: }; -extern TrieHandles g_TrieHandles; +extern TrieHandles g_TrieHandles; extern AMX_NATIVE_INFO trie_Natives[]; #endif diff --git a/plugins/include/celltrie.inc b/plugins/include/celltrie.inc index 2300c6e9..87b292ea 100644 --- a/plugins/include/celltrie.inc +++ b/plugins/include/celltrie.inc @@ -1,5 +1,5 @@ #if defined _celltrie_included -#endinput + #endinput #endif #define _celltrie_included @@ -8,19 +8,137 @@ enum Trie Invalid_Trie = 0 }; - +/** + * Creates a hash map. A hash map is a container that can map strings (called + * "keys") to arbitrary values (cells, arrays, or strings). Keys in a hash map + * 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 AMX Mod X 1.8.3, 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 TrieDestroy(). + */ native Trie:TrieCreate(); + +/** + * Clears all entries from a Map. + * + * @param handle Map Handle. + * + * @error Invalid Handle. + */ native TrieClear(Trie:handle); +/** + * Sets a value in a hash map, either inserting a new entry or replacing an old one. + * + * @param handle Map Handle. + * @param key Key string. + * @param value Value to store at this key. + * + * @return True on success, false on failure. + * @error Invalid Handle. + */ native TrieSetCell(Trie:handle, const key[], any:value); + +/** + * Sets a string value in a Map, either inserting a new entry or replacing an old one. + * + * @param handle Map Handle. + * @param key Key string. + * @param value String to store. + * + * @return True on success, false on failure. + * @error Invalid Handle. + */ native TrieSetString(Trie:handle, const key[], const value[]); + +/** + * Sets an array value in a Map, either inserting a new entry or replacing an old one. + * + * @param handle Map Handle. + * @param key Key string. + * @param buffer Array to store. + * @param size Number of items in the array. + * + * @return True on success, false on failure. + * @error Invalid Handle. + */ native TrieSetArray(Trie:handle, const key[], const any:buffer[], size); +/** + * Retrieves a value in a Map. + * + * @param handle 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:TrieGetCell(Trie:handle, const key[], &any:value); + +/** + * Retrieves a string in a Map. + * + * @param handle Map Handle. + * @param key Key string. + * @param output Buffer to store value. + * @param outputsize Maximum size of string 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). + * @error Invalid Handle. + */ native bool:TrieGetString(Trie:handle, const key[], output[], outputsize); + +/** + * Retrieves an array in a Map. + * + * @param handle Map Handle. + * @param key Key string. + * @param output Buffer to store array. + * @param outputsize Maximum size of array 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). + * @error Invalid Handle. + */ native bool:TrieGetArray(Trie:handle, const key[], any:output[], outputsize); +/** + * Removes a key entry from a Map. + * + * @param handle Map Handle. + * @param key Key string. + * + * @return True on success, false if the value was never set. + * @error Invalid Handle. + */ native bool:TrieDeleteKey(Trie:handle, const key[]); + +/** + * Checks a key entry existence from a Map. + * + * @param handle Map Handle. + * @param key Key string. + * + * @return True on success, false if the value was never set. + * @error Invalid Handle. + */ native bool:TrieKeyExists(Trie:handle, const key[]); + +/** + * Destroys a Map. + * + * @param handle Map Handle. + * + * @return True on success, false if the value was never set. + * @error Invalid Handle. + */ native TrieDestroy(&Trie:handle); From 12a886453214c59b3d3cde00f3cdf27598401bc8 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sat, 3 May 2014 14:28:12 +0200 Subject: [PATCH 3/7] Remove refcounting --- amxmodx/trie_natives.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/amxmodx/trie_natives.h b/amxmodx/trie_natives.h index b575ee68..d0da1d1e 100644 --- a/amxmodx/trie_natives.h +++ b/amxmodx/trie_natives.h @@ -3,7 +3,6 @@ #include "amxmodx.h" #include "sm_stringhashmap.h" -#include #include "CVector.h" using namespace SourceMod; @@ -131,7 +130,7 @@ private: cell data_; }; -struct CellTrie : public ke::Refcounted +struct CellTrie { StringHashMap map; }; From f3bce9ecfd05c3fb4166eb51e27b04031313341c Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sat, 3 May 2014 16:09:31 +0200 Subject: [PATCH 4/7] Add TrieGetSize native. --- amxmodx/trie_natives.cpp | 16 +++++++++++++++ plugins/include/celltrie.inc | 9 +++++++++ plugins/testsuite/trietest.sma | 36 +++++++++++++--------------------- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/amxmodx/trie_natives.cpp b/amxmodx/trie_natives.cpp index e7a885eb..9b073c4b 100644 --- a/amxmodx/trie_natives.cpp +++ b/amxmodx/trie_natives.cpp @@ -262,6 +262,20 @@ static cell AMX_NATIVE_CALL TrieDestroy(AMX *amx, cell *params) return 0; } +// native TrieGetSize(Trie:handle); +static cell AMX_NATIVE_CALL TrieGetSize(AMX *amx, cell *params) +{ + CellTrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + return t->map.elements(); +} + AMX_NATIVE_INFO trie_Natives[] = { { "TrieCreate", TrieCreate }, @@ -279,6 +293,8 @@ AMX_NATIVE_INFO trie_Natives[] = { "TrieKeyExists", TrieKeyExists }, { "TrieDestroy", TrieDestroy }, + { "TrieGetSize", TrieGetSize }, + { NULL, NULL } }; diff --git a/plugins/include/celltrie.inc b/plugins/include/celltrie.inc index 87b292ea..e89e7037 100644 --- a/plugins/include/celltrie.inc +++ b/plugins/include/celltrie.inc @@ -142,3 +142,12 @@ native bool:TrieKeyExists(Trie:handle, const key[]); */ native TrieDestroy(&Trie:handle); +/** + * Retrieves the number of elements in a map. + * + * @param handle Map Handle. + * + * @return Number of elements in the trie. + * @error Invalid Handle. + */ +native TrieGetSize(Trie:handle); diff --git a/plugins/testsuite/trietest.sma b/plugins/testsuite/trietest.sma index 42f122ac..c51d4a7b 100644 --- a/plugins/testsuite/trietest.sma +++ b/plugins/testsuite/trietest.sma @@ -1,10 +1,5 @@ #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() @@ -29,16 +24,7 @@ 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; @@ -53,7 +39,11 @@ public trietest() for (new i = 0; i < 100; i++) { formatex(key, charsmax(key), "K%dK", i); - TrieSetCell(t, key, i); + if (!TrieSetCell(t, key, i)) + { + server_print("TrieGetCell(%d, '%s', %d) failed", t, key, i); + ok = false; + } } for (new i = 0; i < 100; i++) @@ -65,17 +55,22 @@ public trietest() 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; } - } + + // Check size is 100. + if (TrieGetSize(t) != 100) + { + ok = false; + server_print("Map size mismatch, expected: %d got: %d", 100, TrieGetSize(t)); + } + if (ok) pass("Cell tests"); - else fail("Cell tests"); @@ -126,8 +121,6 @@ public trietest() TrieDestroy(t); - check_frees(); - t = TrieCreate(); ok = true; for (new i = 0; i < 1000; i++) @@ -159,10 +152,9 @@ public trietest() else fail("Exists/Delete"); - check_frees(); TrieClear(t); TrieDestroy(t); - check_frees(); + done(); } From b6cd247d1206d82c27a65ef48c8aeedb01c99ed2 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sat, 3 May 2014 22:36:40 +0200 Subject: [PATCH 5/7] Add new 'replace' and 'size' paramaters in some natives. Fix some typos Update the test plugin --- amxmodx/trie_natives.cpp | 97 ++++++++++++++++++---- amxmodx/trie_natives.h | 14 ++-- plugins/include/celltrie.inc | 15 ++-- plugins/testsuite/trietest.sma | 142 ++++++++++++++++++++++++++++----- 4 files changed, 218 insertions(+), 50 deletions(-) diff --git a/amxmodx/trie_natives.cpp b/amxmodx/trie_natives.cpp index 9b073c4b..c9347a3b 100644 --- a/amxmodx/trie_natives.cpp +++ b/amxmodx/trie_natives.cpp @@ -29,7 +29,8 @@ static cell AMX_NATIVE_CALL TrieClear(AMX *amx, cell *params) t->map.clear(); return 1; } -// native TrieSetCell(Trie:handle, const key[], any:value); + +// native TrieSetCell(Trie:handle, const key[], any:value, bool:replace = true); static cell AMX_NATIVE_CALL TrieSetCell(AMX *amx, cell *params) { CellTrie *t = g_TrieHandles.lookup(params[1]); @@ -50,13 +51,22 @@ static cell AMX_NATIVE_CALL TrieSetCell(AMX *amx, cell *params) { return 0; } + + i->value.setCell(params[3]); + return 1; + } + + // Old plugin doesn't have 'replace' parameter. + if (*params / sizeof(cell) == 4 && !params[4]) + { + return 0; } i->value.setCell(params[3]); - return 1; } -// native TrieSetString(Trie:handle, const key[], const data[]); + +// native TrieSetString(Trie:handle, const key[], const data[], bool:replace = true); static cell AMX_NATIVE_CALL TrieSetString(AMX *amx, cell *params) { CellTrie *t = g_TrieHandles.lookup(params[1]); @@ -78,13 +88,22 @@ static cell AMX_NATIVE_CALL TrieSetString(AMX *amx, cell *params) { return 0; } + + i->value.setString(value); + return 1; + } + + // Old plugin doesn't have 'replace' parameter. + if (*params / sizeof(cell) == 4 && !params[4]) + { + return 0; } i->value.setString(value); - return 1; } -// native TrieSetArray(Trie:handle, const key[], const any:buffer[], buffsize) + +// native TrieSetArray(Trie:handle, const key[], const any:buffer[], buffsize, bool:replace = true) static cell AMX_NATIVE_CALL TrieSetArray(AMX *amx, cell *params) { CellTrie *t = g_TrieHandles.lookup(params[1]); @@ -95,8 +114,15 @@ static cell AMX_NATIVE_CALL TrieSetArray(AMX *amx, cell *params) return 0; } + if (params[4] < 0) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid array size (%d)", params[4]); + return 0; + } + int len; const char *key = get_amxstring(amx, params[2], 0, len); + cell *ptr = get_amxaddr(amx, params[3]); StringHashMap::Insert i = t->map.findForAdd(key); if (!i.found()) @@ -105,12 +131,22 @@ static cell AMX_NATIVE_CALL TrieSetArray(AMX *amx, cell *params) { return 0; } + + i->key = key; + i->value.setArray(ptr, params[4]); + return 1; } - i->value.setArray(get_amxaddr(amx, params[3]), params[4]); + // Old plugin doesn't have 'replace' parameter. + if (*params / sizeof(cell) == 4 && !params[5]) + { + return 0; + } + i->value.setArray(ptr, params[4]); return 1; } + // native bool:TrieGetCell(Trie:handle, const key[], &any:value); static cell AMX_NATIVE_CALL TrieGetCell(AMX *amx, cell *params) { @@ -140,9 +176,10 @@ static cell AMX_NATIVE_CALL TrieGetCell(AMX *amx, cell *params) return 1; } - return 1; + return 0; } -// native bool:TrieGetString(Trie:handle, const key[], buff[], len); + +// native bool:TrieGetString(Trie:handle, const key[], buff[], len, &size = 0); static cell AMX_NATIVE_CALL TrieGetString(AMX *amx, cell *params) { CellTrie *t = g_TrieHandles.lookup(params[1]); @@ -153,8 +190,15 @@ static cell AMX_NATIVE_CALL TrieGetString(AMX *amx, cell *params) return 0; } + if (params[4] < 0) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid buffer size (%d)", params[4]); + return 0; + } + int len; const char *key = get_amxstring(amx, params[2], 0, len); + cell *pSize = get_amxaddr(amx, params[5]); StringHashMap::Result r = t->map.find(key); if (!r.found() || !r->value.isString()) @@ -162,11 +206,12 @@ static cell AMX_NATIVE_CALL TrieGetString(AMX *amx, cell *params) return 0; } - set_amxstring_utf8(amx, params[3], r->value.chars(), r->value.arrayLength(), params[4] + 1); // + EOS + *pSize = (cell)set_amxstring_utf8(amx, params[3], r->value.chars(), strlen(r->value.chars()), params[4] + 1); // + EOS return 1; } -// native bool:TrieGetArray(Trie:handle, const key[], any:buff[], len); + +// native bool:TrieGetArray(Trie:handle, const key[], any:buff[], len, &size = 0); static cell AMX_NATIVE_CALL TrieGetArray(AMX *amx, cell *params) { CellTrie *t = g_TrieHandles.lookup(params[1]); @@ -177,10 +222,16 @@ static cell AMX_NATIVE_CALL TrieGetArray(AMX *amx, cell *params) return 0; } + if (params[4] < 0) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid array size (%d)", params[4]); + return 0; + } + int len; const char *key = get_amxstring(amx, params[2], 0, len); - cell *ptr = get_amxaddr(amx, params[3]); - size_t size = params[4]; + cell *pValue = get_amxaddr(amx, params[3]); + cell *pSize = get_amxaddr(amx, params[5]); StringHashMap::Result r = t->map.find(key); if (!r.found() || !r->value.isArray()) @@ -188,17 +239,29 @@ static cell AMX_NATIVE_CALL TrieGetArray(AMX *amx, cell *params) return 0; } + if (!r->value.array()) + { + *pSize = 0; + return 1; + } + + if (!params[4]) + { + return 0; + } + size_t length = r->value.arrayLength(); cell *base = r->value.array(); - if (length <= size) - { - size = length; - } + if (length > size_t(params[4])) + *pSize = params[4]; + else + *pSize = length; - memcpy(ptr, base, sizeof(cell)* size); + memcpy(pValue, base, sizeof(cell) * pSize[0]); return 1; } + // native bool:TrieKeyExists(Trie:handle, const key[]); static cell AMX_NATIVE_CALL TrieKeyExists(AMX *amx, cell *params) { diff --git a/amxmodx/trie_natives.h b/amxmodx/trie_natives.h index d0da1d1e..ca2911e4 100644 --- a/amxmodx/trie_natives.h +++ b/amxmodx/trie_natives.h @@ -61,19 +61,19 @@ public: } size_t arrayLength() const { - //assert(isArray()); + assert(isArray()); return raw()->length; } cell *array() const { - //assert(isArray()); + assert(isArray()); return reinterpret_cast(raw()->base()); } char *chars() const { - //assert(isString()); + assert(isString()); return reinterpret_cast(raw()->base()); } cell cell_() const { - //assert(isCell()); + assert(isCell()); return data_; } @@ -110,13 +110,13 @@ private: } void setType(EntryType aType) { control_ = uintptr_t(raw()) | uintptr_t(aType); - //assert(type() == aType); + assert(type() == aType); } void setTypeAndPointer(EntryType aType, ArrayInfo *ptr) { // malloc() should guarantee 8-byte alignment at worst - //assert((uintptr_t(ptr) & 0x3) == 0); + assert((uintptr_t(ptr) & 0x3) == 0); control_ = uintptr_t(ptr) | uintptr_t(aType); - //assert(type() == aType); + assert(type() == aType); } EntryType type() const { return (EntryType)(control_ & 0x3); diff --git a/plugins/include/celltrie.inc b/plugins/include/celltrie.inc index e89e7037..c6424290 100644 --- a/plugins/include/celltrie.inc +++ b/plugins/include/celltrie.inc @@ -39,11 +39,12 @@ native TrieClear(Trie:handle); * @param handle 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 TrieSetCell(Trie:handle, const key[], any:value); +native TrieSetCell(Trie:handle, const key[], any:value, bool:replace = true); /** * Sets a string value in a Map, either inserting a new entry or replacing an old one. @@ -51,11 +52,12 @@ native TrieSetCell(Trie:handle, const key[], any:value); * @param handle 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 TrieSetString(Trie:handle, const key[], const value[]); +native TrieSetString(Trie:handle, const key[], const value[], bool:replace = true); /** * Sets an array value in a Map, either inserting a new entry or replacing an old one. @@ -64,11 +66,12 @@ native TrieSetString(Trie:handle, const key[], const value[]); * @param key Key string. * @param buffer Array to store. * @param size 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. * @error Invalid Handle. */ -native TrieSetArray(Trie:handle, const key[], const any:buffer[], size); +native TrieSetArray(Trie:handle, const key[], const any:buffer[], size, bool:replace = true); /** * Retrieves a value in a Map. @@ -89,12 +92,13 @@ native bool:TrieGetCell(Trie:handle, const key[], &any:value); * @param key Key string. * @param output Buffer to store value. * @param outputsize 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). * @error Invalid Handle. */ -native bool:TrieGetString(Trie:handle, const key[], output[], outputsize); +native bool:TrieGetString(Trie:handle, const key[], output[], outputsize, &size = 0); /** * Retrieves an array in a Map. @@ -103,12 +107,13 @@ native bool:TrieGetString(Trie:handle, const key[], output[], outputsize); * @param key Key string. * @param output Buffer to store array. * @param outputsize 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). * @error Invalid Handle. */ -native bool:TrieGetArray(Trie:handle, const key[], any:output[], outputsize); +native bool:TrieGetArray(Trie:handle, const key[], any:output[], outputsize, &size = 0); /** * Removes a key entry from a Map. diff --git a/plugins/testsuite/trietest.sma b/plugins/testsuite/trietest.sma index c51d4a7b..61ea727d 100644 --- a/plugins/testsuite/trietest.sma +++ b/plugins/testsuite/trietest.sma @@ -62,30 +62,57 @@ public trietest() } } + // Setting K42K without replace should fail. + new value; + if (TrieSetCell(t, "K42K", 999, false)) { ok = false; server_print("set trie K42K should fail"); } + if (!TrieGetCell(t, "K42K", value) || value != 42) { ok = false; server_print("value at K42K not correct"); } + if (!TrieSetCell(t, "K42K", 999)) { ok = false; server_print("set trie K42K = 999 should succeed"); } + if (!TrieGetCell(t, "K42K", value) || value != 999) { ok = false; server_print("value at K42K not correct"); } + // Check size is 100. if (TrieGetSize(t) != 100) { - ok = false; - server_print("Map size mismatch, expected: %d got: %d", 100, TrieGetSize(t)); + server_print("Map size mismatch (1), expected: %d got: %d", 100, TrieGetSize(t)); ok = false; } + if (TrieGetCell(t, "cat", value)) + { + server_print("trie should not have a cat."); ok = false; + } + + // Check that "K42K" is not a string or array. + new array[32]; + new string[32]; + if (TrieGetArray(t, "K42K", array, sizeof(array)) || + TrieGetString(t, "K42K", string, charsmax(string))) + { + server_print("entry K42K should not be an array or string"); ok = false; + } + + TrieClear(t); + + if (TrieGetSize(t) != 0) + { + server_print("Map size mismatch (2), expected: %d got: %d", 0, TrieGetSize(t)); ok = false; + } + + TrieDestroy(t); + if (ok) pass("Cell tests"); else 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]; @@ -100,29 +127,107 @@ public trietest() static val[32]; static exp[32]; formatex(exp, charsmax(exp), "V%dV", i); - if (!TrieGetString(t, key, val, charsmax(val))) + new size; + + if (!TrieGetString(t, key, val, charsmax(val), size)) { 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 (size != strlen(exp)) + { + server_print("returned size mismatch, key: '%s' expected: '%s' got: '%s'", key, strlen(exp), size); + ok = false; + } } + + if (TrieGetCell(t, "K42K", value) || + TrieGetArray(t, "K42K", array, sizeof(array))) + { + server_print("entry K42K should not be an array or string"); ok = false; + } + + if (TrieGetString(t, "cat", string, charsmax(string))) + { + server_print("trie should not have a cat."); ok = false; + } + if (ok) pass("String tests"); - else fail("String tests"); - TrieDestroy(t); - - t = TrieCreate(); + ok = true; + + new data[5] = { 93, 1, 2, 3, 4 }; + + if (!TrieSetArray(t, "K42K", data, sizeof data)) + { + server_print("K42K should be a string."); ok = false; + } + if (!TrieGetArray(t, "K42K", array, sizeof(array))) + { + server_print("K42K should be V42V."); ok = false; + } + for (new i = 0; i < sizeof data; i++) + { + if (data[i] != array[i]) + { + server_print("K42K slot %d should be %d, got %d", i, data[i], array[i]); ok = false; + } + } + if (TrieGetCell(t, "K42K", value) || + TrieGetString(t, "K42K", string, charsmax(string))) + { + server_print("entry K42K should not be an array or string"); ok = false; + } + if (!TrieSetArray(t, "K42K", data, 1)) + { + server_print("couldn't set K42K to 1-entry array"); ok = false; + } + if (!TrieGetArray(t, "K42K", array, sizeof(array), value)) + { + server_print("couldn't fetch 1-entry array"); ok = false; + } + if (value != 1) + { + server_print("array size mismatch (%d, expected %d)", value, 1); ok = false; + } + + if (ok) + pass("Array tests"); + else + fail("Array tests"); + + + ok = true; + + // Remove "K42K". + if (!TrieDeleteKey(t, "K42K")) + { + server_print("K42K should have been removed"); ok = false; + } + if (TrieDeleteKey(t, "K42K")) + { + server_print("K42K should not exist"); ok =false; + } + if (TrieGetCell(t, "K42K", value) || + TrieGetArray(t, "K42K", array, sizeof(array)) || + TrieGetString(t, "K42K", string, charsmax(string))) + { + server_print("map should not have a K42K"); ok = false; + } + + TrieDestroy(t); + + t = TrieCreate(); + for (new i = 0; i < 1000; i++) { formatex(key, charsmax(key), "!%d!", i); @@ -134,21 +239,16 @@ public trietest() if (!TrieKeyExists(t, key)) { - ok = false; - server_print("Key '%s' does not exist", key); + server_print("Key '%s' does not exist", key); ok = false; } - else + else if (!TrieDeleteKey(t, key)) { - if (!TrieDeleteKey(t, key)) - { - server_print("Key '%s' could not be deleted", key); - ok = false; - } + server_print("Key '%s' could not be deleted", key); ok = false; } } + if (ok) pass("Exists/Delete"); - else fail("Exists/Delete"); From 9ac3763267b4e5f3d67087d98957bfbaba667768 Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sat, 3 May 2014 23:21:56 +0200 Subject: [PATCH 6/7] Add iterators natives --- amxmodx/meta_api.cpp | 1 + amxmodx/msvc10/amxmodx_mm.vcxproj | 1 + amxmodx/msvc10/amxmodx_mm.vcxproj.filters | 3 + amxmodx/sm_memtable.h | 170 ++++++++++++++++++++++ amxmodx/trie_natives.cpp | 111 +++++++++++++- amxmodx/trie_natives.h | 18 +++ plugins/include/celltrie.inc | 63 ++++++++ plugins/testsuite/trietest.sma | 98 +++++++++---- 8 files changed, 437 insertions(+), 28 deletions(-) create mode 100644 amxmodx/sm_memtable.h diff --git a/amxmodx/meta_api.cpp b/amxmodx/meta_api.cpp index 204f4069..f5fd25f5 100755 --- a/amxmodx/meta_api.cpp +++ b/amxmodx/meta_api.cpp @@ -414,6 +414,7 @@ int C_Spawn(edict_t *pent) VectorHolder.clear(); g_TrieHandles.clear(); + g_TrieSnapshotHandles.clear(); g_DataPackHandles.clear(); char map_pluginsfile_path[256]; diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj b/amxmodx/msvc10/amxmodx_mm.vcxproj index a030557a..5931e690 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj @@ -389,6 +389,7 @@ + diff --git a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters index 55bddbf8..089c5375 100644 --- a/amxmodx/msvc10/amxmodx_mm.vcxproj.filters +++ b/amxmodx/msvc10/amxmodx_mm.vcxproj.filters @@ -326,6 +326,9 @@ Header Files + + Header Files + diff --git a/amxmodx/sm_memtable.h b/amxmodx/sm_memtable.h new file mode 100644 index 00000000..d7e2d90d --- /dev/null +++ b/amxmodx/sm_memtable.h @@ -0,0 +1,170 @@ +/** + * vim: set ts=4 sw=4 tw=99 noet : + * ============================================================================= + * SourceMod + * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. + * ============================================================================= + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, version 3.0, as published by the + * Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + * + * As a special exception, AlliedModders LLC gives you permission to link the + * code of this program (as well as its derivative works) to "Half-Life 2," the + * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software + * by the Valve Corporation. You must obey the GNU General Public License in + * all respects for all other code used. Additionally, AlliedModders LLC grants + * this exception to all derivative works. AlliedModders LLC defines further + * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), + * or . + * + * Version: $Id$ + */ + +#ifndef _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ +#define _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ + +#include +#include + +class BaseMemTable +{ +public: + BaseMemTable(unsigned int init_size) + { + membase = (unsigned char *)malloc(init_size); + size = init_size; + tail = 0; + } + ~BaseMemTable() + { + free(membase); + membase = NULL; + } +public: + /** + * Allocates 'size' bytes of memory. + * Optionally outputs the address through 'addr'. + * Returns an index >= 0 on success, < 0 on failure. + */ + int CreateMem(unsigned int addsize, void **addr) + { + int idx = (int)tail; + + while (tail + addsize >= size) { + size *= 2; + membase = (unsigned char *)realloc(membase, size); + } + + tail += addsize; + if (addr) + *addr = (void *)&membase[idx]; + + return idx; + } + + /** + * Given an index into the memory table, returns its address. + * Returns NULL if invalid. + */ + void *GetAddress(int index) + { + if (index < 0 || (unsigned int)index >= tail) + return NULL; + return &membase[index]; + } + + /** + * Scraps the memory table. For caching purposes, the memory + * is not freed, however subsequent calls to CreateMem() will + * begin at the first index again. + */ + void Reset() + { + tail = 0; + } + + inline unsigned int GetMemUsage() + { + return size; + } + + inline unsigned int GetActualMemUsed() + { + return tail; + } + +private: + unsigned char *membase; + unsigned int size; + unsigned int tail; +}; + +class BaseStringTable +{ +public: + BaseStringTable(unsigned int init_size) : m_table(init_size) + { + } +public: + /** + * Adds a string to the string table and returns its index. + */ + int AddString(const char *string) + { + return AddString(string, strlen(string)); + } + + /** + * Adds a string to the string table and returns its index. + */ + int AddString(const char *string, size_t length) + { + size_t len = length + 1; + int idx; + char *addr; + + idx = m_table.CreateMem(len, (void **)&addr); + memcpy(addr, string, length + 1); + return idx; + } + + /** + * Given an index into the string table, returns the associated string. + */ + inline const char *GetString(int str) + { + return (const char *)m_table.GetAddress(str); + } + + /** + * Scraps the string table. For caching purposes, the memory + * is not freed, however subsequent calls to AddString() will + * begin at the first index again. + */ + void Reset() + { + m_table.Reset(); + } + + /** + * Returns the parent BaseMemTable that this string table uses. + */ + inline BaseMemTable *GetMemTable() + { + return &m_table; + } +private: + BaseMemTable m_table; +}; + +#endif //_INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_ + diff --git a/amxmodx/trie_natives.cpp b/amxmodx/trie_natives.cpp index c9347a3b..a5bac284 100644 --- a/amxmodx/trie_natives.cpp +++ b/amxmodx/trie_natives.cpp @@ -9,6 +9,7 @@ using namespace SourceMod; TrieHandles g_TrieHandles; +TrieHandles g_TrieSnapshotHandles; // native Trie:TrieCreate(); static cell AMX_NATIVE_CALL TrieCreate(AMX *amx, cell *params) @@ -304,6 +305,7 @@ static cell AMX_NATIVE_CALL TrieDeleteKey(AMX *amx, cell *params) return 1; } + //native TrieDestroy(&Trie:handle) static cell AMX_NATIVE_CALL TrieDestroy(AMX *amx, cell *params) { @@ -339,6 +341,108 @@ static cell AMX_NATIVE_CALL TrieGetSize(AMX *amx, cell *params) return t->map.elements(); } +static cell AMX_NATIVE_CALL TrieSnapshotCreate(AMX *amx, cell *params) +{ + CellTrie *t = g_TrieHandles.lookup(params[1]); + + if (t == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]); + return 0; + } + + int index = g_TrieSnapshotHandles.create(); + TrieSnapshot *snapshot = g_TrieSnapshotHandles.lookup(index); + snapshot->length = t->map.elements(); + snapshot->keys = new int[snapshot->length]; + + size_t i = 0; + for (StringHashMap::iterator iter = t->map.iter(); !iter.empty(); iter.next(), i++) + { + snapshot->keys[i] = snapshot->strings.AddString(iter->key.chars(), iter->key.length()); + } + assert(i == snapshot->length); + + return static_cast(index); +} + +static cell AMX_NATIVE_CALL TrieSnapshotLength(AMX *amx, cell *params) +{ + TrieSnapshot *snapshot = g_TrieSnapshotHandles.lookup(params[1]); + + if (snapshot == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid snapshot handle provided (%d)", params[1]); + return 0; + } + + return snapshot->length; +} + +static cell AMX_NATIVE_CALL TrieSnapshotKeyBufferSize(AMX *amx, cell *params) +{ + TrieSnapshot *snapshot = g_TrieSnapshotHandles.lookup(params[1]); + + if (snapshot == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid snapshot handle provided (%d)", params[1]); + return 0; + } + + unsigned index = params[2]; + + if (index >= snapshot->length) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid index %d", index); + return 0; + } + + return strlen(snapshot->strings.GetString(snapshot->keys[index])) + 1; +} + +static cell AMX_NATIVE_CALL TrieSnapshotGetKey(AMX *amx, cell *params) +{ + TrieSnapshot *snapshot = g_TrieSnapshotHandles.lookup(params[1]); + + if (snapshot == NULL) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid snapshot handle provided (%d)", params[1]); + return 0; + } + + unsigned index = params[2]; + + if (index >= snapshot->length) + { + LogError(amx, AMX_ERR_NATIVE, "Invalid index %d", index); + return 0; + } + + const char *str = snapshot->strings.GetString(snapshot->keys[index]); + return set_amxstring_utf8(amx, params[3], str, strlen(str), params[4] + 1); +} + +//native TrieSnapshotDestroy(&Snapshot:handle) +static cell AMX_NATIVE_CALL TrieSnapshotDestroy(AMX *amx, cell *params) +{ + cell *ptr = get_amxaddr(amx, params[1]); + + TrieSnapshot *t = g_TrieSnapshotHandles.lookup(*ptr); + + if (t == NULL) + { + return 0; + } + + if (g_TrieSnapshotHandles.destroy(*ptr)) + { + *ptr = 0; + return 1; + } + + return 0; +} + AMX_NATIVE_INFO trie_Natives[] = { { "TrieCreate", TrieCreate }, @@ -355,9 +459,14 @@ AMX_NATIVE_INFO trie_Natives[] = { "TrieDeleteKey", TrieDeleteKey }, { "TrieKeyExists", TrieKeyExists }, { "TrieDestroy", TrieDestroy }, - { "TrieGetSize", TrieGetSize }, + { "TrieSnapshotCreate", TrieSnapshotCreate }, + { "TrieSnapshotLength", TrieSnapshotLength }, + { "TrieSnapshotKeyBufferSize", TrieSnapshotKeyBufferSize }, + { "TrieSnapshotGetKey", TrieSnapshotGetKey }, + { "TrieSnapshotDestroy", TrieSnapshotDestroy }, + { NULL, NULL } }; diff --git a/amxmodx/trie_natives.h b/amxmodx/trie_natives.h index ca2911e4..2d192363 100644 --- a/amxmodx/trie_natives.h +++ b/amxmodx/trie_natives.h @@ -3,6 +3,7 @@ #include "amxmodx.h" #include "sm_stringhashmap.h" +#include "sm_memtable.h" #include "CVector.h" using namespace SourceMod; @@ -135,6 +136,22 @@ struct CellTrie StringHashMap map; }; +struct TrieSnapshot +{ + TrieSnapshot() + : strings(128) + { } + + size_t mem_usage() + { + return length * sizeof(int) + strings.GetMemTable()->GetMemUsage(); + } + + size_t length; + ke::AutoArray keys; + BaseStringTable strings; +}; + template class TrieHandles { @@ -208,6 +225,7 @@ public: extern TrieHandles g_TrieHandles; +extern TrieHandles g_TrieSnapshotHandles; extern AMX_NATIVE_INFO trie_Natives[]; #endif diff --git a/plugins/include/celltrie.inc b/plugins/include/celltrie.inc index c6424290..4b7ca0c3 100644 --- a/plugins/include/celltrie.inc +++ b/plugins/include/celltrie.inc @@ -8,6 +8,11 @@ enum Trie Invalid_Trie = 0 }; +enum Snapshot +{ + Invalid_Snapshot = 0 +}; + /** * Creates a hash map. A hash map is a container that can map strings (called * "keys") to arbitrary values (cells, arrays, or strings). Keys in a hash map @@ -156,3 +161,61 @@ native TrieDestroy(&Trie:handle); * @error Invalid Handle. */ native TrieGetSize(Trie:handle); + +/** + * Creates a snapshot of all keys in the map. If the map is changed after this + * call, the changes are not reflected in the snapshot. Keys are not sorted. + * + * @param handle Map handle. + * + * @return New map snapshot handle, which must be freed via TrieSnapshotDestroy(). + * @error Invalid handle. + */ +native Snapshot:TrieSnapshotCreate(Trie:handle); + +/** + * Returns the number of keys in a map snapshot. Note that this may be + * different from the size of the map, since the map can change after the + * snapshot of its keys was taken. + * + * @param handle Map snapshot. + * + * @return Number of keys. + * @error Invalid Handle. + */ +native TrieSnapshotLength(Snapshot:handle); + +/** + * Returns the buffer size required to store a given key. That is, it returns + * the length of the key plus one. + * + * @param handle Map snapshot. + * @param index Key index (starting from 0). + * + * @return Buffer size required to store the key string. + * @error Invalid Handle or index out of range. + */ +native TrieSnapshotKeyBufferSize(Snapshot:handle, index); + +/** + * Retrieves the key string of a given key in a map snapshot. + * + * @param handle Map snapshot. + * @param index Key index (starting from 0). + * @param buffer String buffer. + * @param maxlength Maximum buffer length. + * + * @return Number of bytes written to the buffer. + * @error Invalid Handle or index out of range. + */ +native TrieSnapshotGetKey(Snapshot:handle, index, buffer[], maxlength); + +/** + * Destroys a Map snapshot + * + * @param handle Map snapshot. + * + * @return True on success, false if the value was never set. + * @error Invalid Handle. + */ +native TrieSnapshotDestroy(&Snapshot:handle); diff --git a/plugins/testsuite/trietest.sma b/plugins/testsuite/trietest.sma index 61ea727d..6a43abaa 100644 --- a/plugins/testsuite/trietest.sma +++ b/plugins/testsuite/trietest.sma @@ -45,7 +45,7 @@ public trietest() ok = false; } } - + for (new i = 0; i < 100; i++) { formatex(key, charsmax(key), "K%dK", i); @@ -61,7 +61,7 @@ public trietest() ok = false; } } - + // Setting K42K without replace should fail. new value; if (TrieSetCell(t, "K42K", 999, false)) { ok = false; server_print("set trie K42K should fail"); } @@ -74,12 +74,12 @@ public trietest() { server_print("Map size mismatch (1), expected: %d got: %d", 100, TrieGetSize(t)); ok = false; } - + if (TrieGetCell(t, "cat", value)) { server_print("trie should not have a cat."); ok = false; } - + // Check that "K42K" is not a string or array. new array[32]; new string[32]; @@ -88,16 +88,16 @@ public trietest() { server_print("entry K42K should not be an array or string"); ok = false; } - + TrieClear(t); - + if (TrieGetSize(t) != 0) { server_print("Map size mismatch (2), expected: %d got: %d", 0, TrieGetSize(t)); ok = false; } - + TrieDestroy(t); - + if (ok) pass("Cell tests"); else @@ -112,7 +112,7 @@ public trietest() fail("Recycle handles"); ok = true; - + for (new i = 0; i < 100; i++) { static val[32]; @@ -120,7 +120,7 @@ public trietest() formatex(val, charsmax(val), "V%dV", i); TrieSetString(t, key, val); } - + for (new i = 0; i < 100; i++) { formatex(key, charsmax(key), "K%dK", i); @@ -128,7 +128,7 @@ public trietest() static exp[32]; formatex(exp, charsmax(exp), "V%dV", i); new size; - + if (!TrieGetString(t, key, val, charsmax(val), size)) { server_print("TrieGetString(%d, '%s', %s) failed", t, key, val); @@ -145,28 +145,28 @@ public trietest() ok = false; } } - + if (TrieGetCell(t, "K42K", value) || TrieGetArray(t, "K42K", array, sizeof(array))) { server_print("entry K42K should not be an array or string"); ok = false; } - + if (TrieGetString(t, "cat", string, charsmax(string))) { server_print("trie should not have a cat."); ok = false; } - + if (ok) pass("String tests"); else fail("String tests"); - + ok = true; new data[5] = { 93, 1, 2, 3, 4 }; - + if (!TrieSetArray(t, "K42K", data, sizeof data)) { server_print("K42K should be a string."); ok = false; @@ -175,7 +175,7 @@ public trietest() { server_print("K42K should be V42V."); ok = false; } - for (new i = 0; i < sizeof data; i++) + for (new i = 0; i < sizeof data; i++) { if (data[i] != array[i]) { @@ -186,7 +186,7 @@ public trietest() TrieGetString(t, "K42K", string, charsmax(string))) { server_print("entry K42K should not be an array or string"); ok = false; - } + } if (!TrieSetArray(t, "K42K", data, 1)) { server_print("couldn't set K42K to 1-entry array"); ok = false; @@ -199,15 +199,15 @@ public trietest() { server_print("array size mismatch (%d, expected %d)", value, 1); ok = false; } - + if (ok) pass("Array tests"); else fail("Array tests"); - - + + ok = true; - + // Remove "K42K". if (!TrieDeleteKey(t, "K42K")) { @@ -223,11 +223,11 @@ public trietest() { server_print("map should not have a K42K"); ok = false; } - + TrieDestroy(t); - + t = TrieCreate(); - + for (new i = 0; i < 1000; i++) { formatex(key, charsmax(key), "!%d!", i); @@ -246,16 +246,60 @@ public trietest() server_print("Key '%s' could not be deleted", key); ok = false; } } - + if (ok) pass("Exists/Delete"); else fail("Exists/Delete"); + ok = true; + TrieClear(t); + + if (TrieGetSize(t)) + { + server_print("size should be 0"); ok = false + } + + TrieSetString(t, "adventure", "time!"); + TrieSetString(t, "butterflies", "bees"); + TrieSetString(t, "egg", "egg"); + + new Snapshot:keys = TrieSnapshotCreate(t); + { + if (TrieSnapshotLength(keys) != 3) + { + server_print("trie snapshot length should be 3"); ok = false; + } + + new bool:found[3], len = TrieSnapshotLength(keys); + new buffer[32]; + for (new i = 0; i < len; i++) + { + new size = TrieSnapshotKeyBufferSize(keys, i); // Just to use it, otherwise you should use charsmax(buffer). + TrieSnapshotGetKey(keys, i, buffer, size); + + if (strcmp(buffer, "adventure") == 0) found[0] = true; + else if (strcmp(buffer, "butterflies") == 0) found[1] = true; + else if (strcmp(buffer, "egg") == 0) found[2] = true; + else { server_print("unexpected key: %s", buffer); ok = false; } + } + + if (!found[0] || !found[1] || !found[2]) + { + server_print("did not find all keys"); ok = false; + } + } + + TrieSnapshotDestroy(keys); TrieDestroy(t); + + if (ok) + pass("Snapshot"); + else + fail("Snapshot"); + done(); - } From 0c1931284ee7280ecc2ae581e6ef418c33d5850e Mon Sep 17 00:00:00 2001 From: Arkshine Date: Sat, 3 May 2014 23:50:43 +0200 Subject: [PATCH 7/7] Clean up include file. --- plugins/include/celltrie.inc | 63 +++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/plugins/include/celltrie.inc b/plugins/include/celltrie.inc index 4b7ca0c3..355fb913 100644 --- a/plugins/include/celltrie.inc +++ b/plugins/include/celltrie.inc @@ -25,140 +25,143 @@ enum Snapshot * 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 TrieDestroy(). + * @return New Map handle, which must be freed via TrieDestroy(). */ native Trie:TrieCreate(); /** * Clears all entries from a Map. * - * @param handle Map Handle. + * @param handle Map handle. * - * @error Invalid Handle. + * @error Invalid handle. */ native TrieClear(Trie:handle); /** * Sets a value in a hash map, either inserting a new entry or replacing an old one. * - * @param handle Map Handle. + * @param handle 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. + * @error Invalid handle. */ native TrieSetCell(Trie:handle, const key[], any:value, bool:replace = true); /** * Sets a string value in a Map, either inserting a new entry or replacing an old one. * - * @param handle Map Handle. + * @param handle 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. + * @error Invalid handle. */ native TrieSetString(Trie:handle, const key[], const value[], bool:replace = true); /** * Sets an array value in a Map, either inserting a new entry or replacing an old one. * - * @param handle Map Handle. + * @param handle Map handle. * @param key Key string. * @param buffer Array to store. * @param size 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. - * @error Invalid Handle. + * @error Invalid handle. + * Invalid array size. */ native TrieSetArray(Trie:handle, const key[], const any:buffer[], size, bool:replace = true); /** * Retrieves a value in a Map. * - * @param handle Map Handle. + * @param handle 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 + * @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. + * @error Invalid handle. */ native bool:TrieGetCell(Trie:handle, const key[], &any:value); /** * Retrieves a string in a Map. * - * @param handle Map Handle. + * @param handle Map handle. * @param key Key string. * @param output Buffer to store value. * @param outputsize 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 + * @return True on success. False if the key is not set, or the key is set * as a value or array (not a string). - * @error Invalid Handle. + * @error Invalid handle. + * Invalid buffer size. */ native bool:TrieGetString(Trie:handle, const key[], output[], outputsize, &size = 0); /** * Retrieves an array in a Map. * - * @param handle Map Handle. + * @param handle Map handle. * @param key Key string. * @param output Buffer to store array. * @param outputsize 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 + * @return True on success. False if the key is not set, or the key is set * as a value or string (not an array). - * @error Invalid Handle. + * @error Invalid handle. + * Invalid array size. */ native bool:TrieGetArray(Trie:handle, const key[], any:output[], outputsize, &size = 0); /** * Removes a key entry from a Map. * - * @param handle Map Handle. + * @param handle Map handle. * @param key Key string. * * @return True on success, false if the value was never set. - * @error Invalid Handle. + * @error Invalid handle. */ native bool:TrieDeleteKey(Trie:handle, const key[]); /** * Checks a key entry existence from a Map. * - * @param handle Map Handle. + * @param handle Map handle. * @param key Key string. * * @return True on success, false if the value was never set. - * @error Invalid Handle. + * @error Invalid handle. */ native bool:TrieKeyExists(Trie:handle, const key[]); /** * Destroys a Map. * - * @param handle Map Handle. + * @param handle Map handle. * * @return True on success, false if the value was never set. - * @error Invalid Handle. + * @error Invalid handle. */ native TrieDestroy(&Trie:handle); /** * Retrieves the number of elements in a map. * - * @param handle Map Handle. + * @param handle Map handle. * * @return Number of elements in the trie. - * @error Invalid Handle. + * @error Invalid handle. */ native TrieGetSize(Trie:handle); @@ -181,7 +184,7 @@ native Snapshot:TrieSnapshotCreate(Trie:handle); * @param handle Map snapshot. * * @return Number of keys. - * @error Invalid Handle. + * @error Invalid handle. */ native TrieSnapshotLength(Snapshot:handle); @@ -193,7 +196,7 @@ native TrieSnapshotLength(Snapshot:handle); * @param index Key index (starting from 0). * * @return Buffer size required to store the key string. - * @error Invalid Handle or index out of range. + * @error Invalid handle or index out of range. */ native TrieSnapshotKeyBufferSize(Snapshot:handle, index); @@ -206,7 +209,7 @@ native TrieSnapshotKeyBufferSize(Snapshot:handle, index); * @param maxlength Maximum buffer length. * * @return Number of bytes written to the buffer. - * @error Invalid Handle or index out of range. + * @error Invalid handle or index out of range. */ native TrieSnapshotGetKey(Snapshot:handle, index, buffer[], maxlength); @@ -216,6 +219,6 @@ native TrieSnapshotGetKey(Snapshot:handle, index, buffer[], maxlength); * @param handle Map snapshot. * * @return True on success, false if the value was never set. - * @error Invalid Handle. + * @error Invalid handle. */ native TrieSnapshotDestroy(&Snapshot:handle);