Permalink
Browse files

inventory::invlet_cache overhaul

Update invlet_cache to be a bidirectional map -- mapping from ids to
invlets but also from invlets to ids.  This removes the need for
iterating over the whole invlet cache on several operations.

This cuts the runtime of the starting_items test by nearly a whole
minute on Linux debug builds.
  • Loading branch information...
jbytheway committed Dec 2, 2018
1 parent d8ad3af commit b1d8f4fdc9eba1c881929e7ca8366c03d9a208e2
Showing with 93 additions and 47 deletions.
  1. +65 −41 src/inventory.cpp
  2. +22 −2 src/inventory.h
  3. +6 −4 src/savegame_json.cpp
@@ -29,6 +29,67 @@ bool invlet_wrapper::valid( const long invlet ) const
return find( static_cast<char>( invlet ) ) != std::string::npos;
}

invlet_preferences::invlet_preferences( std::unordered_map<itype_id, std::string> map )
{
for( const auto &p : map ) {
if( p.second.empty() ) {
// The map gradually accumulates empty lists; remove those here
continue;
}
invlets_by_id.insert( p );
for( char invlet : p.second ) {
uint8_t invlet_u = invlet;
if( !ids_by_invlet[invlet_u].empty() ) {
debugmsg( "Duplicate invlet: %s and %s both mapped to %c",
ids_by_invlet[invlet_u], p.first, invlet );
}
ids_by_invlet[invlet_u] = p.first;
}
}
}

void invlet_preferences::set( char invlet, itype_id id )
{
erase( invlet );
uint8_t invlet_u = invlet;
ids_by_invlet[invlet_u] = id;
invlets_by_id[id].push_back( invlet );
}

void invlet_preferences::erase( char invlet )
{
uint8_t invlet_u = invlet;
const std::string &id = ids_by_invlet[invlet_u];
if( id.empty() ) {
return;
}
std::string &invlets = invlets_by_id[id];
std::string::iterator it = std::find( invlets.begin(), invlets.end(), invlet );
invlets.erase( it );
ids_by_invlet[invlet_u].clear();
}

bool invlet_preferences::contains( char invlet, itype_id id ) const
{
uint8_t invlet_u = invlet;
return ids_by_invlet[invlet_u] == id;
}

std::string invlet_preferences::invlets_for( itype_id id ) const
{
auto map_iterator = invlets_by_id.find( id );
if( map_iterator == invlets_by_id.end() ) {
return {};
}
return map_iterator->second;
}

const std::unordered_map<itype_id, std::string> &
invlet_preferences::get_invlets_by_id() const
{
return invlets_by_id;
}

inventory::inventory() = default;

invslice inventory::slice()
@@ -149,38 +210,14 @@ void inventory::update_cache_with_item( item &newit )
if( newit.invlet == 0 ) {
return;
}
// Iterator over all the keys of the map.
std::map<std::string, std::vector<char> >::iterator i;
for( i = invlet_cache.begin(); i != invlet_cache.end(); i++ ) {
std::string type = i->first;
std::vector<char> &preferred_invlets = i->second;

if( newit.typeId() != type ) {
// Erase the used invlet from all caches.
for( size_t ind = 0; ind < preferred_invlets.size(); ++ind ) {
if( preferred_invlets[ind] == newit.invlet ) {
preferred_invlets.erase( preferred_invlets.begin() + ind );
ind--;
}
}
}
}

// Append the selected invlet to the list of preferred invlets of this item type.
std::vector<char> &pref = invlet_cache[newit.typeId()];
if( std::find( pref.begin(), pref.end(), newit.invlet ) == pref.end() ) {
pref.push_back( newit.invlet );
}
invlet_cache.set( newit.invlet, newit.typeId() );
}

