Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use colony for map and vehicle item storage #31406

Merged
merged 8 commits into from
Jun 20, 2019

Conversation

ifreund
Copy link
Contributor

@ifreund ifreund commented Jun 14, 2019

Summary

SUMMARY: Infrastructure "Use colony for map and vehicle item storage."

Purpose of change

As described in #31338, colony has several advantages over the current std::list, the most important being that iterators are not invalidated by erasure. This will make the item management code much more stable and has already cleaned up one nasty hack with processing activity items.

Describe the solution

  • Replace the std::list<item> stored in submaps and vehicle parts with a colony<item>.
  • Tweak the api ofitem_stack to reflect this change.
  • Make necessary changes to the rest of the code to reflect this
  • Delete item_stack::operator[] and refactor
  • Delete map and vehicle item access/removal functions that take an index and refactor
  • Comb through code to make sure iterators are used wherever possible
  • Rewrite invlet_test.cpp

Additional context

I've done my best to Include What I Use, but I've certainly missed some includes. Maybe I will try and get the IWYU tooling set up when this is done.

@ifreund ifreund added [C++] Changes (can be) made in C++. Previously named `Code` Items / Item Actions / Item Qualities Items and how they work and interact Code: Infrastructure / Style / Static Analysis Code internal infrastructure and style labels Jun 14, 2019
@ifreund ifreund changed the title Use colony for map and vehicle item storage [WIP] Use colony for map and vehicle item storage Jun 14, 2019
@ifreund ifreund force-pushed the colony-item-stack branch 2 times, most recently from ac90687 to bbff510 Compare June 15, 2019 09:18
@jbytheway
Copy link
Contributor

jbytheway commented Jun 15, 2019

colony has several advantages over the current std::list, the most important being that iterators are not invalidated by erasure

That's not true. list iterator invalidation is basically exactly the same as for colony. The advantages of colony are better memory layout and fast-ish random access.

But thanks for working on this :).

Copy link
Contributor

@jbytheway jbytheway left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking promising. This is indeed nice cleanup. Hope this wasn't too soon to do a preliminary review :).

Have you tackled/tested activity serialization yet? Didn't see anything related to that as I perused.

