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
Better handling of item templates #19583
Conversation
I am still uncomfortable about returning long-lived pointers into the internal storage of an (unordered_)map. Can we maybe put in a "freeze" step which is called when the static templates are fully populated and (a) refuse to hand out pointers before the freeze step is done Alternatively just have both maps use unique_ptr (you can still hand out raw pointers; the change is that adding to the map won't invalidate the raw pointer if the object is being managed by a unique_ptr) |
That conditional could easily be implemented in
This comes with a relatively significant performance overhead which is important since anything that converts an The performance overhead probably has more to do with cache coherency than the actual indirection so perhaps the above conditional will be fast enough. It's tempting to suggest an assertion instead that gets excluded in the release build - this should catch any nasty bugs during development. Overall we don't do error handling well. I'm not sure how everyone feels about exceptions but we should certainly make more use of assertions as opposed to |
Sounds like there is scope for storing the result of item::find_type() in callers (e.g. vehicle parts seem like an obvious win). It'd be useful to look at the dynamic call graph for find_type, I'm sure it'll be 2-3 callers that account for almost all of the cost. |
Yes, we already do this optimisation for I'm leaning towards a conceptually simple |
Yeah, that assert would be good (plus enforcing no-modifications-after-finalize, which shouldn't be an issue to always do) Really need to look at the profile to work out where any caching would help. While there is some lowhanging fruit here (unordered_map et al) any further gains are going to be about doing less work - call find_type() less often. |
There is also the (unproven) possibility that keeping all the |
|
It is exactly things with access to m_templates that need to enforce the no-modification rule. For example find_template() will currently modify m_templates if you ask for an undefined ID. This is Bad and will cause hard-to-find bugs. Either that needs to assert if it happens post-freeze, or you need to put the placeholder type in the dynamic map not m_templates (and m_templates can probably lose the mutable in that case..) The other enforcement point I see is load_basic_info which should never be called post-freeze anyway so let's enforce that obviously rather than having a subtle bug if it ever happens. |
I'm going to add the suggested |
Note this isn't a buildbot failure - it just updates the Makefile so that |
Final commit changes missing items to be created as runtime not static types. I think we should probably better handle missing items overall (refuse to create them) but that can come in a separate PR. No further changes |
I think the NDEBUG change needs to be a separate thing. |
No. it needs to go in this once or I'm dropping the |
also, can you fix your system clock / timezone? Having commits appear in the future is pretty confusing when looking at the issue in sequence. |
Setting NDEBUG turns off asserts everywhere. Have you checked that none of them anywhere have side-effects? That would be a bug, but it is also why I object to setting NDEBUG without care. |
Yes, after this merge run |
There aren't many in the project so I'm going to check for side effects now
|
No asserts in release isn't a good idea. We need feedback from users, not just dedicated testers. |
No side effects:
This is good because once we have |
Also, looking at the assert, it tests a boolean field. Are you seriously suggesting that testing a boolean field is sufficiently expensive that you'd rather risk unpredictable bugs? This has already broken nastily once, can we please keep the safety net here? |
That is a naive assertion (heh) if you're just basing it on a grep.
|
We should use Our current error handling is awful. We don't fail on bad JSON we just keep going. Someone even wrote a PR that swallows thrown exceptions during the process (which also eats the file and line number). The nett effect is that you get a dozen unattributed load errors followed by a segfault.
We do need an |
No I'm suggesting that if they aren't compiled out they are defacto |
Or put more eloquently (via stack overflow) "If you're even thinking of leaving assertions on in production, you're probably thinking about them wrong. The whole point of assertions is that you can turn them off in production, because they are not a part of your solution. They are a development tool, used to verify that your assumptions are correct. But the time you go into production, you should already have confidence in your assumptions" They are effectively comments that don't become outdated and make development easier by turning stopping a nullptr deference at source as opposed to allowing it to occur half a dozen function calls later. If any calls should be enabled in production those should be |
In this particular case:
If you can't (or don't want to) enforce the stability guarantee via freezing the map then I think we need to go back to unique_ptr which does provide that guarantee. If you think that assert() is the wrong thing to use to enforce the guarantee then that's fine, but I don't know if there's a simple way to get the same facilities (popup window so the user knows what's going on before you call abort()). Note that debugmsg is no good here because we cannot safely continue if a lookup is done on a nonfrozen map. |
I would be fine with an But I think that is all discussion that better belongs in a separate PR, as I originally suggested. It should not prevent this PR getting merged; drop the NDEBUG change and argue it out separately. |
I've rebased this to everything prior to |
|
||
/** Find all templates matching the UnaryPredicate function */ | ||
std::vector<const itype *> Item_factory::find( const std::function<bool( const itype & )> &func ) { | ||
assert( item_controller->frozen ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This needs to move up into Item_factory::all()
Snap. Was 3/4 of the way through the same, but let's go with yours. I found one assert that needed to be moved (see above). Also a couple of places that could use map::emplace but those aren't a big deal. |
|
||
return &( m_templates[ id ] = def ); | ||
m_runtimes[ id ].reset( def ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use emplace
@@ -209,7 +208,7 @@ class Item_factory | |||
* @param new_type The new item type, must not be null. | |||
*/ | |||
void add_item_type( const itype &def ) { | |||
m_templates[ def.id ] = def; | |||
m_runtimes[ def.id ].reset( new itype( def ) ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could also use emplace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How so?
I've pushed that change to EDIT: Github really should add confirmation to 'Close and comment' |
Merge as per above review |
Yeah, not too worried about the emplace, it just makes it a little clearer that you are inserting a new entry and passing ownership to a unique_ptr. there's no real difference in functionality.
|
Rebuilding with symbols now. |
Not sure how that escaped, I'm going to compile also |
|
Note also #19240 but I'm not sure if it's relevant |
The comments in |
Can probably skip initializing the ruleset after loading global rules? They're meaningless until the world data is loaded at a later point anyway. I guess it only works because the rules get regenerated when the character-specific rules get loaded. |
I was looking at moving that call elsewhere - the code comment say should only include static data but that code attempts a JSON load? |
Probably in |
comment at the very top of
So it seems OK to load the global autopickup config since it's just text and doesn't directly depend on any sort of moddable data, but it doesn't make sense to actually match them up to items (in Delaying the loading of global rules entirely is trickier since you can edit the global rules from the settings menu with no world loaded at all. |
Yes but see also the comments below that
|
Continued in #19618 |
Fixes #19566
Testing shows that removing pointer indirection from #19058 provides a significant performance benefit (16386ms to 12824ms) for the unit test described in #19566. This PR separates the fixed and runtime templates as they have differing storage requirements so that you only pay for what you need. The changes are relatively unobtrusive (59cbb24)
A considerable performance increase (12824ms to 1684ms) is provided by a change from
std::map
tostd::unordered_map
in 3d886fb