enderalse/SKSE/Plugins/fs_skse_plugin_functions/common/IRangeMap.h
2021-10-06 02:45:46 +02:00

216 lines
4.4 KiB
C++

#pragma once
#include <map>
// t_key must be a numeric type
// ### you can't create a range taking up the entire range of t_key
// ### (could be done by switching from start/length -> start/end)
template <typename t_key, typename t_data>
class IRangeMap
{
public:
struct Entry
{
bool Contains(t_key addr, t_key base)
{
return (addr >= base) && (addr <= (base + length - 1));
}
t_key length;
t_data data;
};
typedef std::map <t_key, Entry> EntryMapType;
typedef typename EntryMapType::iterator Iterator;
IRangeMap()
{
//
}
virtual ~IRangeMap()
{
//
}
void Clear(void)
{
m_entries.clear();
}
t_data * Add(t_key start, t_key length)
{
t_data * result = NULL;
Entry * entry = NULL;
t_key end = start + length - 1;
if(end >= start) // check for overflow ### should also check for overflow on length - 1, but that's pedantic
{
// special-case empty lists
if(m_entries.empty())
{
entry = &m_entries[start];
}
else
{
// collision check
EntryMapType::iterator iter = m_entries.lower_bound(start);
// iter contains the first entry at or after start (or null)
if(iter == m_entries.begin())
{
// there can't be anything before this entry
// so we only need to check if it's colliding with us
if(iter->first > end)
{
// can't provide a hint because we're inserting at the top
entry = &m_entries[start];
}
}
else
{
// see if this entry doesn't collide
// can be null (null entries don't collide)
if((iter == m_entries.end()) || (iter->first > end))
{
// we didn't get the first entry in the map
// and there is at least one entry in the map
// therefore there's an entry before iter
EntryMapType::iterator preIter = iter;
preIter--;
// check if this collides
// guaranteed to be the first entry before start
t_key preEnd = preIter->first + preIter->second.length - 1;
if(preEnd < start)
{
// cool, everything's fine, allocate it
EntryMapType::iterator newEntry = m_entries.insert(preIter, EntryMapType::value_type(start, Entry()));
entry = &newEntry->second;
}
}
}
}
}
// set up the entry
if(entry)
{
entry->length = length;
result = &entry->data;
}
return result;
}
t_data * Lookup(t_key addr, t_key * base = NULL, t_key * length = NULL)
{
t_data * result = NULL;
EntryMapType::iterator iter = LookupIter(addr);
if(iter != m_entries.end())
{
if(base) *base = iter->first;
if(length) *length = iter->second.length;
result = &iter->second.data;
}
return result;
}
bool Erase(t_key addr, t_key * base = NULL, t_key * length = NULL)
{
bool result = false;
EntryMapType::iterator iter = LookupIter(addr);
if(iter != m_entries.end())
{
if(base) *base = iter->first;
if(length) *length = iter->second.length;
m_entries.erase(iter);
result = true;
}
return result;
}
t_key GetDataRangeLength(t_data * data)
{
Entry * entry = reinterpret_cast <Entry *>(reinterpret_cast <UInt8 *>(data) - offsetof(Entry, data));
return entry->length;
}
typename EntryMapType::iterator LookupIter(t_key addr)
{
EntryMapType::iterator result = m_entries.end();
if(!m_entries.empty())
{
// we need to find the last entry less than or equal to addr
// find the first entry not less than addr
EntryMapType::iterator iter = m_entries.lower_bound(addr);
// iter is either equal to addr, greater than addr, or the end
if(iter == m_entries.end())
{
// iter is the end
// can only be in the entry before this
// which does exist because map isn't empty
--iter;
if(iter->second.Contains(addr, iter->first))
{
result = iter;
}
}
// at this point iter must be valid
else if(iter->first > addr)
{
// iter is greater than addr
// can only be in the entry before this
// but there may not be an entry before this
if(iter != m_entries.begin())
{
--iter;
if(iter->second.Contains(addr, iter->first))
{
result = iter;
}
}
}
else
{
// iter is equal to addr and matches
result = iter;
}
}
return result;
}
typename EntryMapType::iterator Begin(void)
{
return m_entries.begin();
}
typename EntryMapType::iterator End(void)
{
return m_entries.end();
}
private:
EntryMapType m_entries;
};