const item &get_uppermost_item() const {
return sm->itm[x][y].back();
return *std::prev( sm->itm[x][y].cend() );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine this get_uppermost_item is used e.g. for map rendering. Using the last item for that made sense when newly dropped items were always put to the back, but less so now. We probably want to eventually remove this and e.g. get the largest item on the square to render instead. But not in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah that's exactly what it's for.

And yeah that's for a future PR. I've also considered rewriting some of the inventory code to display alphabetically since storage on the map is no longer ordered.

src/savegame_json.cpp Outdated Show resolved Hide resolved
src/visitable.cpp Outdated Show resolved Hide resolved
src/visitable.cpp Outdated Show resolved Hide resolved
src/pickup.cpp Outdated Show resolved Hide resolved
src/map.h Outdated Show resolved Hide resolved
src/map.cpp Outdated Show resolved Hide resolved
src/json.h Outdated Show resolved Hide resolved
@ifreund
Copy link
Contributor Author

ifreund commented Jun 15, 2019

That's not true. list iterator invalidation is basically exactly the same as for colony. The advantages of colony are better memory layout and fast-ish random access.

Um, yeah may not have totally thought that one through. The meat of this PR is turning out to be not swapping in the colony but getting rid of all indexes to items on the map and vehicles.

@ifreund
Copy link
Contributor Author

ifreund commented Jun 15, 2019

Have you tackled/tested activity serialization yet? Didn't see anything related to that as I perused.

Haven't yet and it works fine for now since item_location serialization still works. I will be tweaking that some before this PR is finished to use the colony get_X_from_X functions.

@jbytheway
Copy link
Contributor

jbytheway commented Jun 15, 2019

Oh, another thing: if you can, it may be worth checking memory consumption impact of these changes. An item is quite big, so a colony<item> with only one item in it might use quite a lot of memory depending on how much space it allows for expansion. If very small stacks of items are common this might be an issue.


// We have a valid target, so preform the full finish function
// instead of just selecting the next valid target
act.index = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IS act.index a bool? It seems unlikely...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I see later that you are treating it as a bool. I can't decide whether it's clearer to assign 0 or false here. Hmm.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree that it's not super clean. I considered adding either a bool or an std::vector<bool> to player_activity but decided that was outside the scope of this PR

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have an aspiration to make accessing things inside player_activity cleaner in general, with some vague ideas of how to go about it. Looking at your code here makes me want to get to that, but I probably won't soon.

item_location &target = act->targets.back();

// Corpses can disappear (rezzing!), so check for that
if( !target || !target->is_corpse() ) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The operator bool on item_location is definitely doing the wrong thing in general. That's not really your fault, but this can totally end up pointing at the wrong item (I think there have been bug reports about butchery doing that already). I think the only way to fix this sensibly is with the weak_ptr idea Kevin has discussed. But that's orthogonal to your changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think what it's doing is actually OK here for now. It just searches at its stored location for an item matching its stored pointer, returning true if such an item is found.

I agree theweak_ptr plan is definitely a lot cleaner and more robust and may look at implementing that once this get wrapped up.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bear in mind that colony deliberately tries to reuse memory, so there is a good chance that there will be some different item at the same address if one is removed and another added, and it has no way to tell the difference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but I don't think there's anything we can do about it yet without the weak_ptr trick aside from hope it's not a corpse.

@ifreund
Copy link
Contributor Author

ifreund commented Jun 15, 2019

the invlet tests are currently broken, will look into a fix.

@ifreund ifreund force-pushed the colony-item-stack branch from e0520de to 460a8c2 Compare June 15, 2019 20:12
@ifreund
Copy link
Contributor Author

ifreund commented Jun 16, 2019

This PR is virtually complete now. The only remaining major task is a rewrite of invlet_test.cpp to account for the new indexless, unordered nature of items on the map/in vehicles.

@ifreund ifreund force-pushed the colony-item-stack branch from d276aa8 to d8f0cb1 Compare June 16, 2019 09:29
src/game.cpp Outdated Show resolved Hide resolved
const item &seed = farm_map.i_at( pos ).front();
if( farm_valid_seed( seed ) ) {
if( farm_map.furn( pos ) == f_plant_harvest ) {
// Can't use item_stack::only_item() since there might be fertilizer
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this is a concern? I don't think fertilizer actually gets added as an item on the tile; it just modifies the time to mature.

On the subject of unrelated bugs, I see this is checking specifically for f_plant_harvest, whereas it probably ought to be doing something more generic now, because there are other harvestable furnitures, I think. You don't have to fix that on this PR, though.

Copy link
Member

@anothersimulacrum anothersimulacrum Jun 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be checking for the GROWTH_HARVEST flag, but it looks as though this whole thing is largely a duplicate of iexamine::harvest, and is out of date with the current state of what iexamine::harvest does.

Which belongs in a separate PR

Copy link
Contributor Author

@ifreund ifreund Jun 16, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function led me to believe that fertilizer stays on the tile. It could also easily be outdated though.

Cataclysm-DDA/src/iexamine.cpp

Lines 2066 to 2082 in 4d34142

ret_val<bool> iexamine::can_fertilize( player &p, const tripoint &tile,
const itype_id &fertilizer )
{
if( !g->m.has_flag_furn( "PLANT", tile ) ) {
return ret_val<bool>::make_failure( _( "Tile isn't a plant" ) );
}
if( g->m.i_at( tile ).size() > 1 ) {
return ret_val<bool>::make_failure( _( "Tile is already fertilized" ) );
}
if( !p.has_charges( fertilizer, 1 ) ) {
return ret_val<bool>::make_failure(
_( "Tried to fertilize with %s, but player doesn't have any." ),
fertilizer.c_str() );
}
return ret_val<bool>::make_success();
}

And we can see it gets added here

g->m.spawn_item( tile, "fertilizer", 1, 1, calendar::turn );

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I did read iexamine::fertilize_plant but I missed that line.

src/gates.cpp Outdated Show resolved Hide resolved
src/item_stack.h Outdated Show resolved Hide resolved
@ifreund
Copy link
Contributor Author

ifreund commented Jun 17, 2019

Alright, I think this is ready to go.

The new invlet test uses a hack to enable item UUIDs, but It seems to actually be more robust than what we had previously as the old test was giving false positives after #30603.

Sorry that this is so large. If needed there are some parts that I could probably break off, but I wont have time to do so for a couple days.

@ifreund ifreund changed the title [WIP] Use colony for map and vehicle item storage Use colony for map and vehicle item storage Jun 17, 2019
tests/invlet_test.cpp Outdated Show resolved Hide resolved
tests/invlet_test.cpp Outdated Show resolved Hide resolved
@ifreund ifreund force-pushed the colony-item-stack branch from 41151cd to a4481bf Compare June 17, 2019 21:19
@ifreund
Copy link
Contributor Author

ifreund commented Jun 17, 2019

Added a comment explaining why the unique id hack was implemented the way it is using components and rebased to clean up all the commit messages before merge.

@ifreund
Copy link
Contributor Author

ifreund commented Jun 18, 2019

Some rough numbers for the performance gains of map::process_active_items() with this PR applied:

Low number of active items:
Pre colony: 5.5% of game::do_turn()
Post colony: 3.2% of game::do_turn()

Lots of active items:
Pre colony: 28.7% of game::do_turn()
Post colony: 18.0% of game::do_turn()

Profiling was done while waiting with active items strewn about the map

The next target for optimizing item processing is item::has_flag() which could be made much faster by replacing the current std::set<std::string> with our enum bitset introduced in #29368

@ifreund ifreund added the Code: Performance Performance boosting code (CPU, memory, etc.) label Jun 18, 2019
@ZhilkinSerg ZhilkinSerg self-assigned this Jun 18, 2019
@ifreund
Copy link
Contributor Author

ifreund commented Jun 18, 2019

Profiling results part 2, Memory usage:

A worst case scenario for colony is 1 item per tile as the minimum group size is 8.

I tested this case by spawning and killing 2600 moose, to create a one corpse per tile test case.

Screen Shot 2019-06-18 at 5 05 30 PM

Memory usage of submap::load()is a little over 8 times higher after applying this PR.
Pre colony: 845.42KB, 0.4% of total memory usage.
Post colony: 8.06MB, 3.5% of total memory usage.

Why is it not exactly 8 times higher?

I haven't looked into the gritty details, but colony has more memory overhead than just the stored types to increase performance. For example, there is a list to keep track of which elements have been erased so those memory locations can be reused for future insertion.

What can we do to mitigate this?

The simplest option is setting the starting group size to something less than 8 (minimum of 3). I haven't looked into how this would effect performance yet, but will do some investigation.

Another option would be to store one colony<item> per submap and a colony<item *> for every maptile. This would be more optimal for memory usage, but increases code complexity and may affect performance.

Do we need to do something about this?

I'm not sure, the memory usage isn't crazy and the current setup is probably better for performance than optimizing for lower memory usage. Up to @kevingranade though really. Regardless, I'd like to address that issue (if needed) in a follow up PR assuming that this is acceptable to merge as is.

@jbytheway
Copy link
Contributor

Thanks for testing that. Another option is to look into making the item objects smaller. The way they are currently structured is quite wasteful in most cases.

@ifreund
Copy link
Contributor Author

ifreund commented Jun 18, 2019

Conflicts resolved

@ZhilkinSerg
Copy link
Contributor

Conflicts resolved

You wish :)

ifreund added 8 commits June 19, 2019 11:32
colony has a better memory layout and faster random access than
the previous std::list while mantaining pointer/iterator
stability
The goal here is to remove all raw indexes to items on the map
or in vehicles as indexes to colonies are unstable while pointers
and iterators are stable.

In this commit ACT_MOVE_ITEMS, ACT_PICKUP, and ACT_WEAR were
refactored to use item_location with significant code cleanup.
Notably `Pickup::pick_up()` now uses item_stack iterators instead of
indexes since the previous hack proved to be too fragile.

Since items are now stored unordered on the map, the order of items
displayed in the pickup ui is now sorted using `item::operator<()`
Indexes to a colony are not stable through insertion/erasure
and should be avoided for everything except serialization
Indexes to colonies are unstable and everything has now been converted
to use iterators and pointers.

Also fixed a few minor style issues and typos.
"front" no longer makes sense since items in a colony are unordered.

The new function gives a debug message if there is not exactly one item
at the location.

Also fixed a few bugs caused by previous refactoring and caught in
code review
Rewrite the item management of the the invlet test to use an item
unique id hack to make it more robust and functional now that items on
the map are unordered.

Note: this test seems to have been giving false positives since CleverRaven#30603
was merged, since the AUTO_INV_ASSIGN option was not being properly
set.  This only became apparent after implementing the more robust
item management code with unique ids.

This also fixes a few remaing calls in the tests to a removed vehicle
function which still compiled due to implicit conversion of 0 to a pointer.
@ifreund ifreund force-pushed the colony-item-stack branch from ae9ae28 to 3922cca Compare June 19, 2019 09:33
@ifreund
Copy link
Contributor Author

ifreund commented Jun 19, 2019

You wish :)

Look again :)

