-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
updates on diagnostics (log + new diagnostics) #1085
Conversation
9256886
to
57332cc
Compare
/// State used by the [PrintDiagnosticsPlugin] | ||
pub struct PrintDiagnosticsState { | ||
/// State used by the [LogDiagnosticsPlugin] | ||
pub struct LogDiagnosticsState { |
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 is pub
but is not exported from the crate
Maybe use pub(crate)
?
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.
It has to be pub
because it's in a public interface:
crate-visible type `LogDiagnosticsState` in public interface
can't leak crate-visible type
It's public, just not user addressable...
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.
If we stop exporting the systems, then that error goes away. I'm not sure why we would be exporting the systems anyway?
It was created as public in 704a742, which was in May. I suspect it's just an oversight.
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.
pub
s are removed
if let Some(ref filter) = state.filter { | ||
for diagnostic in filter.iter().map(|id| diagnostics.get(*id).unwrap()) { | ||
Self::print_diagnostic(diagnostic); | ||
Self::log_diagnostic(diagnostic); | ||
} | ||
} else { | ||
for diagnostic in diagnostics.iter() { |
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.
Can these be sorted so that the diagnostics are in a consistent order?
It's not helpful every time you restart the app getting a different order.
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.
Currently you can have them ordered if you use a filter, as the filter is stored in a Vec
and then iterated over.
Without filter, the diagnostics are stored in a HashMap
and that doesn't keep order... We could switch to a BTreeMap
, or add a Vec
to keep order of insertion and use it to iterate... What do you think?
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.
So BTreeMap
would probably be a good start, although a Vec
sorted by name
or by insertion order could both also be a good choice.
Basically, anything deterministic would be better than the current situation.
Edit: Given that there are generally only a handful of diagnostic types, it might make sense to just use a Vec
to store them anyway and binary search to get a specific diagnostic.
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.
Moved to a BTreeMap
. After looking a little into a Vec
solution, I didn't like it as it would have mean every diagnostic recording a value would have to go through the Vec
...
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.
O(logn) is better than O(n), but for a large number of diagnostics idk if its a good idea to replace a hash map. While we currently only have a small number, I don't want to assume that will always be the case. Maybe LogDiagnosticsPlugin could store its own order? Sorting is a behavior inherent to the LogDiagnosticsPlugin so that makes sense to me.
The issue is keeping the order synced up / accounting for new diagnostics. Lol we could have a Diagnostic event. Ex: DiagnosticEvent::Added(DiagnosticId)
, DiagnosticEvent::Removed(DiagnosticId)
, etc
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.
I looked into adding an event, seems not easy as adding a diagnostics can happen at app build time by mutating a resource, so the new diagnostic plugin would be responsible from calling the event (I think)
Keeping the order in LogDiagnosticsPlugin without events seemed not pretty
I added a BTreeSet
of the DiagnosticId
to Diagnostics
that is updated when adding a diagnostics, and that is used for iterating orderedly
I have confirmed that the change does make the order consistent, which is definitely an improvement. I'm less sure about the default output format, just because there's an awful lot of noise. |
output of example log_diagnostics:
before, it was:
|
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.
Good calls all around!
|
||
pub fn diagnostic_system(mut diagnostics: ResMut<Diagnostics>, query: Query<Entity>) { | ||
let mut count = 0.; | ||
for _entity in &mut query.iter() { |
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.
we could replace this with let count = query.iter().count()
. Thats simpler, but also if/when we re-implement ExactSizeIterator for query it could make this O(1)
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.
Or alternatively we could make it O(1) now by adding a world.entity_count() function and making this system thread-local.
Your call!
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.
Tried adding it to World
, in my test with a system adding and removing entities every frame, the two counts are the same
if let Some(ref filter) = state.filter { | ||
for diagnostic in filter.iter().map(|id| diagnostics.get(*id).unwrap()) { | ||
Self::print_diagnostic(diagnostic); | ||
Self::log_diagnostic(diagnostic); | ||
} | ||
} else { | ||
for diagnostic in diagnostics.iter() { |
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.
O(logn) is better than O(n), but for a large number of diagnostics idk if its a good idea to replace a hash map. While we currently only have a small number, I don't want to assume that will always be the case. Maybe LogDiagnosticsPlugin could store its own order? Sorting is a behavior inherent to the LogDiagnosticsPlugin so that makes sense to me.
The issue is keeping the order synced up / accounting for new diagnostics. Lol we could have a Diagnostic event. Ex: DiagnosticEvent::Added(DiagnosticId)
, DiagnosticEvent::Removed(DiagnosticId)
, etc
I do think we might want to try cutting down on the line-noise of the logs a bit (maybe by printing the whole table as a single log). I also think we could probably maintain an up-to-date "entity count" number within Entities::flush to avoid the need to do atomic ops every time entity_count is needed (I had it in my head that this number already exists). But for now this all looks good. |
I found those two diagnostics useful while debugging game that slowed down over time, to track lost entities or extra assets (less true with new auto cleanup assets)