char inventory::find_usable_cached_invlet( const std::string &item_type )
{
if( ! invlet_cache.count( item_type ) ) {
return 0;
}

// Some of our preferred letters might already be used.
for( auto invlet : invlet_cache[item_type] ) {
for( auto invlet : invlet_cache.invlets_for( item_type ) ) {
// Don't overwrite user assignments.
if( assigned_invlet.count( invlet ) ) {
continue;
@@ -981,14 +1018,7 @@ void inventory::reassign_item( item &it, char invlet, bool remove_old )
return;
}
if( remove_old && it.invlet ) {
auto invlet_list_iter = invlet_cache.find( it.typeId() );
if( invlet_list_iter != invlet_cache.end() ) {
auto &invlet_list = invlet_list_iter->second;
invlet_list.erase( std::remove_if( invlet_list.begin(),
invlet_list.end(), [&it]( char cached_invlet ) {
return cached_invlet == it.invlet;
} ), invlet_list.end() );
}
invlet_cache.erase( it.invlet );
}
it.invlet = invlet;
update_cache_with_item( it );
@@ -1004,13 +1034,7 @@ void inventory::update_invlet( item &newit, bool assign_invlet )

// Remove letters that are not in the favorites cache
if( newit.invlet ) {
auto invlet_list_iter = invlet_cache.find( newit.typeId() );
bool found = false;
if( invlet_list_iter != invlet_cache.end() ) {
auto &invlet_list = invlet_list_iter->second;
found = std::find( invlet_list.begin(), invlet_list.end(), newit.invlet ) != invlet_list.end();
}
if( !found ) {
if( !invlet_cache.contains( newit.invlet, newit.typeId() ) ) {
newit.invlet = '\0';
}
}
@@ -50,6 +50,27 @@ class invlet_wrapper : private std::string

const extern invlet_wrapper inv_chars;

// For each item id, store a set of "favorite" inventory letters.
// This class maintains a bidirectional mapping between invlet letters and item ids.
// Each invlet has at most one id and each id has any number of invlets.
class invlet_preferences
{
public:
invlet_preferences() = default;
invlet_preferences( std::unordered_map<itype_id, std::string> );

void set( char invlet, itype_id );
void erase( char invlet );
bool contains( char invlet, itype_id ) const;
std::string invlets_for( itype_id ) const;

// For serialization only
const std::unordered_map<itype_id, std::string> &get_invlets_by_id() const;
private:
std::unordered_map<itype_id, std::string> invlets_by_id;
std::array<itype_id, 256> ids_by_invlet;
};

class inventory : public visitable<inventory>
{
public:
@@ -180,8 +201,7 @@ class inventory : public visitable<inventory>
void copy_invlet_of( const inventory &other );

private:
// For each item ID, store a set of "favorite" inventory letters.
std::map<std::string, std::vector<char> > invlet_cache;
invlet_preferences invlet_cache;
char find_usable_cached_invlet( const std::string &item_type );

invstack items;
@@ -1369,7 +1369,7 @@ void npc::store( JsonOut &json ) const
void inventory::json_save_invcache( JsonOut &json ) const
{
json.start_array();
for( const auto &elem : invlet_cache ) {
for( const auto &elem : invlet_cache.get_invlets_by_id() ) {
json.start_object();
json.member( elem.first );
json.start_array();
@@ -1388,19 +1388,21 @@ void inventory::json_save_invcache( JsonOut &json ) const
void inventory::json_load_invcache( JsonIn &jsin )
{
try {
std::unordered_map<itype_id, std::string> map;
JsonArray ja = jsin.get_array();
while( ja.has_more() ) {
JsonObject jo = ja.next_object();
std::set<std::string> members = jo.get_member_names();
for( const auto &member : members ) {
std::vector<char> vect;
std::string invlets;
JsonArray pvect = jo.get_array( member );
while( pvect.has_more() ) {
vect.push_back( pvect.next_int() );
invlets.push_back( pvect.next_int() );
}
invlet_cache[member] = vect;
map[member] = invlets;
}
}
invlet_cache = { map };
} catch( const JsonError &jsonerr ) {
debugmsg( "bad invcache json:\n%s", jsonerr.c_str() );
}

0 comments on commit b1d8f4f

Please sign in to comment.