@kevingranade kevingranade merged commit 85f057a into CleverRaven:master Jun 20, 2019
@ZhilkinSerg ZhilkinSerg removed their assignment Jun 20, 2019
@ifreund ifreund deleted the colony-item-stack branch June 20, 2019 10:02
ifreund added a commit to ifreund/Cataclysm-DDA that referenced this pull request Jun 26, 2019
After CleverRaven#31796 item processing invalidates item_locations, which
causes issues with all activities using item_locations.

This commit rewrites active_item_cache to store a safe_reference<item>
instead of an item_stack::iterator, removing the now unnecessary
active_item_set and has() functions.

This also fixes a bug with active item reording after processing introduced
by CleverRaven#31406 and a minor bug with the submaps_with_active_items cache.
ifreund added a commit to ifreund/Cataclysm-DDA that referenced this pull request Jun 26, 2019
After CleverRaven#31796 item processing invalidates item_locations, which
causes issues with all activities using item_locations.

This commit rewrites active_item_cache to store a safe_reference<item>
instead of an item_stack::iterator, removing the now unnecessary
active_item_set and has() functions.

This also fixes a bug with active item reordering after processing introduced
by CleverRaven#31406 and a minor bug with the submaps_with_active_items cache.
kevingranade pushed a commit that referenced this pull request Jun 27, 2019
* Rework active_item_cache to use safe references

After #31796 item processing invalidates item_locations, which
causes issues with all activities using item_locations.

This commit rewrites active_item_cache to store a safe_reference<item>
instead of an item_stack::iterator, removing the now unnecessary
active_item_set and has() functions.

This also fixes a bug with active item reordering after processing introduced
by #31406 and a minor bug with the submaps_with_active_items cache.

* Make changes from code review

In particular increment the iterator properly in active_item_cache::get()

* Remove redundant get() calls on safe references

clang-tidy told me what to do
}

case HARVEST_SAP: {
liquid_handler::handle_liquid_from_container( *container, PICKUP_RANGE );
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like *container can be a nullptr and there is no check for that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This option is only available if has_sap is true, and has_sap can only be true if a container was found.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not saying this code isn't fragile and prone to break at some point, but it is currently correct I believe.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
[C++] Changes (can be) made in C++. Previously named `Code` Code: Infrastructure / Style / Static Analysis Code internal infrastructure and style Code: Performance Performance boosting code (CPU, memory, etc.) Items / Item Actions / Item Qualities Items and how they work and interact
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants