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

Stacks of items on the ground render either the last-dropped favorite item or the largest item. #72153

Closed
wants to merge 3 commits into from

Conversation

noulolhaha
Copy link

Summary

Interface "Instead of rendering the last-dropped item on the ground, it renders the last-dropped favorite items on the ground, or the biggest item if no such favorite item."

Purpose of change

Addresses #71637

Once, I lost my backpack in a lab and spent like an hour trying to find it because after dropping my backpack on a tile, I noticed I also had some broken glass in my pockets and decided to drop that too. Because the game rendered the glass instead of the backpack. it was a huge pain to find it.

In general, it makes much more sense for bigger items to display in favor of smaller ones, and for favorite items to display in favor of non-favorite ones, and will prevent stuff like bullet casings covering up actually important items.

Describe the solution

In the code that renders dropped items, I replaced the calls to submap::get_uppermost_item() with a call to a new function, submap::get_most_visible_item(), which iterates through the items on that tile and returns the first (and therefore last dropped) favorite item or, failing that, the biggest item.

Describe alternatives you've considered

Marking the item as favorite and enabling creation of notes when a favorite item is dropped helps, but it doesn't give its exact position as well, and it doesn't change the fact that it's really weird/potentially deceptive for the last dropped item to render, even when you drop something tiny on something big.

Testing

I walked around in a new game picking up and dropping various items in various orders, and strolling through the city. Worked as expected, and I did not notice any performance issues, even with thousands of items on the same tile.
I also ran all tests locally and saw no related issues.

Additional context

If this leads to performance issues with item rendering (given the O(n items on tile) time complexity), it would be pretty simple to just cache the most visible item on each tile, but I can't imagine this being an issue unless there are literally >1,000,000 items in view, in which case there are most likely already other performance issues.

@github-actions github-actions bot added [C++] Changes (can be) made in C++. Previously named `Code` Info / User Interface Game - player communication, menus, etc. new contributor labels Mar 4, 2024
@@ -472,7 +472,7 @@ void tileset_cache::loader::create_textures_from_tile_atlas( const SDL_Surface_P
for( tiles_pixel_color_entry &entry : tile_values_data ) {
std::vector<texture> *tile_values = std::get<0>( entry );
color_pixel_function_pointer color_pixel_function = get_color_pixel_function( std::get<1>
( entry ) );
( entry ) );
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
( entry ) );
( entry ) );

Copy link
Contributor

Choose a reason for hiding this comment

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

Note: You can accept all the bot is suggesting here in a single commit, (then squish that commit into yours locally, if you want, then force push).

Comment on lines +937 to +939
std::remove_if(
v.obj.begin(),
v.obj.end(),
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
std::remove_if(
v.obj.begin(),
v.obj.end(),
std::remove_if(
v.obj.begin(),
v.obj.end(),

[&]( int id ) {
return id >= offset || id < 0;
} ),
v.obj.end()
);
);
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
);
);

Comment on lines +948 to +950
std::remove_if(
vs.begin(),
vs.end(),
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
std::remove_if(
vs.begin(),
vs.end(),
std::remove_if(
vs.begin(),
vs.end(),

[&]( const weighted_object<int, std::vector<int>> &o ) {
return o.obj.empty();
}
),
),
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
),
),

@@ -3579,7 +3578,7 @@
auto field_at = [&]( const tripoint & q, const bool invis ) -> field_type_id {
const auto it = field_override.find( q );
return it != field_override.end() ? it->second :
( !fld_overridden || !invis ) ? here.field_at( q ).displayed_field_type() : fd_null;
( !fld_overridden || !invis ) ? here.field_at( q ).displayed_field_type() : fd_null;
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
( !fld_overridden || !invis ) ? here.field_at( q ).displayed_field_type() : fd_null;
( !fld_overridden || !invis ) ? here.field_at( q ).displayed_field_type() : fd_null;

Comment on lines +4721 to +4722
player_to_screen( iD + point( direction_offset, 0 ) ),
formatted_text( sText, FG, direction ) );
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
player_to_screen( iD + point( direction_offset, 0 ) ),
formatted_text( sText, FG, direction ) );
player_to_screen( iD + point( direction_offset, 0 ) ),
formatted_text( sText, FG, direction ) );

