diff --git a/include/bx/handlealloc.h b/include/bx/handlealloc.h index aad39cd..2a05f21 100644 --- a/include/bx/handlealloc.h +++ b/include/bx/handlealloc.h @@ -8,9 +8,11 @@ #include "bx.h" #include "allocator.h" +#include "uint32_t.h" namespace bx { + /// class HandleAlloc { public: @@ -127,6 +129,7 @@ namespace bx BX_FREE(_allocator, _handleAlloc); } + /// template class HandleAllocT : public HandleAlloc { @@ -144,6 +147,7 @@ namespace bx uint16_t m_padding[2*MaxHandlesT]; }; + /// template class HandleListT { @@ -327,6 +331,7 @@ namespace bx Link m_links[MaxHandlesT]; }; + /// template class HandleAllocLruT { @@ -422,6 +427,293 @@ namespace bx HandleAllocT m_alloc; }; + /// + template + class HandleHashMapT + { + public: + static const uint16_t invalid = UINT16_MAX; + + HandleHashMapT() + : m_maxCapacity(MaxCapacityT) + { + reset(); + } + + ~HandleHashMapT() + { + } + + bool insert(KeyT _key, uint16_t _handle) + { + if (invalid == _handle) + { + return false; + } + + const KeyT hash = mix(_key); + const uint32_t first = hash % MaxCapacityT; + uint32_t idx = first; + do + { + if (m_handle[idx] == invalid) + { + m_key[idx] = _key; + m_handle[idx] = _handle; + ++m_numElements; + return true; + } + + if (m_key[idx] == _key) + { + return false; + } + + idx = (idx + 1) % MaxCapacityT; + + } while (idx != first); + + return false; + } + + void removeByKey(KeyT _key) + { + uint32_t idx = findIndex(_key); + if (UINT32_MAX != idx) + { + m_handle[idx] = invalid; + --m_numElements; + } + } + + void removeByHandle(uint16_t _handle) + { + if (invalid != _handle) + { + for (uint32_t idx = 0; idx < MaxCapacityT; ++idx) + { + if (m_handle[idx] == _handle) + { + m_handle[idx] = invalid; + --m_numElements; + } + } + } + } + + uint16_t find(KeyT _key) const + { + uint32_t idx = findIndex(_key); + if (UINT32_MAX != idx) + { + return m_handle[idx]; + } + + return invalid; + } + + void reset() + { + memset(m_handle, 0xff, sizeof(m_handle) ); + m_numElements = 0; + } + + uint32_t getNumElements() const + { + return m_numElements; + } + + uint32_t getMaxCapacity() const + { + return m_maxCapacity; + } + + struct Iterator + { + uint16_t handle; + + private: + friend class HandleHashMapT; + uint32_t pos; + uint32_t num; + }; + + Iterator first() const + { + Iterator it; + it.handle = invalid; + it.pos = 0; + it.num = m_numElements; + + if (0 == it.num) + { + return it; + } + + ++it.num; + next(it); + return it; + } + + bool next(Iterator& _it) const + { + if (0 == _it.num) + { + return false; + } + + for ( + ;_it.pos < MaxCapacityT && invalid == m_handle[_it.pos] + ; ++_it.pos + ); + _it.handle = m_handle[_it.pos]; + ++_it.pos; + --_it.num; + return true; + } + + private: + uint32_t findIndex(KeyT _key) const + { + const KeyT hash = mix(_key); + + const uint32_t first = hash % MaxCapacityT; + uint32_t idx = first; + do + { + if (m_handle[idx] == invalid) + { + return UINT32_MAX; + } + + if (m_key[idx] == _key) + { + return idx; + } + + idx = (idx + 1) % MaxCapacityT; + + } while (idx != first); + + return UINT32_MAX; + } + + uint32_t mix(uint32_t _x) const + { + const uint32_t tmp0 = uint32_mul(_x, UINT32_C(2246822519) ); + const uint32_t tmp1 = uint32_rol(tmp0, 13); + const uint32_t result = uint32_mul(tmp1, UINT32_C(2654435761) ); + return result; + } + + uint64_t mix(uint64_t _x) const + { + const uint64_t tmp0 = uint64_mul(_x, UINT64_C(14029467366897019727) ); + const uint64_t tmp1 = uint64_rol(tmp0, 31); + const uint64_t result = uint64_mul(tmp1, UINT64_C(11400714785074694791) ); + return result; + } + + uint32_t m_maxCapacity; + uint32_t m_numElements; + + KeyT m_key[MaxCapacityT]; + uint16_t m_handle[MaxCapacityT]; + }; + + /// + template + class HandleHashMapAllocT + { + public: + static const uint16_t invalid = UINT16_MAX; + + HandleHashMapAllocT() + { + reset(); + } + + ~HandleHashMapAllocT() + { + } + + uint16_t alloc(KeyT _key) + { + uint16_t handle = m_alloc.alloc(); + if (invalid == handle) + { + return invalid; + } + + bool ok = m_table.insert(_key, handle); + if (!ok) + { + m_alloc.free(handle); + return invalid; + } + + return handle; + } + + void free(KeyT _key) + { + uint16_t handle = m_table.find(_key); + if (invalid == handle) + { + return; + } + + m_table.removeByKey(_key); + m_alloc.free(handle); + } + + void free(uint16_t _handle) + { + m_table.removeByHandle(_handle); + m_alloc.free(_handle); + } + + uint16_t find(KeyT _key) const + { + return m_table.find(_key); + } + + const uint16_t* getHandles() const + { + return m_alloc.getHandles(); + } + + uint16_t getHandleAt(uint16_t _at) const + { + return m_alloc.getHandleAt(_at); + } + + uint16_t getNumHandles() const + { + return m_alloc.getNumHandles(); + } + + uint16_t getMaxHandles() const + { + return m_alloc.getMaxHandles(); + } + + bool isValid(uint16_t _handle) const + { + return m_alloc.isValid(_handle); + } + + void reset() + { + m_table.reset(); + m_alloc.reset(); + } + + private: + HandleHashMapT m_table; + HandleAllocT m_alloc; + }; + } // namespace bx #endif // BX_HANDLE_ALLOC_H_HEADER_GUARD diff --git a/include/bx/hash.h b/include/bx/hash.h index 4250115..6e2f084 100644 --- a/include/bx/hash.h +++ b/include/bx/hash.h @@ -162,6 +162,7 @@ namespace bx template inline uint32_t hashMurmur2A(const Ty& _data) { + BX_STATIC_ASSERT(BX_TYPE_IS_POD(Ty) ); return hashMurmur2A(&_data, sizeof(Ty) ); } diff --git a/include/bx/string.h b/include/bx/string.h index 1bd3225..495c7c9 100644 --- a/include/bx/string.h +++ b/include/bx/string.h @@ -14,12 +14,146 @@ #include #include // wchar_t +#include +#include + #ifndef va_copy # define va_copy(_a, _b) (_a) = (_b) #endif // va_copy namespace bx { + /// Non-zero-terminated string view. + class StringView + { + public: + StringView() + { + clear(); + } + + StringView(const char* _ptr, uint32_t _len = UINT32_MAX) + { + clear(); + + if (NULL != _ptr) + { + uint32_t len = UINT32_MAX == _len ? strlen(_ptr) : _len; + if (0 != len) + { + m_len = len; + m_ptr = _ptr; + } + } + } + + void clear() + { + m_ptr = ""; + m_len = 0; + } + + const char* getPtr() const + { + return m_ptr; + } + + const char* getTerm() const + { + return m_ptr + m_len; + } + + bool isEmpty() const + { + return 0 == m_len; + } + + uint32_t getLength() const + { + return m_len; + } + + protected: + friend uint32_t hashMurmur2A(const StringView& _data); + + const char* m_ptr; + uint32_t m_len; + }; + + inline uint32_t hashMurmur2A(const StringView& _data) + { + return hashMurmur2A(_data.m_ptr, _data.m_len); + } + + inline uint32_t hashMurmur2A(const char* _data) + { + return hashMurmur2A(StringView(_data) ); + } + + /// ASCII string + template + class StringT : public StringView + { + public: + StringT() + : StringView("", 0) + { + } + + StringT(const char* _rhs) + { + clear(); + + if (NULL != _rhs) + { + uint32_t len = strlen(_rhs); + m_len = len; + if (0 != len) + { + ++len; + + char* ptr = (char*)BX_ALLOC(*allocator, len); + + memcpy(ptr, _rhs, len); + + *const_cast(&m_ptr) = ptr; + } + } + } + + StringT(const StringView& _str) + { + uint32_t len = _str.getLength(); + m_len = len; + if (0 != len) + { + ++len; + + char* ptr = (char*)BX_ALLOC(*allocator, len); + + memcpy(ptr, _str.getPtr(), len-1); + ptr[len] = '\0'; + + *const_cast(&m_ptr) = ptr; + } + } + + ~StringT() + { + clear(); + } + + void clear() + { + if (0 != m_len) + { + BX_FREE(*allocator, const_cast(m_ptr) ); + + StringView::clear(); + } + } + }; + /// inline bool toBool(const char* _str) { diff --git a/include/bx/uint32_t.h b/include/bx/uint32_t.h index 50f8e09..917c47e 100644 --- a/include/bx/uint32_t.h +++ b/include/bx/uint32_t.h @@ -692,6 +692,46 @@ namespace bx #endif // BX_COMPILER_ } + inline uint64_t uint64_sll(uint64_t _a, int _sa) + { + return _a << _sa; + } + + inline uint64_t uint64_srl(uint64_t _a, int _sa) + { + return _a >> _sa; + } + + inline uint64_t uint64_sra(uint64_t _a, int _sa) + { + return ( (int64_t)_a) >> _sa; + } + + inline uint64_t uint64_rol(uint64_t _a, int _sa) + { + return ( _a << _sa) | (_a >> (32-_sa) ); + } + + inline uint64_t uint64_ror(uint64_t _a, int _sa) + { + return ( _a >> _sa) | (_a << (32-_sa) ); + } + + inline uint64_t uint64_add(uint64_t _a, uint64_t _b) + { + return _a + _b; + } + + inline uint64_t uint64_sub(uint64_t _a, uint64_t _b) + { + return _a - _b; + } + + inline uint64_t uint64_mul(uint64_t _a, uint64_t _b) + { + return _a * _b; + } + /// Greatest common divisor. inline uint32_t uint32_gcd(uint32_t _a, uint32_t _b) { diff --git a/scripts/genie.lua b/scripts/genie.lua index fdde771..edc86a7 100644 --- a/scripts/genie.lua +++ b/scripts/genie.lua @@ -49,8 +49,64 @@ project "bx.test" } files { - path.join(BX_DIR, "tests/**.cpp"), - path.join(BX_DIR, "tests/**.H"), + path.join(BX_DIR, "tests/*_test.cpp"), + path.join(BX_DIR, "tests/*_test.H"), + path.join(BX_DIR, "tests/dbg.*"), + } + + configuration { "vs* or mingw*" } + links { + "psapi", + } + + configuration { "android*" } + targetextension ".so" + linkoptions { + "-shared", + } + + configuration { "nacl or nacl-arm" } + targetextension ".nexe" + links { + "ppapi", + "pthread", + } + + configuration { "pnacl" } + targetextension ".pexe" + links { + "ppapi", + "pthread", + } + + configuration { "linux-*" } + links { + "pthread", + } + + configuration { "osx" } + links { + "Cocoa.framework", + } + + configuration {} + + strip() + +project "bx.bench" + kind "ConsoleApp" + + debugdir (path.join(BX_DIR, "tests")) + + includedirs { + path.join(BX_DIR, "include"), + BX_THIRD_PARTY_DIR, + } + + files { + path.join(BX_DIR, "tests/*_bench.cpp"), + path.join(BX_DIR, "tests/*_bench.h"), + path.join(BX_DIR, "tests/dbg.*"), } configuration { "vs* or mingw*" } diff --git a/tests/fpumath.cpp b/tests/fpumath_test.cpp similarity index 100% rename from tests/fpumath.cpp rename to tests/fpumath_test.cpp diff --git a/tests/handle.cpp b/tests/handle.cpp deleted file mode 100644 index b5b7c18..0000000 --- a/tests/handle.cpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2010-2016 Branimir Karadzic. All rights reserved. - * License: https://github.com/bkaradzic/bx#license-bsd-2-clause - */ - -#include "test.h" -#include - -TEST(HandleListT) -{ - bx::HandleListT<32> list; - - list.pushBack(16); - CHECK(list.getFront() == 16); - CHECK(list.getBack() == 16); - - list.pushFront(7); - CHECK(list.getFront() == 7); - CHECK(list.getBack() == 16); - - uint16_t expected0[] = { 15, 31, 7, 16, 17, 11, 13 }; - list.pushBack(17); - list.pushBack(11); - list.pushBack(13); - list.pushFront(31); - list.pushFront(15); - uint16_t count = 0; - for (uint16_t it = list.getFront(); it != UINT16_MAX; it = list.getNext(it), ++count) - { - CHECK(it == expected0[count]); - } - CHECK(count == BX_COUNTOF(expected0) ); - - list.remove(17); - list.remove(31); - list.remove(16); - list.pushBack(16); - uint16_t expected1[] = { 15, 7, 11, 13, 16 }; - count = 0; - for (uint16_t it = list.getFront(); it != UINT16_MAX; it = list.getNext(it), ++count) - { - CHECK(it == expected1[count]); - } - CHECK(count == BX_COUNTOF(expected1) ); - - list.popBack(); - list.popFront(); - list.popBack(); - list.popBack(); - - CHECK(list.getFront() == 7); - CHECK(list.getBack() == 7); - - list.popBack(); - CHECK(list.getFront() == UINT16_MAX); - CHECK(list.getBack() == UINT16_MAX); -} - -TEST(HandleAllocLruT) -{ - bx::HandleAllocLruT<16> lru; - - uint16_t handle[4] = - { - lru.alloc(), - lru.alloc(), - lru.alloc(), - lru.alloc(), - }; - - lru.touch(handle[1]); - - uint16_t expected0[] = { handle[1], handle[3], handle[2], handle[0] }; - uint16_t count = 0; - for (uint16_t it = lru.getFront(); it != UINT16_MAX; it = lru.getNext(it), ++count) - { - CHECK(it == expected0[count]); - } -} diff --git a/tests/handle_bench.cpp b/tests/handle_bench.cpp new file mode 100644 index 0000000..e230c4d --- /dev/null +++ b/tests/handle_bench.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +#include +#include + +#include + +#include +#include + +int main() +{ + const uint32_t numElements = 4<<10; + const uint32_t numIterations = 16; + + // + { + int64_t elapsed = -bx::getHPCounter(); + + for (uint32_t ii = 0; ii < numIterations; ++ii) + { + typedef tinystl::unordered_map TinyStlUnorderedMap; + TinyStlUnorderedMap map; + for (uint32_t jj = 0; jj < numElements; ++jj) + { + tinystl::pair ok = map.insert(tinystl::make_pair(uint64_t(jj), uint16_t(jj) ) ); + assert(ok.second); + } + } + + elapsed += bx::getHPCounter(); + printf(" TinyStl: %15f\n", double(elapsed) ); + } + + /// + { + int64_t elapsed = -bx::getHPCounter(); + + for (uint32_t ii = 0; ii < numIterations; ++ii) + { + typedef std::unordered_map StdUnorderedMap; + StdUnorderedMap map; + for (uint32_t jj = 0; jj < numElements; ++jj) + { + std::pair ok = map.insert(std::make_pair(uint64_t(jj), uint16_t(jj) ) ); + assert(ok.second); + } + } + + elapsed += bx::getHPCounter(); + printf(" STL: %15f\n", double(elapsed) ); + } + + /// + { + int64_t elapsed = -bx::getHPCounter(); + + for (uint32_t ii = 0; ii < numIterations; ++ii) + { + typedef bx::HandleHashMapT HandleHashMap; + HandleHashMap map; + for (uint32_t jj = 0; jj < numElements; ++jj) + { + bool ok = map.insert(jj, uint16_t(jj) ); + assert(ok); + } + } + + elapsed += bx::getHPCounter(); + printf("HandleHashMap: %15f\n", double(elapsed) ); + } + + return EXIT_SUCCESS; +} diff --git a/tests/handle_test.cpp b/tests/handle_test.cpp new file mode 100644 index 0000000..29019e3 --- /dev/null +++ b/tests/handle_test.cpp @@ -0,0 +1,119 @@ +/* + * Copyright 2010-2016 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx#license-bsd-2-clause + */ + +#include "test.h" +#include +#include + +TEST_CASE("HandleListT", "") +{ + bx::HandleListT<32> list; + + list.pushBack(16); + REQUIRE(list.getFront() == 16); + REQUIRE(list.getBack() == 16); + + list.pushFront(7); + REQUIRE(list.getFront() == 7); + REQUIRE(list.getBack() == 16); + + uint16_t expected0[] = { 15, 31, 7, 16, 17, 11, 13 }; + list.pushBack(17); + list.pushBack(11); + list.pushBack(13); + list.pushFront(31); + list.pushFront(15); + uint16_t count = 0; + for (uint16_t it = list.getFront(); it != UINT16_MAX; it = list.getNext(it), ++count) + { + REQUIRE(it == expected0[count]); + } + REQUIRE(count == BX_COUNTOF(expected0) ); + + list.remove(17); + list.remove(31); + list.remove(16); + list.pushBack(16); + uint16_t expected1[] = { 15, 7, 11, 13, 16 }; + count = 0; + for (uint16_t it = list.getFront(); it != UINT16_MAX; it = list.getNext(it), ++count) + { + REQUIRE(it == expected1[count]); + } + REQUIRE(count == BX_COUNTOF(expected1) ); + + list.popBack(); + list.popFront(); + list.popBack(); + list.popBack(); + + REQUIRE(list.getFront() == 7); + REQUIRE(list.getBack() == 7); + + list.popBack(); + REQUIRE(list.getFront() == UINT16_MAX); + REQUIRE(list.getBack() == UINT16_MAX); +} + +TEST_CASE("HandleAllocLruT", "") +{ + bx::HandleAllocLruT<16> lru; + + uint16_t handle[4] = + { + lru.alloc(), + lru.alloc(), + lru.alloc(), + lru.alloc(), + }; + + lru.touch(handle[1]); + + uint16_t expected0[] = { handle[1], handle[3], handle[2], handle[0] }; + uint16_t count = 0; + for (uint16_t it = lru.getFront(); it != UINT16_MAX; it = lru.getNext(it), ++count) + { + REQUIRE(it == expected0[count]); + } +} + +TEST_CASE("HandleHashTable", "") +{ + typedef bx::HandleHashMapT<512> HashMap; + + HashMap hm; + + REQUIRE(512 == hm.getMaxCapacity() ); + + bx::StringView sv0("test0"); + + bool ok = hm.insert(bx::hashMurmur2A(sv0), 0); + REQUIRE(ok); + + ok = hm.insert(bx::hashMurmur2A(sv0), 0); + REQUIRE(!ok); + REQUIRE(1 == hm.getNumElements() ); + + bx::StringView sv1("test1"); + + ok = hm.insert(bx::hashMurmur2A(sv1), 0); + REQUIRE(ok); + REQUIRE(2 == hm.getNumElements() ); + + hm.removeByHandle(0); + REQUIRE(0 == hm.getNumElements() ); + + ok = hm.insert(bx::hashMurmur2A(sv0), 0); + REQUIRE(ok); + + hm.removeByKey(bx::hashMurmur2A(sv0) ); + REQUIRE(0 == hm.getNumElements() ); + + for (uint32_t ii = 0, num = hm.getMaxCapacity(); ii < num; ++ii) + { + ok = hm.insert(ii, uint16_t(ii) ); + REQUIRE(ok); + } +} diff --git a/tests/macros.cpp b/tests/macros_test.cpp similarity index 100% rename from tests/macros.cpp rename to tests/macros_test.cpp diff --git a/tests/main.cpp b/tests/main_test.cpp similarity index 100% rename from tests/main.cpp rename to tests/main_test.cpp diff --git a/tests/misc.cpp b/tests/misc_test.cpp similarity index 100% rename from tests/misc.cpp rename to tests/misc_test.cpp diff --git a/tests/simd_t.cpp b/tests/simd_test.cpp similarity index 100% rename from tests/simd_t.cpp rename to tests/simd_test.cpp diff --git a/tests/string_test.cpp b/tests/string_test.cpp new file mode 100644 index 0000000..390a75b --- /dev/null +++ b/tests/string_test.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2010-2016 Branimir Karadzic. All rights reserved. + * License: https://github.com/bkaradzic/bx#license-bsd-2-clause + */ + +#include "test.h" +#include +#include +#include + +bx::AllocatorI* g_allocator; + +TEST_CASE("StringView", "") +{ + bx::StringView sv("test"); + REQUIRE(4 == sv.getLength() ); + + bx::CrtAllocator crt; + g_allocator = &crt; + + typedef bx::StringT<&g_allocator> String; + + String st(sv); + REQUIRE(4 == st.getLength() ); + + st.clear(); + REQUIRE(0 == st.getLength() ); + REQUIRE(4 == sv.getLength() ); + + sv.clear(); + REQUIRE(0 == sv.getLength() ); +} diff --git a/tests/thread.cpp b/tests/thread_test.cpp similarity index 100% rename from tests/thread.cpp rename to tests/thread_test.cpp diff --git a/tests/tokenizecmd.cpp b/tests/tokenizecmd_test.cpp similarity index 100% rename from tests/tokenizecmd.cpp rename to tests/tokenizecmd_test.cpp diff --git a/tests/uint32_t.cpp b/tests/uint32_test.cpp similarity index 100% rename from tests/uint32_t.cpp rename to tests/uint32_test.cpp diff --git a/tests/unordered_map_nonpod.cpp b/tests/unordered_map_nonpod_test.cpp similarity index 100% rename from tests/unordered_map_nonpod.cpp rename to tests/unordered_map_nonpod_test.cpp diff --git a/tests/unordered_set_copyctor.cpp b/tests/unordered_set_copyctor_test.cpp similarity index 100% rename from tests/unordered_set_copyctor.cpp rename to tests/unordered_set_copyctor_test.cpp diff --git a/tests/unordered_set_pod.cpp b/tests/unordered_set_pod_test.cpp similarity index 100% rename from tests/unordered_set_pod.cpp rename to tests/unordered_set_pod_test.cpp diff --git a/tests/vector_complex.cpp b/tests/vector_complex_test.cpp similarity index 100% rename from tests/vector_complex.cpp rename to tests/vector_complex_test.cpp diff --git a/tests/vector_header.cpp b/tests/vector_header_test.cpp similarity index 100% rename from tests/vector_header.cpp rename to tests/vector_header_test.cpp diff --git a/tests/vector_nocopy.cpp b/tests/vector_nocopy_test.cpp similarity index 100% rename from tests/vector_nocopy.cpp rename to tests/vector_nocopy_test.cpp diff --git a/tests/vector_nodefault.cpp b/tests/vector_nodefault_test.cpp similarity index 100% rename from tests/vector_nodefault.cpp rename to tests/vector_nodefault_test.cpp diff --git a/tests/vector_primitive.cpp b/tests/vector_primitive_test.cpp similarity index 100% rename from tests/vector_primitive.cpp rename to tests/vector_primitive_test.cpp diff --git a/tests/vector_shrinktofit.cpp b/tests/vector_shrinktofit_test.cpp similarity index 100% rename from tests/vector_shrinktofit.cpp rename to tests/vector_shrinktofit_test.cpp