Merge pull request #8 from Arkshine/trie-update

Trie Update
This commit is contained in:
Vincent Herbet 2014-05-04 20:14:10 +02:00
commit db3933d150
27 changed files with 4637 additions and 298 deletions

View File

@ -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
#

View File

@ -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)
################################################

View File

@ -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];

View File

@ -92,7 +92,7 @@
</Midl>
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\;..\..\public\amtl;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;PAWN_CELL_SIZE=32;ASM32;JIT;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@ -143,7 +143,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<OmitFramePointers>true</OmitFramePointers>
<AdditionalIncludeDirectories>..\;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\;..\..\public\amtl;$(METAMOD)\metamod;$(HLSDK)\common;$(HLSDK)\engine;$(HLSDK)\dlls;$(HLSDK)\pm_shared;$(HLSDK)\public;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;JIT;ASM32;PAWN_CELL_SIZE=32;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<IgnoreStandardIncludePath>false</IgnoreStandardIncludePath>
<StringPooling>true</StringPooling>
@ -192,7 +192,7 @@
</Midl>
<ClCompile>
<Optimization>Disabled</Optimization>
<AdditionalIncludeDirectories>..\;$(METAMOD)\metamod;$(HLSDK)\multiplayer\common;$(HLSDK)\multiplayer\engine;$(HLSDK)\multiplayer\dlls;$(HLSDK)\multiplayer\pm_shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\;..\..\public\amtl;$(METAMOD)\metamod;$(HLSDK)\multiplayer\common;$(HLSDK)\multiplayer\engine;$(HLSDK)\multiplayer\dlls;$(HLSDK)\multiplayer\pm_shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;PAWN_CELL_SIZE=32;ASM32;JIT;BINLOG_ENABLED;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
@ -243,7 +243,7 @@
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Speed</FavorSizeOrSpeed>
<OmitFramePointers>true</OmitFramePointers>
<AdditionalIncludeDirectories>..\;$(METAMOD)\metamod;$(HLSDK)\multiplayer\common;$(HLSDK)\multiplayer\engine;$(HLSDK)\multiplayer\dlls;$(HLSDK)\multiplayer\pm_shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..\;..\..\public\amtl;$(METAMOD)\metamod;$(HLSDK)\multiplayer\common;$(HLSDK)\multiplayer\engine;$(HLSDK)\multiplayer\dlls;$(HLSDK)\multiplayer\pm_shared;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;amxmodx_EXPORTS;JIT;ASM32;PAWN_CELL_SIZE=32;BINLOG_ENABLED;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<IgnoreStandardIncludePath>false</IgnoreStandardIncludePath>
<StringPooling>true</StringPooling>
@ -389,6 +389,8 @@
<ClInclude Include="..\sh_list.h" />
<ClInclude Include="..\sh_stack.h" />
<ClInclude Include="..\sh_tinyhash.h" />
<ClInclude Include="..\sm_memtable.h" />
<ClInclude Include="..\sm_stringhashmap.h" />
<ClInclude Include="..\svn_version.h" />
<ClInclude Include="..\trie_natives.h" />
<ClInclude Include="..\zlib\zconf.h" />

View File

@ -323,6 +323,12 @@
<ClInclude Include="..\CDataPack.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\sm_stringhashmap.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\sm_memtable.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="..\version.rc">

170
amxmodx/sm_memtable.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_
#define _INCLUDE_SOURCEMOD_CORE_STRINGTABLE_H_
#include <stdlib.h>
#include <string.h>
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_

241
amxmodx/sm_stringhashmap.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* 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 <am-allocator-policies.h>
#include <am-hashmap.h>
#include <am-string.h>
#include <am-moveable.h>
#include <string.h>
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 <typename T>
class StringHashMap
{
typedef detail::CharsAndLength CharsAndLength;
typedef ke::HashMap<ke::AString, T, detail::StringHashMapPolicy> 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_

View File

@ -6,18 +6,11 @@
#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<TrieData> celltrie;
TrieHandles<CellTrie> g_TrieHandles;
TrieHandles<TrieSnapshot> g_TrieSnapshotHandles;
void triedata_dtor(TrieData *ptr)
{
ptr->freeCells();
}
// native Trie:TrieCreate();
static cell AMX_NATIVE_CALL TrieCreate(AMX *amx, cell *params)
{
@ -27,268 +20,429 @@ 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);
// 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]);
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<Entry>::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[]);
// 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[], bool:replace = true);
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<Entry>::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)
// 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, bool:replace = true)
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;
}
if (params[4] < 0)
{
LogError(amx, AMX_ERR_NATIVE, "Invalid array size (%d)", params[4]);
return 0;
}
TrieData *td = NULL;
int len;
const char *key = get_amxstring(amx, params[2], 0, len);
cell *ptr = get_amxaddr(amx, params[3]);
if ((td = t->retrieve(key)) == NULL)
StringHashMap<Entry>::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->key = key;
i->value.setArray(ptr, params[4]);
return 1;
}
// 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)
{
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<Entry>::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;
}
// native bool:TrieGetString(Trie:handle, const key[], buff[], len);
return 0;
}
// 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]);
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;
}
if (params[4] < 0)
{
LogError(amx, AMX_ERR_NATIVE, "Invalid buffer size (%d)", params[4]);
return 0;
}
TrieData *td = NULL;
int len;
const char *key = get_amxstring(amx, params[2], 0, len);
cell *pSize = get_amxaddr(amx, params[5]);
if ((td = t->retrieve(key)) == NULL)
{
return 0;
}
cell *ptr = get_amxaddr(amx, params[3]);
if (!td->getString(ptr, params[4]))
StringHashMap<Entry>::Result r = t->map.find(key);
if (!r.found() || !r->value.isString())
{
return 0;
}
*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]);
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;
}
if (params[4] < 0)
{
LogError(amx, AMX_ERR_NATIVE, "Invalid array size (%d)", params[4]);
return 0;
}
TrieData *td = NULL;
int len;
const char *key = get_amxstring(amx, params[2], 0, len);
cell *pValue = get_amxaddr(amx, params[3]);
cell *pSize = get_amxaddr(amx, params[5]);
if ((td = t->retrieve(key)) == NULL)
StringHashMap<Entry>::Result r = t->map.find(key);
if (!r.found() || !r->value.isArray())
{
return 0;
}
cell *ptr = get_amxaddr(amx, params[3]);
if (!td->getArray(ptr, params[4]))
if (!r->value.array())
{
return 0;
}
*pSize = 0;
return 1;
}
if (!params[4])
{
return 0;
}
size_t length = r->value.arrayLength();
cell *base = r->value.array();
if (length > size_t(params[4]))
*pSize = params[4];
else
*pSize = length;
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)
{
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<cell>(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<Entry>::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)
// native TrieGetSize(Trie:handle);
static cell AMX_NATIVE_CALL TrieGetSize(AMX *amx, cell *params)
{
return trie_malloc_count;
}
static cell AMX_NATIVE_CALL TrieFreeCount(AMX *amx, cell *params)
CellTrie *t = g_TrieHandles.lookup(params[1]);
if (t == NULL)
{
return trie_free_count;
LogError(amx, AMX_ERR_NATIVE, "Invalid map handle provided (%d)", params[1]);
return 0;
}
#endif
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<Entry>::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<cell>(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 },
@ -305,11 +459,13 @@ AMX_NATIVE_INFO trie_Natives[] =
{ "TrieDeleteKey", TrieDeleteKey },
{ "TrieKeyExists", TrieKeyExists },
{ "TrieDestroy", TrieDestroy },
{ "TrieGetSize", TrieGetSize },
#ifndef NDEBUG
{ "TrieMallocCount", TrieMallocCount },
{ "TrieFreeCount", TrieFreeCount },
#endif
{ "TrieSnapshotCreate", TrieSnapshotCreate },
{ "TrieSnapshotLength", TrieSnapshotLength },
{ "TrieSnapshotKeyBufferSize", TrieSnapshotKeyBufferSize },
{ "TrieSnapshotGetKey", TrieSnapshotGetKey },
{ "TrieSnapshotDestroy", TrieSnapshotDestroy },
{ NULL, NULL }
};

View File

@ -2,150 +2,161 @@
#define _TRIE_NATIVES_H_
#include "amxmodx.h"
#include "sm_trie_tpl.h"
#include "sm_stringhashmap.h"
#include "sm_memtable.h"
#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
enum EntryType
{
EntryType_Cell,
EntryType_CellArray,
EntryType_String,
};
class TrieData
class Entry
{
private:
cell *m_data;
cell m_cell;
cell m_cellcount;
int m_type;
struct ArrayInfo
{
size_t length;
size_t maxbytes;
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<cell *>(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;
void *base() {
return this + 1;
}
};
public:
Entry()
: control_(0)
{
}
Entry(ke::Moveable<Entry> 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<cell *>(raw()->base());
}
char *chars() const {
assert(isString());
return reinterpret_cast<char *>(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<ArrayInfo *>(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
{
StringHashMap<Entry> map;
};
struct TrieSnapshot
{
TrieSnapshot()
: strings(128)
{ }
size_t mem_usage()
{
return length * sizeof(int) + strings.GetMemTable()->GetMemUsage();
}
size_t length;
ke::AutoArray<int> keys;
BaseStringTable strings;
};
template <typename T>
class TrieHandles
{
private:
CVector< KTrie< TrieData > *> m_tries;
CVector<T *> m_tries;
public:
TrieHandles() { }
@ -166,7 +177,7 @@ public:
m_tries.clear();
}
KTrie<TrieData> *lookup(int handle)
T *lookup(int handle)
{
handle--;
@ -184,12 +195,12 @@ public:
if (m_tries[i] == NULL)
{
// reuse handle
m_tries[i] = new KTrie<TrieData>;
m_tries[i] = new T;
return static_cast<int>(i) + 1;
}
}
m_tries.push_back(new KTrie<TrieData>);
m_tries.push_back(new T);
return m_tries.size();
}
bool destroy(int handle)
@ -213,7 +224,8 @@ public:
};
extern TrieHandles g_TrieHandles;
extern TrieHandles<CellTrie> g_TrieHandles;
extern TrieHandles<TrieSnapshot> g_TrieSnapshotHandles;
extern AMX_NATIVE_INFO trie_Natives[];
#endif

View File

@ -8,19 +8,217 @@ 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
* 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);
native TrieSetCell(Trie:handle, const key[], any:value);
native TrieSetString(Trie:handle, const key[], const value[]);
native TrieSetArray(Trie:handle, const key[], const any:buffer[], size);
/**
* 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.
* @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, 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 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[], 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 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.
* 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 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);
native bool:TrieGetString(Trie:handle, const key[], output[], outputsize);
native bool:TrieGetArray(Trie:handle, const key[], any:output[], outputsize);
/**
* 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.
* @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.
* 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 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.
* 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 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);
/**
* 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);
/**
* 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);

View File

@ -1,10 +1,5 @@
#include <amxmodx>
// 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,32 +55,64 @@ 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;
}
}
// 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)
{
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];
@ -105,31 +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");
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);
check_frees();
t = TrieCreate();
ok = true;
for (new i = 0; i < 1000; i++)
{
formatex(key, charsmax(key), "!%d!", i);
@ -141,29 +239,67 @@ 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");
check_frees();
TrieClear(t);
TrieDestroy(t);
check_frees();
done();
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();
}

View File

@ -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 <stdio.h>
#include <stdlib.h>
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_

102
public/amtl/am-atomics.h Normal file
View File

@ -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 <am-utility.h>
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 <size_t Width>
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<sizeof(uintptr_t)> 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_

196
public/amtl/am-hashmap.h Normal file
View File

@ -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 <am-hashtable.h>
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 <typename K,
typename V,
typename HashPolicy,
typename AllocPolicy = SystemAllocatorPolicy>
class HashMap : public AllocPolicy
{
private:
struct Entry
{
K key;
V value;
Entry()
{
}
Entry(Moveable<Entry> other)
: key(Moveable<K>(other->key)),
value(Moveable<V>(other->value))
{
}
Entry(const K &aKey, const V &aValue)
: key(aKey),
value(aValue)
{ }
Entry(const K &aKey, Moveable<V> aValue)
: key(aKey),
value(aValue)
{ }
Entry(Moveable<K> aKey, const V &aValue)
: key(aKey),
value(aValue)
{ }
Entry(Moveable<K> aKey, Moveable<V> aValue)
: key(aKey),
value(aValue)
{ }
};
struct Policy
{
typedef Entry Payload;
template <typename Lookup>
static uint32_t hash(const Lookup &key) {
return HashPolicy::hash(key);
}
template <typename Lookup>
static bool matches(const Lookup &key, const Payload &payload) {
return HashPolicy::matches(key, payload.key);
}
};
typedef HashTable<Policy, AllocPolicy> 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 <typename Lookup>
Result find(const Lookup &key) {
return table_.find(key);
}
template <typename Lookup>
Insert findForAdd(const Lookup &key) {
return table_.findForAdd(key);
}
template <typename Lookup>
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<K> key, const V &value) {
return table_.add(i, Entry(key, value));
}
bool add(Insert &i, const K &key, Moveable<V> value) {
return table_.add(i, Entry(key, value));
}
bool add(Insert &i, Moveable<K> key, Moveable<V> value) {
return table_.add(i, Entry(key, value));
}
bool add(Insert &i, Moveable<K> 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 <typename T>
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_

129
public/amtl/am-hashset.h Normal file
View File

@ -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 <am-hashtable.h>
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 <typename K,
typename HashPolicy,
typename AllocPolicy = SystemAllocatorPolicy>
class HashSet : public AllocPolicy
{
struct Policy {
typedef K Payload;
template <typename Lookup>
static uint32_t hash(const Lookup &key) {
return HashPolicy::hash(key);
}
template <typename Lookup>
static bool matches(const Lookup &key, const Payload &payload) {
return HashPolicy::matches(key, payload);
}
};
typedef HashTable<Policy, AllocPolicy> 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 <typename Lookup>
Result find(const Lookup &key) {
return table_.find(key);
}
template <typename Lookup>
Insert findForAdd(const Lookup &key) {
return table_.findForAdd(key);
}
template <typename Lookup>
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<K> 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_

631
public/amtl/am-hashtable.h Normal file
View File

@ -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 <new>
#include <limits.h>
#include <stdlib.h>
#include "am-allocator-policies.h"
#include "am-utility.h"
#include "am-moveable.h"
namespace ke {
namespace detail {
template <typename T>
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> 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 <typename HashPolicy, typename AllocPolicy = SystemAllocatorPolicy>
class HashTable : public AllocPolicy
{
friend class iterator;
typedef typename HashPolicy::Payload Payload;
typedef detail::HashTableEntry<Payload> Entry;
private:
static const uint32_t kMinCapacity = 16;
static const uint32_t kMaxCapacity = INT_MAX / sizeof(Entry);
template <typename Key>
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<Payload>(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 <typename Key>
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 <typename Key>
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 <typename Key>
Result find(const Key &key) {
return lookup(key);
}
// The Insert object must not be used past mutating table operations.
template <typename Key>
Insert findForAdd(const Key &key) {
return lookupForAdd(key);
}
template <typename Key>
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> 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 <size_t Size>
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<sizeof(ptr)>(reinterpret_cast<uintptr_t>(ptr));
}
} // namespace ke
#endif // _INCLUDE_KEIMA_HASHTABLE_H_

183
public/amtl/am-inlinelist.h Normal file
View File

@ -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 <stddef.h>
#include <assert.h>
namespace ke {
template <typename T> class InlineList;
// Objects can recursively inherit from InlineListNode in order to have
// membership in an InlineList<T>.
template <typename T>
class InlineListNode
{
friend class InlineList<T>;
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 <typename T>
class InlineList
{
typedef InlineListNode<T> 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<T *>(iter_);
}
T * operator ->() {
return static_cast<T *>(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_

309
public/amtl/am-linkedlist.h Normal file
View File

@ -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 <new>
#include <stdlib.h>
#include <am-allocator-policies.h>
#include <am-utility.h>
#include <am-moveable.h>
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 T, class AllocPolicy = SystemAllocatorPolicy>
class LinkedList : public AllocPolicy
{
public:
friend class iterator;
class Node
{
public:
Node(const T &o)
: obj(o)
{
}
Node(Moveable<T> 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<T> obj) {
return insertBefore(end(), obj) != end();
}
bool prepend(const T &obj) {
return insertBefore(begin(), obj) != begin();
}
bool prepend(Moveable<T> 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<T> 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<Node> 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<T> 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 <typename U>
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<T> &other) KE_DELETE;
LinkedList(const LinkedList<T> &other) KE_DELETE;
};
} // namespace ke
#endif //_INCLUDE_CSDM_LIST_H

73
public/amtl/am-moveable.h Normal file
View File

@ -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> 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 <typename T>
struct Moveable
{
public:
explicit Moveable(T &t)
: t_(t)
{
}
T *operator ->() {
return &t_;
}
operator T &() {
return t_;
}
private:
T &t_;
};
template <typename T>
static inline Moveable<T>
Move(T &t)
{
return Moveable<T>(t);
}
} // namespace ke
#endif // _include_amtl_moveable_h_

View File

@ -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 <am-refcounting.h>
#include <am-atomics.h>
namespace ke {
template <typename T>
class RefcountedThreadsafe
{
public:
RefcountedThreadsafe()
: refcount_(1)
{
}
void AddRef() {
refcount_.increment();
}
bool Release() {
if (!refcount_.decrement()) {
delete static_cast<T *>(this);
return false;
}
return true;
}
protected:
~RefcountedThreadsafe() {
}
private:
AtomicRefCount refcount_;
};
} // namespace ke
#endif // _include_amtl_ts_refcounting_h_

View File

@ -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 <am-utility.h>
#include <am-moveable.h>
namespace ke {
template <typename T> 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 <typename T>
class Newborn
{
public:
Newborn(T *t)
: thing_(t)
{
}
T *release() const {
return ReturnAndVoid(thing_);
}
private:
mutable T *thing_;
};
template <typename T>
static inline Newborn<T>
NoAddRef(T *t)
{
return Newborn<T>(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 <typename T>
class PassRef
{
public:
PassRef(T *thing)
: thing_(thing)
{
AddRef();
}
PassRef()
: thing_(NULL)
{
}
PassRef(const Newborn<T *> &other)
: thing_(other.release())
{
// Don't addref, newborn means already addref'd.
}
template <typename S>
inline PassRef(const Ref<S> &other);
PassRef(const PassRef &other)
: thing_(other.release())
{
}
template <typename S>
PassRef(const PassRef<S> &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 <typename S>
PassRef &operator =(const PassRef<S> &other) {
Release();
thing_ = other.release();
return *this;
}
private:
// Disallowed operators.
PassRef &operator =(T *other);
PassRef &operator =(Newborn<T> &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 <typename T>
class Refcounted
{
public:
Refcounted()
: refcount_(1)
{
}
void AddRef() {
refcount_++;
}
void Release() {
assert(refcount_ > 0);
if (--refcount_ == 0)
delete static_cast<T *>(this);
}
protected:
~Refcounted() {
}
private:
uintptr_t refcount_;
};
// Simple class for automatic refcounting.
template <typename T>
class Ref
{
public:
Ref(T *thing)
: thing_(thing)
{
AddRef();
}
Ref()
: thing_(NULL)
{
}
Ref(const Ref &other)
: thing_(other.thing_)
{
AddRef();
}
Ref(Moveable<Ref> other)
: thing_(other->thing_)
{
other->thing_ = NULL;
}
template <typename S>
Ref(const Ref<S> &other)
: thing_(*other)
{
AddRef();
}
Ref(const PassRef<T> &other)
: thing_(other.release())
{
}
template <typename S>
Ref(const PassRef<S> &other)
: thing_(other.release())
{
}
Ref(const Newborn<T> &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 <typename S>
Ref &operator =(S *thing) {
Release();
thing_ = thing;
AddRef();
return *this;
}
template <typename S>
Ref &operator =(const PassRef<S> &other) {
Release();
thing_ = other.release();
return *this;
}
template <typename S>
Ref &operator =(const Newborn<S> &other) {
Release();
thing_ = other.release();
return *this;
}
Ref &operator =(const Ref &other) {
Release();
thing_ = other.thing_;
AddRef();
return *this;
}
Ref &operator =(Moveable<Ref> 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 <typename T> template <typename S>
PassRef<T>::PassRef(const Ref<S> &other)
: thing_(*other)
{
AddRef();
}
} // namespace ke
#endif // _include_amtl_refcounting_h_

134
public/amtl/am-string.h Normal file
View File

@ -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 <stdlib.h>
#include <string.h>
#include <am-utility.h>
#include <am-moveable.h>
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<AString> 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<AString> 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<char> chars_;
size_t length_;
};
}
#endif // _include_amtl_string_h_

View File

@ -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 <pthread.h>
#include <sys/time.h>
#include <errno.h>
#include <stdio.h>
#if defined(__linux__)
# include <sys/prctl.h>
#endif
#if defined(__APPLE__)
# include <dlfcn.h>
#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<ThreadData> 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_

View File

@ -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 <assert.h>
#if defined(_MSC_VER)
# include <windows.h>
# include <WinBase.h>
#else
# include <pthread.h>
#endif
#include <am-utility.h>
// 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_

View File

@ -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 <windows.h>
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_

346
public/amtl/am-utility.h Normal file
View File

@ -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 <assert.h>
#include <stddef.h>
#include <stdlib.h>
//#include <stdint.h>
#if defined(_MSC_VER)
# include <intrin.h>
#endif
#include <am-moveable.h>
#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 <typename T> 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 <typename T>
class AutoPtr
{
T *t_;
public:
AutoPtr()
: t_(NULL)
{
}
AutoPtr(T *t)
: t_(t)
{
}
AutoPtr(Moveable<AutoPtr<T> > 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<AutoPtr<T> > 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 <typename T>
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 <typename T>
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 <typename T> static inline T
Min(const T &t1, const T &t2)
{
return t1 < t2 ? t1 : t2;
}
template <typename T> static inline T
Max(const T &t1, const T &t2)
{
return t1 > t2 ? t1 : t2;
}
template <typename T>
class StorageBuffer
{
public:
T *address() {
return reinterpret_cast<T *>(buffer_);
}
const T *address() const {
return reinterpret_cast<const T *>(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_

239
public/amtl/am-vector.h Normal file
View File

@ -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 <new>
#include <stdlib.h>
#include <am-allocator-policies.h>
#include <am-utility.h>
#include <am-moveable.h>
namespace ke {
template <typename T, typename AllocPolicy = SystemAllocatorPolicy>
class Vector : public AllocPolicy
{
public:
Vector(AllocPolicy = AllocPolicy())
: data_(NULL),
nitems_(0),
maxsize_(0)
{
}
Vector(Moveable<Vector<T, AllocPolicy> > 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<T> 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<T> 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<T> 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<T>(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<T> &other) KE_DELETE;
Vector &operator =(const Vector<T> &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<T>(data_[nitems_ - 1])))
return false;
for (size_t i = nitems_ - 2; i > at; i--)
data_[i] = Moveable<T>(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<T>(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_ */