@@ -4818,7 +4819,7 @@
const auto ter = [&]( const tripoint & q, const bool invis ) -> ter_id {
const auto override = ter_override.find( q );
return override != ter_override.end() ? override->second :
( !overridden || !invis ) ? here.ter( q ) : t_null;
( !overridden || !invis ) ? here.ter( q ) : t_null;
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
( !overridden || !invis ) ? here.ter( q ) : t_null;
( !overridden || !invis ) ? here.ter( q ) : t_null;

@@ -5238,7 +5239,7 @@

std::vector<map_extra_id> map_extra_ids = MapExtras::get_all_function_names();
map_extra_ids.erase(
std::remove_if( map_extra_ids.begin(), map_extra_ids.end(),
std::remove_if( map_extra_ids.begin(), map_extra_ids.end(),
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
std::remove_if( map_extra_ids.begin(), map_extra_ids.end(),
std::remove_if( map_extra_ids.begin(), map_extra_ids.end(),

*
* Assumes there is at least one item.
* Used to decide which item to render.
* TODO: might be slow enough to justify storing the most visible item for each tile?
Copy link
Contributor

Choose a reason for hiding this comment

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

[JSON & C++ formatters] reported by reviewdog 🐶

Suggested change
* TODO: might be slow enough to justify storing the most visible item for each tile?
* TODO: might be slow enough to justify storing the most visible item for each tile?

*
* Assumes there is at least one item.
* Used to decide which item to render.
* TODO: might be slow enough to justify storing the most visible item for each tile?
Copy link
Contributor

@Brambor Brambor Mar 4, 2024

Choose a reason for hiding this comment

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

You can (and should!) check this yourself.
Spawn a ton of items and see if the game is still playable before and after your changes.

Copy link
Author

Choose a reason for hiding this comment

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

I actually did test it ingame, spawned a bunch of items, I mentioned that in the PR but didn't update the comment in the code, mb

Copy link
Contributor

@Brambor Brambor left a comment

Choose a reason for hiding this comment

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

I am not sure if this is viable, but if it is, the merger will want these to be fixed.

units::volume biggest_volume = 0_ml;

// uppermost item is at end of the list, so iterate backwards so uppermost item is preferred
auto items = get_items();
Copy link
Contributor

Choose a reason for hiding this comment

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

Avoid using auto

auto items = get_items();
// unsigned int i = get_items().size();
for( auto it = items.rbegin(); it != items.rend(); it++ ) {
const auto &item = *it;
Copy link
Contributor

Choose a reason for hiding this comment

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

Avoid using auto

Copy link
Contributor

Choose a reason for hiding this comment

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

It's generally acceptable for more complex types such as iterators, though. So only the one inside the loop needs to be changed, in case that wasn't clear.

@@ -418,6 +418,35 @@ class maptile_impl
return *std::prev( sm->get_items( pos() ).cend() );
}

/** Returns the uppermost favorite item if there is a favorite item. Otherwise, returns the item with the greatest volume.
*
* Assumes there is at least one item.
Copy link
Contributor

Choose a reason for hiding this comment

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

You should check it in the code instead of silently returning nullptr. Throw some cata error (not sure how from the top of my head).

Copy link
Author

Choose a reason for hiding this comment

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

I put that there because get_uppermost_item() made that assumption, so I figured I could too. I'll change it though.

* Used to decide which item to render.
* TODO: might be slow enough to justify storing the most visible item for each tile?
*/
const item &get_most_visible_item() const {
Copy link
Contributor

@Brambor Brambor Mar 4, 2024

Choose a reason for hiding this comment

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

The implementation of this size should be in the .cpp file, not the .h file.

Btw this will improve the compilation time (when changing only the implementation) enormously, as you only recompile one file.

Copy link
Author

Choose a reason for hiding this comment

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

I realized that - I was again just basing it off what get_uppermost_item() did, and assumed that there was some reason that other functions in the same file were implemented in the header. I'll fix that.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's based on simplicity of the function. One liners can be in .h files. But anything in .h means drastically more recompile time on change.

Copy link
Author

Choose a reason for hiding this comment

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

oh wait, actually it's all in a header because it's a method of a template class.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh, you might be right. I don't know enough about template stuff splitting, Feel free to mark this as resolved.

@Brambor
Copy link
Contributor

Brambor commented Mar 4, 2024

I like the idea of showing a favourite item if it can be done fast. I believe this is not fast. But you can prove me wrong by testing it.

I don't care for showing the largest item.

@SurFlurer
Copy link
Contributor

Would you like to test its performance impact?

@github-actions github-actions bot added json-styled JSON lint passed, label assigned by github actions BasicBuildPassed This PR builds correctly, label assigned by github actions labels Mar 4, 2024
@SurFlurer
Copy link
Contributor

SurFlurer commented Mar 4, 2024

I got an error when playing with your changes.

Steps to reproduce:

  1. Run CDDA with msvc debugger attached to it.
  2. Load a world, wield something then start to craft something.
  3. When the game asks you what to do with the in-progress item, select "Put it down and start working".
  4. The error pops up.

Specifically, itm at

if( itm.has_itype_variant() ) {
is null.

The same happens when I try to debug spawn 10000 plastic chunks.

@SurFlurer
Copy link
Contributor

SurFlurer commented Mar 4, 2024

Once, I lost my backpack in a lab and spent like an hour trying to find it because after dropping my backpack on a tile, I noticed I also had some broken glass in my pockets and decided to drop that too. Because the game rendered the glass instead of the backpack. it was a huge pain to find it.

In case you are not aware, you can press "V" to list all items around the player.

image

And it's quite easy to locate whatever you're looking for, especially if you add the item to your favorite. Either scroll down the list or press "/" to search to filter the list.

@noulolhaha
Copy link
Author

I did not mean to push just now, I am still making changes. Now I know what that button does.

@noulolhaha
Copy link
Author

Alright, I fixed those crashes. After doing some profiling, I've found that it does make the game run slightly slower, which isn't great, so I'm making submaps store the most visible item on each tile so it only needs to be recalculated when an item is added (just compare new item to current most visible, O(1)) or when the most visible item is removed (O(N), but this isn't going to happen very often and worst case it's iterating through 4096 items in contiguous memory).

@noulolhaha
Copy link
Author

On further examination, that might not actually be possible since apparently there's a method used throughout the code that just gives people a read-write reference to the items, meaning there's no way to know when an item is added/removed. I'll see if I can find a clever workaround for performance, though.

If I can't, I'll just make it ignore volume when deciding what to render, since calling base_volume() was the main slowdown in get_most_visible_item(), and make it just prioritize favorite items.

@noulolhaha
Copy link
Author

After more performance testing with ~300,000 dropped items on the ground, I found that this just won't work performance wise, even if it ignores volume and just tries to prioritize favorite items. Given that it's just a small QOL feature it's definitely not worth the performance hit, so I'm gonna close this. Sad.

@noulolhaha noulolhaha closed this Mar 8, 2024
@Inglonias
Copy link
Contributor

Better luck next time. Sorry things didn't work out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
BasicBuildPassed This PR builds correctly, label assigned by github actions [C++] Changes (can be) made in C++. Previously named `Code` Info / User Interface Game - player communication, menus, etc. json-styled JSON lint passed, label assigned by github actions new contributor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants