From e0cdbf7dabf80ab090858de592dd479173e2f86d Mon Sep 17 00:00:00 2001 From: Rob Parolin Date: Mon, 29 Apr 2019 16:00:33 -0700 Subject: [PATCH] release candidate --- .gitlab-ci.yml | 14 - include/EASTL/bonus/lru_cache.h | 407 ++++++++++++++++++ include/EASTL/internal/char_traits.h | 159 ++----- include/EASTL/internal/config.h | 4 +- test/packages/EABase/.gitlab-ci.yml | 7 - .../include/Common/EABase/config/eacompiler.h | 2 + test/source/EASTLTest.h | 32 +- test/source/TestLruCache.cpp | 286 ++++++++++++ test/source/main.cpp | 1 + 9 files changed, 735 insertions(+), 177 deletions(-) delete mode 100644 .gitlab-ci.yml create mode 100644 include/EASTL/bonus/lru_cache.h delete mode 100644 test/packages/EABase/.gitlab-ci.yml create mode 100644 test/source/TestLruCache.cpp diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml deleted file mode 100644 index e75c69e8..00000000 --- a/.gitlab-ci.yml +++ /dev/null @@ -1,14 +0,0 @@ -image: registry.gitlab.ea.com/dre/automation/frostbite/framework-linux:7.19.01 - -before_script: - - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.ea.com/foundation/foundation-ci-files.git ci-files - -debug: - script: - - mono $FRAMEWORK_HOME/bin/nant.exe -masterconfigfile:ci-files/dev.xml -buildroot:/br -D:config=unix64-clang-dev-debug test-run -debug-opt: - script: - - mono $FRAMEWORK_HOME/bin/nant.exe -masterconfigfile:ci-files/dev.xml -buildroot:/br -D:config=unix64-clang-dev-debug-opt test-run -opt: - script: - - mono $FRAMEWORK_HOME/bin/nant.exe -masterconfigfile:ci-files/dev.xml -buildroot:/br -D:config=unix64-clang-dev-opt test-run diff --git a/include/EASTL/bonus/lru_cache.h b/include/EASTL/bonus/lru_cache.h new file mode 100644 index 00000000..5c1c32e3 --- /dev/null +++ b/include/EASTL/bonus/lru_cache.h @@ -0,0 +1,407 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// lru_cache is a container that simplifies caching of objects in a map. +// Basically, you give the container a key, like a string, and the data you want. +// The container provides callback mechanisms to generate data if it's missing +// as well as delete data when it's purged from the cache. This container +// uses a least recently used method: whatever the oldest item is will be +// replaced with a new entry. +// +// Algorithmically, the container is a combination of a map and a list. +// The list stores the age of the entries by moving the entry to the head +// of the list on each access, either by a call to get() or to touch(). +// The map is just the map as one would expect. +// +// This is useful for caching off data that is expensive to generate, +// for example text to speech wave files that are dynamically generated, +// but that will need to be reused, as is the case in narration of menu +// entries as a user scrolls through the entries. +/////////////////////////////////////////////////////////////////////////////// + +#ifndef EASTL_LRUCACHE_H +#define EASTL_LRUCACHE_H + +#if defined(EA_PRAGMA_ONCE_SUPPORTED) +#pragma once +#endif + +#include +#include +#include + +namespace eastl +{ + /// EASTL_LRUCACHE_DEFAULT_NAME + /// + /// Defines a default container name in the absence of a user-provided name. + /// + #ifndef EASTL_LRUCACHE_DEFAULT_NAME + #define EASTL_LRUCACHE_DEFAULT_NAME EASTL_DEFAULT_NAME_PREFIX " lru_cache" // Unless the user overrides something, this is "EASTL lru_cache". + #endif + + + /// EASTL_LRUCACHE_DEFAULT_ALLOCATOR + /// + #ifndef EASTL_LRUCACHE_DEFAULT_ALLOCATOR + #define EASTL_LRUCACHE_DEFAULT_ALLOCATOR allocator_type(EASTL_LRUCACHE_DEFAULT_NAME) + #endif + + /// lru_cache + /// + /// Implements a caching map based off of a key and data. + /// LRUList parameter is any container that guarantees the validity of its iterator even after a modification (e.g. list) + /// LRUMap is any mapping container that can map a key to some data. By default, we use unordered_set, but it might be better + /// to use hash_map or some other structure depending on your key/data combination. For example, you may want to swap the + /// map backing if using strings as keys or if the data objects are small. In any case, unordered_set is a good default and should + /// work well enough since the purpose of this class is to cache results of expensive, order of milliseconds, operations + /// + /// Algorithmic Performance (default data structures): + /// touch() -> O(1) + /// insert() / update(), get() / operator[] -> equivalent to unordered_set (O(1) on average, O(n) worst) + /// size() -> O(1) + /// + /// All accesses to a given key (insert, update, get) will push that key to most recently used. + /// If the data objects are shared between threads, it would be best to use a smartptr to manage the lifetime of the data. + /// as it could be removed from the cache while in use by another thread. + template , + typename map_type = eastl::unordered_map, eastl::hash, eastl::equal_to, Allocator > > + class lru_cache + { + public: + using key_type = Key; + using value_type = Value; + using allocator_type = Allocator; + using size_type = eastl_size_t; + using list_iterator = typename list_type::iterator; + using map_iterator = typename map_type::iterator; + using data_container_type = eastl::pair; + using iterator = typename map_type::iterator; + using const_iterator = typename map_type::const_iterator; + using this_type = lru_cache ; + using create_callback_type = eastl::function; + using delete_callback_type = eastl::function; + + /// lru_cache constructor + /// + /// Creates a Key / Value map that only stores size Value objects until it deletes them. + /// For complex objects or operations, the creator and deletor callbacks can be used. + /// This works just like a regular map object: on access, the Value will be created if it doesn't exist, returned otherwise. + explicit lru_cache(size_type size, + const allocator_type &allocator = EASTL_LRUCACHE_DEFAULT_ALLOCATOR, + create_callback_type creator = nullptr, + delete_callback_type deletor = nullptr) + : m_list(allocator) + , m_map(allocator) + , m_capacity(size) + , m_create_callback(creator) + , m_delete_callback(deletor) + {} + + /// lru_cache destructor + /// + /// Iterates across every entry in the map and calls the deletor before calling the standard destructors + ~lru_cache() + { + // Destruct everything we have cached + for (auto &iter : m_map) + { + if (m_delete_callback) m_delete_callback(iter.second.first); + } + } + + lru_cache(this_type &) = delete; + this_type &operator=(const this_type&) = delete; + + /// insert + /// + /// insert key k with value v. + /// If key already exists, no change is made and the return value is false. + /// If the key doesn't exist, the data is added to the map and the return value is true. + bool insert(const key_type &k, const value_type &v) + { + if (m_map.find(k) == m_map.end()) + { + make_space(); + + m_list.push_front(k); + m_map[k] = data_container_type(v, m_list.begin()); + + return true; + } + else + { + return false; + } + } + + /// emplace + /// + /// Places a new object in place k created with args + /// If the key already exists, it is replaced. + template + void emplace(const key_type &k, Args&&... args) + { + make_space(); + + m_list.push_front(k); + m_map.emplace(k, data_container_type(eastl::forward(args)..., m_list.begin())); + } + + /// insert_or_assign + /// + /// Same as add, but replaces the data at key k, if it exists, with the new entry v + /// Note that the deletor for the old v will be called before it's replaced with the new value of v + void insert_or_assign(const key_type &k, const value_type &v) + { + auto iter = m_map.find(k); + + if (m_map.find(k) != m_map.end()) + { + assign(iter, v); + } + else + { + insert(k, v); + } + } + + /// contains + /// + /// Returns true if key k exists in the cache + bool contains(const key_type &k) const + { + return m_map.find(k) != m_map.end(); + } + + /// at + /// + /// Retrives the data for key k, not valid if k does not exist + eastl::optional at(const key_type &k) + { + auto iter = m_map.find(k); + + if (iter != m_map.end()) + { + return iter->second.first; + } + else + { + return eastl::nullopt; + } + } + + /// get + /// + /// Retrives the data for key k. If no data exists, it will be created by calling the + /// creator. + value_type &get(const key_type &k) + { + auto iter = m_map.find(k); + + // The entry exists in the cache + if (iter != m_map.end()) + { + touch(k); + return iter->second.first; + } + else // The entry doesn't exist in the cache, so create one + { + // Add the entry to the map + insert(k, m_create_callback ? m_create_callback(k) : value_type()); + + // return the new data + return m_map[k].first; + } + } + + /// Equivalent to get(k) + value_type &operator[](const key_type &k) { return get(k); } + + /// erase + /// + /// erases key k from the cache. + /// If k does not exist, returns false. If k exists, returns true. + bool erase(const key_type &k) + { + auto iter = m_map.find(k); + + if (iter != m_map.end()) + { + m_list.erase(iter->second.second); + + // Delete the actual entry + map_erase(iter); + + return true; + } + + return false; + } + + /// erase_oldest + /// + /// Removes the oldest entry from the cache. + void erase_oldest() + { + auto key = m_list.back(); + m_list.pop_back(); + + // Delete the actual entry + auto iter = m_map.find(key); + map_erase(iter); + } + + /// touch + /// + /// Touches key k, marking it as most recently used. + /// If k does not exist, returns false. If the touch was successful, returns true. + bool touch(const key_type &k) + { + auto iter = m_map.find(k); + + if (iter != m_map.end()) + { + touch(iter); + return true; + } + + return false; + } + + /// touch + /// + /// Touches key at iterator iter, moving it to most recently used position + void touch(iterator &iter) + { + auto listRef = iter->second.second; + + m_list.erase(listRef); + m_list.push_front(iter->first); + iter->second.second = m_list.begin(); + } + + /// assign + /// + /// Updates key k with data v. + /// If key k does not exist, returns false and no changes are made. + /// If key k exists, existing data has its deletor called and key k's data is replaced with new v data + bool assign(const key_type &k, const value_type &v) + { + auto iter = m_map.find(k); + + if (iter != m_map.end()) + { + assign(iter, v); + return true; + } + + return false; + } + + /// assign + /// + /// Updates data at spot iter with data v. + void assign(iterator &iter, const value_type &v) + { + if (m_delete_callback) + m_delete_callback(iter->second.first); + touch(iter); + iter->second.first = v; + } + + // standard container functions + iterator begin() EA_NOEXCEPT { return m_map.begin(); } + iterator end() EA_NOEXCEPT { return m_map.end(); } + iterator rbegin() EA_NOEXCEPT { return m_map.rbegin(); } + iterator rend() EA_NOEXCEPT { return m_map.rend(); } + const_iterator begin() const EA_NOEXCEPT { return m_map.begin(); } + const_iterator cbegin() const EA_NOEXCEPT { return m_map.cbegin(); } + const_iterator crbegin() const EA_NOEXCEPT { return m_map.crbegin(); } + const_iterator end() const EA_NOEXCEPT { return m_map.end(); } + const_iterator cend() const EA_NOEXCEPT { return m_map.cend(); } + const_iterator crend() const EA_NOEXCEPT { return m_map.crend(); } + + bool empty() const EA_NOEXCEPT { return m_map.empty(); } + size_type size() const EA_NOEXCEPT { return m_map.size(); } + size_type capacity() const EA_NOEXCEPT { return m_capacity; } + + void clear() EA_NOEXCEPT + { + // Since we have a delete callback, we want to reuse the trim function by cheating the max + // size to clear all the entries to avoid duplicating code. + auto old_max = m_capacity; + + m_capacity = 0; + trim(); + m_capacity = old_max; + } + + /// resize + /// + /// Resizes the cache. Can be used to either expand or contract the cache. + /// In the case of a contraction, the oldest entries will be evicted with their respective + /// deletors called before completing. + void resize(size_type newSize) + { + m_capacity = newSize; + trim(); + } + + void setCreateCallback(create_callback_type callback) { m_create_callback = callback; } + void setDeleteCallback(delete_callback_type callback) { m_delete_callback = callback; } + + // EASTL extensions + const allocator_type& get_allocator() const EA_NOEXCEPT { return m_map.get_allocator(); } + allocator_type& get_allocator() EA_NOEXCEPT { return m_map.get_allocator(); } + void set_allocator(const allocator_type& allocator) { m_map.set_allocator(allocator); m_list.set_allocator(allocator); } + + /// Does not reset the callbacks + void reset_lose_memory() EA_NOEXCEPT { m_map.reset_lose_memory(); m_list.reset_lose_memory(); } + + private: + inline void map_erase(map_iterator pos) + { + if (m_delete_callback) + m_delete_callback(pos->second.first); + m_map.erase(pos); + } + + bool trim() + { + if (size() <= m_capacity) + { + return false; // No trim necessary + } + + // We need to trim + do + { + erase_oldest(); + } while (m_list.size() > m_capacity); + + return true; + } + + void make_space() + { + if (size() == m_capacity) + { + erase_oldest(); + } + } + + list_type m_list; + map_type m_map; + size_type m_capacity; + create_callback_type m_create_callback; + delete_callback_type m_delete_callback; + }; +} + + + +#endif diff --git a/include/EASTL/internal/char_traits.h b/include/EASTL/internal/char_traits.h index 8561fc00..cdf9c237 100644 --- a/include/EASTL/internal/char_traits.h +++ b/include/EASTL/internal/char_traits.h @@ -14,7 +14,9 @@ #ifndef EASTL_CHAR_TRAITS_H #define EASTL_CHAR_TRAITS_H -EA_ONCE() +#if defined(EA_PRAGMA_ONCE_SUPPORTED) + #pragma once +#endif #include #include @@ -132,31 +134,17 @@ namespace eastl inline char8_t CharToLower(char8_t c) { return (char8_t)tolower((uint8_t)c); } - inline char16_t CharToLower(char16_t c) - { if((unsigned)c <= 0xff) return (char16_t)tolower((uint8_t)c); return c; } - - inline char32_t CharToLower(char32_t c) - { if((unsigned)c <= 0xff) return (char32_t)tolower((uint8_t)c); return c; } - - #if defined(EA_WCHAR_UNIQUE) && EA_WCHAR_UNIQUE - inline wchar_t CharToLower(wchar_t c) - { if((unsigned)c <= 0xff) return (wchar_t)tolower((uint8_t)c); return c; } - #endif + template + inline T CharToLower(T c) + { if((unsigned)c <= 0xff) return (T)tolower((uint8_t)c); return c; } inline char8_t CharToUpper(char8_t c) { return (char8_t)toupper((uint8_t)c); } - inline char16_t CharToUpper(char16_t c) - { if((unsigned)c <= 0xff) return (char16_t)toupper((uint8_t)c); return c; } - - inline char32_t CharToUpper(char32_t c) - { if((unsigned)c <= 0xff) return (char32_t)toupper((uint8_t)c); return c; } - - #if defined(EA_WCHAR_UNIQUE) && EA_WCHAR_UNIQUE - inline wchar_t CharToUpper(wchar_t c) - { if((unsigned)c <= 0xff) return (wchar_t)toupper((uint8_t)c); return c; } - #endif + template + inline T CharToUpper(T c) + { if((unsigned)c <= 0xff) return (T)toupper((uint8_t)c); return c; } template @@ -176,6 +164,7 @@ namespace eastl return memcmp(p1, p2, n); } + template inline int CompareI(const T* p1, const T* p2, size_t n) { @@ -192,23 +181,8 @@ namespace eastl } - inline const char8_t* Find(const char8_t* p, char8_t c, size_t n) - { - return (const char8_t*)memchr(p, c, n); - } - - inline const char16_t* Find(const char16_t* p, char16_t c, size_t n) - { - for(; n > 0; --n, ++p) - { - if(*p == c) - return p; - } - - return NULL; - } - - inline const char32_t* Find(const char32_t* p, char32_t c, size_t n) + template + inline const T* Find(const T* p, T c, size_t n) { for(; n > 0; --n, ++p) { @@ -219,52 +193,21 @@ namespace eastl return NULL; } - #if defined(EA_WCHAR_UNIQUE) && EA_WCHAR_UNIQUE - inline const wchar_t* Find(const wchar_t* p, wchar_t c, size_t n) - { - for(; n > 0; --n, ++p) - { - if(*p == c) - return p; - } - - return NULL; - } - #endif - - inline EA_CPP14_CONSTEXPR size_t CharStrlen(const char8_t* p) + inline const char8_t* Find(const char8_t* p, char8_t c, size_t n) { - const char8_t* pCurrent = p; - while(*pCurrent) - ++pCurrent; - return (size_t)(pCurrent - p); + return (const char8_t*)memchr(p, c, n); } - inline EA_CPP14_CONSTEXPR size_t CharStrlen(const char16_t* p) - { - const char16_t* pCurrent = p; - while(*pCurrent) - ++pCurrent; - return (size_t)(pCurrent - p); - } - inline EA_CPP14_CONSTEXPR size_t CharStrlen(const char32_t* p) + template + inline EA_CPP14_CONSTEXPR size_t CharStrlen(const T* p) { - const char32_t* pCurrent = p; + const auto* pCurrent = p; while(*pCurrent) ++pCurrent; return (size_t)(pCurrent - p); } - #if defined(EA_WCHAR_UNIQUE) && EA_WCHAR_UNIQUE - inline EA_CPP14_CONSTEXPR size_t CharStrlen(const wchar_t* p) - { - const wchar_t* pCurrent = p; - while(*pCurrent) - ++pCurrent; - return (size_t)(pCurrent - p); - } - #endif template inline T* CharStringUninitializedCopy(const T* pSource, const T* pSourceEnd, T* pDestination) @@ -273,6 +216,7 @@ namespace eastl return pDestination + (pSourceEnd - pSource); } + template const T* CharTypeStringFindEnd(const T* pBegin, const T* pEnd, T c) { @@ -285,6 +229,7 @@ namespace eastl return pEnd; } + template const T* CharTypeStringRSearch(const T* p1Begin, const T* p1End, @@ -331,6 +276,7 @@ namespace eastl return p1End; } + template inline const T* CharTypeStringFindFirstOf(const T* p1Begin, const T* p1End, const T* p2Begin, const T* p2End) { @@ -345,6 +291,7 @@ namespace eastl return p1End; } + template inline const T* CharTypeStringRFindFirstNotOf(const T* p1RBegin, const T* p1REnd, const T* p2Begin, const T* p2End) { @@ -362,6 +309,7 @@ namespace eastl return p1REnd; } + template inline const T* CharTypeStringFindFirstNotOf(const T* p1Begin, const T* p1End, const T* p2Begin, const T* p2End) { @@ -379,6 +327,7 @@ namespace eastl return p1End; } + template inline const T* CharTypeStringRFindFirstOf(const T* p1RBegin, const T* p1REnd, const T* p2Begin, const T* p2End) { @@ -393,6 +342,7 @@ namespace eastl return p1REnd; } + template inline const T* CharTypeStringRFind(const T* pRBegin, const T* pREnd, const T c) { @@ -413,34 +363,16 @@ namespace eastl return pDestination + n; } - inline char16_t* CharStringUninitializedFillN(char16_t* pDestination, size_t n, const char16_t c) - { - char16_t* pDest16 = pDestination; - const char16_t* const pEnd = pDestination + n; - while(pDest16 < pEnd) - *pDest16++ = c; - return pDestination + n; - } - - inline char32_t* CharStringUninitializedFillN(char32_t* pDestination, size_t n, const char32_t c) + template + inline T* CharStringUninitializedFillN(T* pDestination, size_t n, const T c) { - char32_t* pDest32 = pDestination; - const char32_t* const pEnd = pDestination + n; - while(pDest32 < pEnd) - *pDest32++ = c; + T * pDest = pDestination; + const T* const pEnd = pDestination + n; + while(pDest < pEnd) + *pDest++ = c; return pDestination + n; } - #if defined(EA_WCHAR_UNIQUE) && EA_WCHAR_UNIQUE - inline wchar_t* CharStringUninitializedFillN(wchar_t* pDestination, size_t n, const wchar_t c) - { - wchar_t* pDest32 = pDestination; - const wchar_t* const pEnd = pDestination + n; - while(pDest32 < pEnd) - *pDest32++ = c; - return pDestination + n; - } - #endif inline char8_t* CharTypeAssignN(char8_t* pDestination, size_t n, char8_t c) { @@ -449,34 +381,15 @@ namespace eastl return pDestination; } - inline char16_t* CharTypeAssignN(char16_t* pDestination, size_t n, char16_t c) + template + inline T* CharTypeAssignN(T* pDestination, size_t n, T c) { - char16_t* pDest16 = pDestination; - const char16_t* const pEnd = pDestination + n; - while(pDest16 < pEnd) - *pDest16++ = c; + T* pDest = pDestination; + const T* const pEnd = pDestination + n; + while(pDest < pEnd) + *pDest++ = c; return pDestination; } - - inline char32_t* CharTypeAssignN(char32_t* pDestination, size_t n, char32_t c) - { - char32_t* pDest32 = pDestination; - const char32_t* const pEnd = pDestination + n; - while(pDest32 < pEnd) - *pDest32++ = c; - return pDestination; - } - - #if defined(EA_WCHAR_UNIQUE) && EA_WCHAR_UNIQUE - inline wchar_t* CharTypeAssignN(wchar_t* pDestination, size_t n, wchar_t c) - { - wchar_t* pDest32 = pDestination; - const wchar_t* const pEnd = pDestination + n; - while(pDest32 < pEnd) - *pDest32++ = c; - return pDestination; - } - #endif } // namespace eastl #endif // EASTL_CHAR_TRAITS_H diff --git a/include/EASTL/internal/config.h b/include/EASTL/internal/config.h index 6f6b9e83..6b84dc0b 100644 --- a/include/EASTL/internal/config.h +++ b/include/EASTL/internal/config.h @@ -89,8 +89,8 @@ /////////////////////////////////////////////////////////////////////////////// #ifndef EASTL_VERSION - #define EASTL_VERSION "3.13.04" - #define EASTL_VERSION_N 31304 + #define EASTL_VERSION "3.13.05" + #define EASTL_VERSION_N 31305 #endif diff --git a/test/packages/EABase/.gitlab-ci.yml b/test/packages/EABase/.gitlab-ci.yml deleted file mode 100644 index 1b297724..00000000 --- a/test/packages/EABase/.gitlab-ci.yml +++ /dev/null @@ -1,7 +0,0 @@ -image: registry.gitlab.ea.com/dre/automation/frostbite/framework-linux:7.19.01 -build: - script: - - git clone https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.ea.com/foundation/foundation-ci-files.git ci-files - - mono $FRAMEWORK_HOME/bin/nant.exe -masterconfigfile:ci-files/dev.xml -buildroot:/br -D:config=unix64-clang-dev-debug test-run - - mono $FRAMEWORK_HOME/bin/nant.exe -masterconfigfile:ci-files/dev.xml -buildroot:/br -D:config=unix64-clang-dev-debug-opt test-run - - mono $FRAMEWORK_HOME/bin/nant.exe -masterconfigfile:ci-files/dev.xml -buildroot:/br -D:config=unix64-clang-dev-opt test-run diff --git a/test/packages/EABase/include/Common/EABase/config/eacompiler.h b/test/packages/EABase/include/Common/EABase/config/eacompiler.h index 9de621e8..2890b819 100644 --- a/test/packages/EABase/include/Common/EABase/config/eacompiler.h +++ b/test/packages/EABase/include/Common/EABase/config/eacompiler.h @@ -298,6 +298,8 @@ #if !defined(EA_COMPILER_CPP17_ENABLED) && defined(__cplusplus) #if (__cplusplus >= 201703L) #define EA_COMPILER_CPP17_ENABLED 1 + #elif defined(_MSVC_LANG) && (_MSVC_LANG >= 201703L) // C++17+ + #define EA_COMPILER_CPP17_ENABLED 1 #endif #endif diff --git a/test/source/EASTLTest.h b/test/source/EASTLTest.h index abbab956..56889692 100644 --- a/test/source/EASTLTest.h +++ b/test/source/EASTLTest.h @@ -52,6 +52,7 @@ int TestIntrusiveSList(); int TestIterator(); int TestList(); int TestListMap(); +int TestLruCache(); int TestMap(); int TestMemory(); int TestMeta(); @@ -147,37 +148,6 @@ int TestTupleVector(); -/////////////////////////////////////////////////////////////////////////////// -// EA_CHAR16 -// -// EA_CHAR16 is defined in EABase 2.0.20 and later. If we are using an earlier -// version of EABase then we replicate what EABase 2.0.20 does. -// -// -#ifndef EA_WCHAR - #define EA_WCHAR(s) L ## s -#endif - -#ifndef EA_CHAR16 - #if !defined(EA_CHAR16_NATIVE) - #if defined(_MSC_VER) && (_MSC_VER >= 1600) && defined(_HAS_CHAR16_T_LANGUAGE_SUPPORT) // VS2010+ - #define EA_CHAR16_NATIVE 1 - #elif defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 404) && (defined(__GXX_EXPERIMENTAL_CXX0X__) || defined(__STDC_VERSION__)) // g++ (C++ compiler) 4.4+ with -std=c++0x or gcc (C compiler) 4.4+ with -std=gnu99 - #define EA_CHAR16_NATIVE 1 - #else - #define EA_CHAR16_NATIVE 0 - #endif - #endif - - #if EA_CHAR16_NATIVE && !defined(_MSC_VER) // Microsoft doesn't support char16_t string literals. - #define EA_CHAR16(s) u ## s - #elif (EA_WCHAR_SIZE == 2) - #define EA_CHAR16(s) L ## s - #endif -#endif - - - /// EASTL_TestLevel /// diff --git a/test/source/TestLruCache.cpp b/test/source/TestLruCache.cpp new file mode 100644 index 00000000..383cb431 --- /dev/null +++ b/test/source/TestLruCache.cpp @@ -0,0 +1,286 @@ +///////////////////////////////////////////////////////////////////////////// +// Copyright (c) Electronic Arts Inc. All rights reserved. +///////////////////////////////////////////////////////////////////////////// + +#include "EASTLTest.h" +#include +#include + +namespace TestLruCacheInternal +{ + struct Foo + { + static int count; + + Foo() + : a(count++) + , b(count++) + { } + + Foo(int x, int y) : a(x), b(y) {} + + int a; + int b; + + bool operator==(const Foo &other) + { + return this->a == other.a && this->b == other.b; + } + }; + + int Foo::count = 0; + + class FooCreator + { + public: + FooCreator() : mFooCreatedCount(0) {} + + Foo *Create() + { + mFooCreatedCount++; + return new Foo(); + } + + void Destroy(Foo *f) + { + delete f; + mFooCreatedCount--; + } + + int mFooCreatedCount; + }; +} + + +int TestLruCache() +{ + int nErrorCount = 0; + + // Test simple situation + { + using namespace TestLruCacheInternal; + + eastl::lru_cache lruCache(3); + + // Empty state + EATEST_VERIFY(lruCache.contains(1) == false); + EATEST_VERIFY(lruCache.size() == 0); + EATEST_VERIFY(lruCache.empty() == true); + EATEST_VERIFY(lruCache.capacity() == 3); + EATEST_VERIFY(lruCache.at(1).has_value() == false); + + // Auto create with get call + EATEST_VERIFY(lruCache[0].a == 0); + EATEST_VERIFY(lruCache[0].b == 1); + EATEST_VERIFY(lruCache.contains(1) == false); + EATEST_VERIFY(lruCache.contains(0) == true); + EATEST_VERIFY(lruCache.size() == 1); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + + // Fill structure up to 2 more entries to fill out, also test at() + lruCache.insert(1, Foo(2, 3)); + EATEST_VERIFY(lruCache.at(1).value().a == 2); + EATEST_VERIFY(lruCache.at(1).value().b == 3); + EATEST_VERIFY(lruCache.contains(0) == true); + EATEST_VERIFY(lruCache.contains(1) == true); + EATEST_VERIFY(lruCache.contains(2) == false); + EATEST_VERIFY(lruCache.contains(3) == false); + EATEST_VERIFY(lruCache.size() == 2); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + + lruCache.insert(2, Foo(4, 5)); + EATEST_VERIFY(lruCache[2].a == 4); + EATEST_VERIFY(lruCache[2].b == 5); + EATEST_VERIFY(lruCache.contains(0) == true); + EATEST_VERIFY(lruCache.contains(1) == true); + EATEST_VERIFY(lruCache.contains(2) == true); + EATEST_VERIFY(lruCache.contains(3) == false); + EATEST_VERIFY(lruCache.size() == 3); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + + // Add another entry, at this point 0 is the oldest, so it should be pulled + lruCache.insert(3, Foo(6, 7)); + EATEST_VERIFY(lruCache[3].a == 6); + EATEST_VERIFY(lruCache[3].b == 7); + EATEST_VERIFY(lruCache.contains(0) == false); + EATEST_VERIFY(lruCache.contains(1) == true); + EATEST_VERIFY(lruCache.contains(2) == true); + EATEST_VERIFY(lruCache.contains(3) == true); + EATEST_VERIFY(lruCache.size() == 3); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + + // Touch the now oldest 1 key + EATEST_VERIFY(lruCache.touch(1) == true); + + // Add another entry, this will be #4 but since 1 was touched, 2 is now the oldest + lruCache.insert(4, Foo(8, 9)); + EATEST_VERIFY(lruCache[4].a == 8); + EATEST_VERIFY(lruCache[4].b == 9); + EATEST_VERIFY(lruCache.contains(0) == false); + EATEST_VERIFY(lruCache.contains(1) == true); + EATEST_VERIFY(lruCache.contains(2) == false); + EATEST_VERIFY(lruCache.contains(3) == true); + EATEST_VERIFY(lruCache.contains(4) == true); + EATEST_VERIFY(lruCache.size() == 3); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + + // Test resize down + EATEST_VERIFY(lruCache.touch(3) == true); // Let's make some key in the middle the most recent + lruCache.resize(1); // Resize down to 1 entry in the cache + EATEST_VERIFY(lruCache.contains(0) == false); + EATEST_VERIFY(lruCache.contains(1) == false); + EATEST_VERIFY(lruCache.contains(2) == false); + EATEST_VERIFY(lruCache.contains(3) == true); + EATEST_VERIFY(lruCache.contains(4) == false); + EATEST_VERIFY(lruCache.size() == 1); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 1); + + // Let's resize up to a size of 5 now + lruCache.resize(5); + EATEST_VERIFY(lruCache.contains(0) == false); + EATEST_VERIFY(lruCache.contains(1) == false); + EATEST_VERIFY(lruCache.contains(2) == false); + EATEST_VERIFY(lruCache.contains(3) == true); + EATEST_VERIFY(lruCache.contains(4) == false); + EATEST_VERIFY(lruCache.size() == 1); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 5); + + // Let's try updating + lruCache.assign(3, Foo(0, 0)); + EATEST_VERIFY(lruCache[3] == Foo(0, 0)); + EATEST_VERIFY(lruCache.contains(0) == false); + EATEST_VERIFY(lruCache.contains(1) == false); + EATEST_VERIFY(lruCache.contains(2) == false); + EATEST_VERIFY(lruCache.contains(3) == true); + EATEST_VERIFY(lruCache.contains(4) == false); + EATEST_VERIFY(lruCache.size() == 1); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 5); + + // add or update existing + lruCache.insert_or_assign(3, Foo(1, 1)); + EATEST_VERIFY(lruCache[3] == Foo(1, 1)); + EATEST_VERIFY(lruCache.contains(0) == false); + EATEST_VERIFY(lruCache.contains(1) == false); + EATEST_VERIFY(lruCache.contains(2) == false); + EATEST_VERIFY(lruCache.contains(3) == true); + EATEST_VERIFY(lruCache.contains(4) == false); + EATEST_VERIFY(lruCache.size() == 1); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 5); + + // Add or update a new entry + lruCache.insert_or_assign(25, Foo(2, 2)); + EATEST_VERIFY(lruCache[3] == Foo(1, 1)); + EATEST_VERIFY(lruCache[25] == Foo(2, 2)); + EATEST_VERIFY(lruCache.contains(0) == false); + EATEST_VERIFY(lruCache.contains(1) == false); + EATEST_VERIFY(lruCache.contains(2) == false); + EATEST_VERIFY(lruCache.contains(3) == true); + EATEST_VERIFY(lruCache.contains(4) == false); + EATEST_VERIFY(lruCache.contains(25) == true); + EATEST_VERIFY(lruCache.size() == 2); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 5); + + // clear everything + lruCache.clear(); + EATEST_VERIFY(lruCache.size() == 0); + EATEST_VERIFY(lruCache.empty() == true); + EATEST_VERIFY(lruCache.capacity() == 5); + EATEST_VERIFY(lruCache.contains(3) == false); + + // test unilateral reset + lruCache[1] = Foo(1, 2); + lruCache.reset_lose_memory(); + EATEST_VERIFY(lruCache.size() == 0); + } + + // Test more advanced creation / deletion via callbacks + { + using namespace TestLruCacheInternal; + + FooCreator fooCreator; + + auto createCallback = [&fooCreator](int k) { return fooCreator.Create(); }; + auto deleteCallback = [&fooCreator](Foo *f) { fooCreator.Destroy(f); }; + + eastl::lru_cache lruCache(3, EASTLAllocatorType("eastl lru_cache"), createCallback, deleteCallback); + + lruCache[1]; + EATEST_VERIFY(fooCreator.mFooCreatedCount == 1); + EATEST_VERIFY(lruCache.size() == 1); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + EATEST_VERIFY(lruCache.contains(1) == true); + EATEST_VERIFY(lruCache.contains(2) == false); + + lruCache[2]; + EATEST_VERIFY(fooCreator.mFooCreatedCount == 2); + EATEST_VERIFY(lruCache.size() == 2); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + EATEST_VERIFY(lruCache.contains(1) == true); + EATEST_VERIFY(lruCache.contains(2) == true); + + // Update 2, which should delete the existing entry + { + auto f = fooCreator.Create(); + EATEST_VERIFY(fooCreator.mFooCreatedCount == 3); + f->a = 20; + f->b = 21; + lruCache.assign(2, f); + EATEST_VERIFY(fooCreator.mFooCreatedCount == 2); + EATEST_VERIFY(lruCache.size() == 2); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + EATEST_VERIFY(lruCache.contains(1) == true); + EATEST_VERIFY(lruCache.contains(2) == true); + EATEST_VERIFY(lruCache[2]->a == 20); + EATEST_VERIFY(lruCache[2]->b == 21); + } + + lruCache.erase(2); + EATEST_VERIFY(fooCreator.mFooCreatedCount == 1); + EATEST_VERIFY(lruCache.size() == 1); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + EATEST_VERIFY(lruCache.contains(1) == true); + EATEST_VERIFY(lruCache.contains(2) == false); + + lruCache.erase(1); + EATEST_VERIFY(fooCreator.mFooCreatedCount == 0); + EATEST_VERIFY(lruCache.size() == 0); + EATEST_VERIFY(lruCache.empty() == true); + EATEST_VERIFY(lruCache.capacity() == 3); + EATEST_VERIFY(lruCache.contains(1) == false); + EATEST_VERIFY(lruCache.contains(2) == false); + + // Test insert_or_assign + { + auto f = fooCreator.Create(); + f->a = 22; + f->b = 30; + EATEST_VERIFY(fooCreator.mFooCreatedCount == 1); + + lruCache.insert_or_assign(7, f); + EATEST_VERIFY(lruCache.size() == 1); + EATEST_VERIFY(lruCache.empty() == false); + EATEST_VERIFY(lruCache.capacity() == 3); + EATEST_VERIFY(lruCache.contains(1) == false); + EATEST_VERIFY(lruCache.contains(2) == false); + EATEST_VERIFY(lruCache.contains(7) == true); + EATEST_VERIFY(lruCache.erase(7) == true); + EATEST_VERIFY(fooCreator.mFooCreatedCount == 0); + } + } + + return nErrorCount; +} diff --git a/test/source/main.cpp b/test/source/main.cpp index 3dce92c9..2dbc1ebe 100644 --- a/test/source/main.cpp +++ b/test/source/main.cpp @@ -112,6 +112,7 @@ int EAMain(int argc, char* argv[]) testSuite.AddTest("Iterator", TestIterator); testSuite.AddTest("List", TestList); testSuite.AddTest("ListMap", TestListMap); + testSuite.AddTest("LRUCache", TestLruCache); testSuite.AddTest("Map", TestMap); testSuite.AddTest("Memory", TestMemory); testSuite.AddTest("Meta", TestMeta);