blob: 19fdae527da55fff43fbc70db63919e8c19140dd [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once
namespace JsUtil
{
// - Maintains an MRU linked list of fixed maximum length and a dictionary that contains all added values
// - RemoveRecentlyUnusedItems should be called to remove values that are not in the MRU list from the dictionary
// - Added values are linked to the beginning of the MRU list (most recently used) as part of an MruListEntry, and when the
// list is full, entries from the end of the list (least recently used) are reused for the new values
// - TryGetValue, if the key exists in the dictionary, adds the value to the MRU list as the most recently used value, and
// removes the least recently used value as necessary
template<
class TKey,
class TValue,
class TAllocator = Recycler,
class TSizePolicy = PowerOf2SizePolicy,
template<class ValueOrKey> class TComparer = DefaultComparer,
template<class K, class V> class TDictionaryEntry = SimpleDictionaryEntry>
class MruDictionary
{
private:
struct MruListEntry : public DoublyLinkedListElement<MruListEntry, TAllocator>
{
Field(TValue, TAllocator) value;
Field(TKey, TAllocator) key;
Field(int) dictionaryDataIndex;
MruListEntry(const TKey &key, const TValue &value) : key(key), value(value), dictionaryDataIndex(0)
{
}
PREVENT_COPY(MruListEntry);
};
public:
class MruDictionaryData
{
private:
Field(MruListEntry *, TAllocator) entry;
Field(TValue, TAllocator) value;
public:
MruDictionaryData() : entry(nullptr)
{
}
MruDictionaryData &operator =(const void *const nullValue)
{
// Needed to support KeyValueEntry::Clear for dictionaries
Assert(!nullValue);
entry = nullptr;
value = nullptr; // TValue must also support this for the same reason
return *this;
}
MruListEntry *Entry() const
{
return entry;
}
const TValue &Value() const
{
Assert(!entry);
return value;
}
void OnAddedToMruList(MruListEntry *const entry)
{
Assert(!this->entry);
this->entry = entry;
}
void OnRemovedFromMruList()
{
Assert(entry);
value = entry->value;
entry = nullptr;
}
};
private:
Field(const int) mruListCapacity;
Field(int) mruListCount;
typedef DoublyLinkedList<MruListEntry, TAllocator> EntriesType;
Field(EntriesType) entries;
typedef
BaseDictionary<
TKey,
MruDictionaryData,
// MruDictionaryData always has pointer to GC pointer (MruEntry)
typename ForceNonLeafAllocator<TAllocator>::AllocatorType,
TSizePolicy,
TComparer,
TDictionaryEntry>
TDictionary;
TDictionary dictionary;
typedef typename TDictionary::AllocatorType AllocatorType;
public:
MruDictionary(AllocatorType *const allocator, const int mruListCapacity)
: mruListCapacity(mruListCapacity), mruListCount(0), dictionary(allocator)
{
Assert(allocator);
Assert(mruListCapacity > 0);
}
static MruDictionary *New(TAllocator *const allocator, DECLSPEC_GUARD_OVERFLOW const int mruListCapacity)
{
return AllocatorNew(TAllocator, allocator, MruDictionary, allocator, mruListCapacity);
}
private:
void AddToDictionary(MruListEntry *const entry)
{
const auto dictionaryDataIndex = dictionary.Add(entry->key, MruDictionaryData());
dictionary.GetReferenceAt(dictionaryDataIndex)->OnAddedToMruList(entry);
entry->dictionaryDataIndex = dictionaryDataIndex;
}
void ReuseLeastRecentlyUsedEntry(const TKey &key, const TValue &value, const int dictionaryDataIndex)
{
Assert(mruListCount == mruListCapacity);
// Reuse the least recently used entry for this key/value pair and make it the most recently used
const auto entry = entries.Tail();
dictionary.GetReferenceAt(dictionaryDataIndex)->OnAddedToMruList(entry);
dictionary.GetReferenceAt(entry->dictionaryDataIndex)->OnRemovedFromMruList();
entries.MoveToBeginning(entry);
entry->key = key;
entry->value = value;
entry->dictionaryDataIndex = dictionaryDataIndex;
}
public:
bool TryGetValue(const TKey &key, TValue *const value)
{
MruDictionaryData *dictionaryData;
int dictionaryDataIndex;
if(!dictionary.TryGetReference(key, &dictionaryData, &dictionaryDataIndex))
return false;
const auto entry = dictionaryData->Entry();
if(entry)
{
// Make this the most recently used entry
entries.MoveToBeginning(entry);
*value = entry->value;
return true;
}
*value = dictionaryData->Value();
// The key passed into this function may be temporary, and should not be placed in the MRU list or dictionary. Get
// the proper key to be used from the dictionary. That key should have the necessary lifetime.
ReuseLeastRecentlyUsedEntry(dictionary.GetKeyAt(dictionaryDataIndex), dictionaryData->Value(), dictionaryDataIndex);
return true;
}
void Add(const TKey &key, const TValue &value)
{
Assert(!dictionary.ContainsKey(key));
Assert(mruListCount <= mruListCapacity);
if(mruListCount == mruListCapacity)
{
ReuseLeastRecentlyUsedEntry(key, value, dictionary.Add(key, MruDictionaryData()));
return;
}
const auto entry = AllocatorNew(TAllocator, dictionary.GetAllocator(), MruListEntry, key, value);
AddToDictionary(entry);
entries.LinkToBeginning(entry);
++mruListCount;
}
void RemoveRecentlyUnusedItems()
{
if(dictionary.Count() == mruListCount)
return;
if(dictionary.Count() / 2 <= mruListCount)
{
dictionary.MapAndRemoveIf(
[](const typename TDictionary::EntryType &dictionaryEntry) -> bool
{
return !dictionaryEntry.Value().Entry();
});
return;
}
dictionary.Clear();
for(auto entry = entries.Head(); entry; entry = entry->Next())
AddToDictionary(entry);
}
PREVENT_COPY(MruDictionary);
};
}