Join GitHub today
GitHub is home to over 36 million developers working together to host and review code, manage projects, and build software together.
Sign upUpdate active items periodically instead of constantly. #10599
Conversation
kevingranade
added some commits
Dec 22, 2014
kevingranade
added
the
(S2 - Confirmed)
label
Dec 22, 2014
BevapDin
reviewed
Dec 22, 2014
| struct list_iterator_hash | ||
| { | ||
| size_t operator()(const std::list<item>::iterator &i) const { | ||
| return (size_t)&*i; |
This comment has been minimized.
This comment has been minimized.
BevapDin
Dec 22, 2014
Contributor
This might be problematic on invalid iterators (maybe only with debug iterators). I know that the logic here does not actually read/write the invalid item, so it might be fine, but have you considered using plain old pointers?
Also, I thing for safety, the map functions like add_item and i_rem etc. should just forward to the submap and not access submap::itm directly. That way one can ensure that every change of the item list is correctly
This comment has been minimized.
This comment has been minimized.
kevingranade
Dec 22, 2014
Author
Member
I'm not sure what you mean by use a pointer, my guess is you mean make active_item_set a std::unordered_set<item *> and insert into it with active_item_set.insert( &*it ).
While that would work, I'm not sure what the benefit would be. Let me know if I'm missing something.
By forwarding to the submap, I assume you mean adding setters/getters etc to the submap and making the actual container private? That's the kind of thing I'm generally working toward, but I'm running out of time right now so I don't want to embark on another set of API changes in this PR unless it's totally necessary.
This comment has been minimized.
This comment has been minimized.
BevapDin
Dec 22, 2014
Contributor
my guess is you mean make active_item_set a std::unordered_set<item *>
Yes.
While that would work, I'm not sure what the benefit would be
I'm pretty sure the standard forbids dereferencing invalid iterators, so the std iterators could have a check in the them which triggers an assertion or similar.
Judging from the latest crash report http://smf.cataclysmdda.com/index.php?topic=8998.0 especially the "attempt to copy-construct an iterator from a singular iterator" indicates this.
This comment has been minimized.
This comment has been minimized.
kevingranade
Dec 22, 2014
Author
Member
If we inserted an invalid pointer into the container, it might avoid the assert, but will lead to undefined behavior if the pointer is later dereferenced. We must store the iterator in active_items anyway, right?
This comment has been minimized.
This comment has been minimized.
BevapDin
Dec 23, 2014
Contributor
But the pointer isn't dereferenced. The pointer is stored, but before dereferencing it, has_active_item is called and looks for the item pointer (only pointer comparison) in the active item set, it does not find it and the processing of that item is skipped.
But there was a discussion on stackoverflow that reading a deleted pointer (not the target, just the pointer itself) invokes undefined behavior http://stackoverflow.com/questions/4990462
This comment has been minimized.
This comment has been minimized.
kevingranade
Dec 23, 2014
Author
Member
Yea, on balance I think it's better to just use the iterator and ensure that it is always removed from active_item_cache before we remove the item, pointer or iterator that's the only safe thing to do.
There might be something fancy we could do with linked smart pointers or something, but the requirement that we be able to move the object around to different containers with potentially different semantics (player/monster/NPC inventory, possibly things I'm not thinking of) makes me skeptical that we can beat schlepping around the iterators. If you have something I'm all ears though.
This comment has been minimized.
This comment has been minimized.
BevapDin
Dec 23, 2014
Contributor
You will end up with an invalid iterator anyway in map::process_items_in_submap, after the active_items (which contains iterators) is copied.
Any of the iterators in the copied set can become invalid when the matching item is removed from the map before it's actually processed. That's the point where has_active_item would return false. Invoking that function invokes a lookup in the active item set which invokes hashing of the iterator which invokes dereferencing it.
This comment has been minimized.
This comment has been minimized.
kevingranade
Dec 23, 2014
Author
Member
Ok, you are correct. The set needs to store raw pointers, item_reference needs a (hidden) raw pointer, and we use that to check validity of an item_reference before we dereference it in any way.
Thanks for showing me the error of my ways ;)
BevapDin
reviewed
Dec 22, 2014
| @@ -4371,7 +4358,7 @@ std::list<item>::iterator vehicle::remove_item( int part, std::list<item>::itera | |||
| std::list<item>& veh_items = parts[part].items; | |||
|
|
|||
| if( it->needs_processing() ) { | |||
This comment has been minimized.
This comment has been minimized.
BevapDin
Dec 22, 2014
Contributor
I believe this is the reason for the recent crashes. You usually remove an item when it has stopped being active. But in that case the item is not removed from the active item set, it stays there and is processed again in the next turn. Edit: Nope, it isn't.
This comment has been minimized.
This comment has been minimized.
kevingranade
Dec 22, 2014
Author
Member
Could be, makes more sense to check if the iterator is in the container rather than if it should be in the list. There might be somewhere flipping the active flag that I didn't notice.
kevingranade
added some commits
Dec 22, 2014
kevingranade
reviewed
Dec 23, 2014
| @@ -11465,7 +11464,8 @@ void map::rotate(int turns) | |||
| std::swap( cosmetics_rot[old_x][old_y], new_sm->cosmetics[new_lx][new_ly] ); | |||
| auto items = i_at(new_x, new_y); | |||
| itrot[old_x][old_y].reserve( items.size() ); | |||
| std::move( items.begin(), items.end(), std::back_inserter(itrot[old_x][old_y]) ); | |||
This comment has been minimized.
This comment has been minimized.
kevingranade
Dec 23, 2014
Author
Member
This move invalidated the iterators in the active item cache, then the i_clear would crash trying to remove them.
A copy followed by a i_clear() should preserve the expected behavior.
This comment has been minimized.
This comment has been minimized.
|
Anything else? |
This comment has been minimized.
This comment has been minimized.
|
Seems to fix the crashes and freezes I was experiencing. |
This comment has been minimized.
This comment has been minimized.
|
Would using std::stack and std::shared_ptr help with a stack infrastructure and invalid pointers? |
This comment has been minimized.
This comment has been minimized.
|
I'm not sure if that's going to make things any simpler, and would involve I think a sizeable overhaul of the map and vehicle code to wedge shared_ptr instances in all over the place instead of items simply living in lists. I'd love it if you could take a weak_ptr to an object owned by a container, but that's not supported, the owner has to be a shared_ptr. |
KA101
self-assigned this
Dec 23, 2014
This comment has been minimized.
This comment has been minimized.
|
Ah fair enough my man. I think auto_ptr makes life easy on ownership, so long as an instance of it lives. |
KA101
added a commit
that referenced
this pull request
Dec 23, 2014
KA101
merged commit 0289312
into
CleverRaven:master
Dec 23, 2014
1 check passed
kevingranade
removed
the
(S2 - Confirmed)
label
Dec 23, 2014
narc0tiq
referenced this pull request
Dec 24, 2014
Closed
SIGABRT after set fire in the basement. #10629
BevapDin
reviewed
Dec 25, 2014
|
|
||
| // get() only returns the first size() / processing_speed() elements of each list, rounded up. | ||
| // It relies on the processing logic to remove and reinsert the items to they | ||
| // move to the back of their respective lists (or to new lists). |
This comment has been minimized.
This comment has been minimized.
BevapDin
Dec 25, 2014
Contributor
Now that I read this, that behavior will mess up the index of the items on the field. The zombie corpse (active) is at index 0, but once it's processed, it will be at the end of the item list, having the wrong index.
This comment has been minimized.
This comment has been minimized.
macrosblackd
Dec 25, 2014
Contributor
That might be what causes tiles with active items to 'rotate' the topmost item.
This comment has been minimized.
This comment has been minimized.
KA101
Dec 26, 2014
Contributor
I still have a revert button on this. Might pull the plug and see if that changes anything.
This comment has been minimized.
This comment has been minimized.
|
Perhaps this is that segfault bug some of us constantly get? |
This comment has been minimized.
This comment has been minimized.
|
Are the crashes happening while you're crafting or doing other long actions |
This comment has been minimized.
This comment has been minimized.
|
Crashes are when active items (re)enter the reality bubble. We've had active foodstuffs disappear when losing (fresh), etc tags too. |
This comment has been minimized.
This comment has been minimized.
|
Hmm, the first is probably a problem with the deserialization code, the |
narc0tiq
referenced this pull request
Jan 1, 2015
Merged
Remove optional maxitems from map::add_item{,_at} #10700
kevingranade
assigned
narc0tiq
and unassigned
KA101
Jan 1, 2015
narc0tiq
assigned
KA101
and unassigned
narc0tiq
Jan 1, 2015
This comment has been minimized.
This comment has been minimized.
|
Ah, @kevingranade, that 'bot you have doesn't understand the difference between mentioning a PR or issue and posting a fix for an issue to make it auto-close. |
KA101
removed their assignment
Jan 1, 2015
This comment has been minimized.
This comment has been minimized.
|
And you were working on it, Narc, which is why Kevin put you on the unit. Let's not mistake the merger for a code-author. |
This comment has been minimized.
This comment has been minimized.
|
Sort of. This isn't an issue, so it's not getting auto-closed -- and in #10700 I was only referencing it as being a related PR (for background info). Pretty sure it doesn't make sense to assign me this PR, where assignees for PRs tend to be used as a "I'm about to merge this" flag. |
kevingranade commentedDec 22, 2014
The short version is this change lets the game spend up to 600 turns (1 hour) between rot checks for food, and up to 100 turns (10 minutes) between raise checks for zombies and similar.
There's also a fix in there for a bug where every comestible was being set to active, this pretty much wrecked my attempts to make this work properly, because all food everywhere was being added to the lists for processing, and worse, many of them were being added to the default "check every turn" list.
The longer version is that I split the active item list into a potentially arbitrary number of sub-lists, each of which guarantees only that the items on it will be processed at least once every n turns. In reality we're only using three at the moment (with ns of 1, 100 and 600)
It does this by building a list of item references from its lists that contains the first list.size() / n items rounded up and returning that list for processing. When being processed, the items are removed from the lists and then pushed onto the back of the list, so after processing the items that were at the start of each list are now at the end of each list.
Another optimization is item_stack::push_back_fast(), if we know an item can and will fit in a square, such as if we just removed it while processing it, we can skip the checks imposed by map::add_item_or_charges and go straight to map::add_item. The check for inserting items into vehicles isn't as expensive, so I didn't bother with it for now.
Fixes #9926 and should fix #10269