-
Notifications
You must be signed in to change notification settings - Fork 34
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
Planeta again #293
Planeta again #293
Conversation
Conflicts: src/planet-a/chunk_generate.cpp
std::uint16_t m_edgeVrtxCount{0}; | ||
|
||
std::uint16_t m_fillVrtxCount{}; | ||
std::uint16_t m_edgeVrtxCount{}; |
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.
Empty braces like this will zero-initialize (initialize to zero) to prevent an uninitialized value.
I don't have this in guidelines.md, but I'd intend that empty braces means "don't care what this value is, but don't keep it uninitialized," whereas {0}
would imply that the zero is important.
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.
Kind of a philosophical question here, but is there any actual difference between "Don't care what the value is, but don't leave it uninitialized" and "don't initialize" ?
Using empty-brace initialization for variables where you don't care what the resulting value is means that automated tools like clang-tidy / msvc-analyzer can't identify "read-from-uninitialized" bugs in the code, which i think is a big risk.
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.
Note that I have no problem with the variables here being initialized to zero if that's what's intended. But "initialize them, but i don't care to what" is, in my opinion, worse than 'don't initialize'.
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.
And, of course, this whole concept only applies to primative types. Class types / types with constructors implicitly manage their initial state.
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.
Kind of a philosophical question here, but is there any actual difference between "Don't care what the value is, but don't leave it uninitialized" and "don't initialize" ?
If I look at code that looks like this:
int value;
do_something(value);
value
is uninitialized and there may be weird inconsistent errors when it's used wrong. static analysis may also scream at it. However, the intention is clear that the initial value of value
is dont-care.
If I instead do...
int value = 0;
do_something(value);
there's no risk of using uninitialized values, but the zero looks significant, as if it was a start of a count for example.
Instead using...
int value{};
do_something(value);
value
would be zero-initialized, there's no risk of any weird inconsistent errors. The intention is default or don't-care, nothing is explicitly specified.
Using empty-brace initialization for variables where you don't care what the resulting value is means that automated tools like clang-tidy / msvc-analyzer can't identify "read-from-uninitialized" bugs in the code, which i think is a big risk.
Are you implying that uninitialized values can help find logic errors?
I believe we should avoid uninitialized values whenever possible. Using uninitialized memory as part of regular calculation logic something I'd mostly consider a bad idea nowadays.
PREfast also tends to scream at every uninitialized value.
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 don't remember where I originally learned this from. I did find this recent similar discussion through google search results: https://www.reddit.com/r/cpp/comments/1dm2y2t/is_empty_brace_initialization_considered_best/
Since I'm noticing I'm throwing around the term "don't care" quite a bit, I would note that this is a term from digital logic; I'm not sure if it's used in other parts of computing.
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.
Are you implying that uninitialized values can help find logic errors?
Not quite.
I'm saying that from the perspective of a static analyzer
int bob;
and
int alice{};
are very different looking.
If the programmer doesn't care about the value, then the programmer doesn't want the value to be read from until something is put there. A static analyzer (e.g. PREfast) can detect this kind of problem with bob
, but alice
looks initialized.
My advice is that empty brace initialization for primitive types is a bad idea exactly because of this, and explicitly setting a value is better.
I'd be interested to see an example where PREfast was saying "read from uninitialized!" when that wasn't happening. I suppose it could happen with non-const reference args to functions in other translation units, but that seems like a bug in PREfast that it isn't going and looking at the other translation unit.
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.
Are you implying that uninitialized values can help find logic errors?
Not quite.
The further explanation makes this a yes? In your explanation, bob
is kept uninitialized to use it to help find a potential logic error.
So from your point of view, would this example also be unacceptable?
int value = 0; // This can be anything, will be overwritten immediately
do_something(value);
Since this is more about using uninitialized memory (less about zero-initialization), we're not the first to have this discussion:
I'm mostly on the side of just disallowing uninitialized memory. Undefined behaviour that does stuff like being different between debug vs release is far harder to debug IMO, compared to accidentally reading a zero.
Clang-tidy does have an option for this: cppcoreguidelines-init-variables
I'd be interested to see an example where PREfast was saying "read from uninitialized!" when that wasn't happening....
This alert is from this PR:
https://github.com/TheOpenSpaceProgram/osp-magnum/security/code-scanning/333
It doesn't even care about whether the variable is being read or not. It just doesn't like uninitialized members.
@@ -36,34 +38,52 @@ namespace osp | |||
using Corrade::Containers::ArrayView; | |||
using Corrade::Containers::arrayView; | |||
|
|||
/** | |||
* @brief Wraps a Corrade ArrayView or StridedArrayView to use as a 2D array of equally sized rows | |||
*/ | |||
template<typename T> | |||
struct ArrayView2DWrapper |
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.
Since this class is only supposed to be used with ArrayView or StridedArrayView, you might want to consider adding template specializations for only those two, and leaving the "default" template declaration of the struct undefined.
The lazy way of just leaving it like this is also fine, of course.
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 a requires clause.
either way.
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 guess you mean a concept for array views?
I'll be lazy on this as there's already a short comment explaining what this type is, and it's a little too simple.
constexpr bool is_not_used() const noexcept { return stride == 0; } | ||
|
||
std::size_t offset{}; | ||
std::ptrdiff_t stride{}; |
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.
empty-brace-init instead of = 0 ?
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 think I mentioned this in another review comment: my intention for an empty brace is "don't care what this value is, just not uninitialized." A zero explicitly indicates that the value is important to some business logic somewhere.
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.
oh, it's this one: https://github.com/TheOpenSpaceProgram/osp-magnum/pull/293/files#r1642043900
*/ | ||
constexpr bool is_distance_near(Vector3l const a, Vector3l const b, std::uint64_t const threshold) noexcept | ||
constexpr bool is_distance_near(Vector3l const a, Vector3l const b, double const threshold) noexcept |
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.
why switch to double?
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.
The previous implementation here just doesn't work for the Earth-sized distances. I'd rather keep it simple and not write something ultra-optimized here.
src/osp/core/math_int64.h
Outdated
std::uint64_t const dz = absdelta(a.z(), b.z()); | ||
double const dx( absdelta(a.x(), b.x()) ); | ||
double const dy( absdelta(a.y(), b.y()) ); | ||
double const dz( absdelta(a.z(), b.z()) ); |
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.
Personally i don't find switching from assignment initialization to braced initialization easier to read.
Are you doing this to force an error if the type of the result of absdelta is not the same as the type of the variable?
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 agree. Not particularly a sane move to do this on my end. Curly braces will enforce the type; however, these are those error-prone round braces that have a silly chance of being interpreted as a function declaration. I'm intentionally implicit-casting int64 from absdelta to a double.
std::uint64_t const magnitudeSqr = (dx*dx + dy*dy + dz*dz); | ||
|
||
return magnitudeSqr < threshold*threshold; | ||
return (dx*dx + dy*dy + dz*dz) < threshold*threshold; |
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.
You could check and see if the codegen is better or worse when using https://en.cppreference.com/w/cpp/numeric/math/hypot here.
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 could def see an argument that using the sum of squares is faster than doing a square root, so i have no concerns about this code. Just pointing out that there's a special function for a different way to slice this.
src/planet-a/geometry.h
Outdated
std::vector<osp::Vector3> chunkVbufPos; | ||
std::vector<osp::Vector3> chunkVbufNrm; | ||
std::vector<osp::Vector3u> chunkIbuf; | ||
Corrade::Containers::Array<unsigned char> vrtxBuffer; ///< Output vertex buffer |
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.
unsigned char
, specifically?
not std::byte
, or just char
?
{ | ||
1.05146222e+0f, 5.46533058e-1f, 2.75904484e-1f, 1.38283174e-1f, 6.91829904e-2f, | ||
3.45966718e-2f, 1.72989830e-2f, 8.64957239e-3f, 4.32479631e-3f, 2.16239942e-3f | ||
1.05146222e+0f, 5.46533058e-1f, 2.75904484e-1f, 1.38283174e-1f, |
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.
could you document where the initial values of this are from? Is it icosahedron_tables.py
?
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.
There's already comment; it's just not visible on the diff.
4.13372560e-2f, 2.06730441e-2f, 1.03370743e-2f, 5.16860619e-3f, 2.58431173e-3f | ||
}; | ||
inline constexpr std::array<float, 24> const gc_icoMaxEdgeVsLevel | ||
{{ |
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.
double brace init here? that seems like maybe a typo?
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 don't remember the exact details, but this is required for some of the other tables in this file for arrays of structs. This is harmless afaik
Major terrain-related changes:
terrain
scenario, and tiny planet forterrain_small
scenarioNon-terrain-related changes:
src/osp/core/buffer_format.h
, based on and improved from the "StrideDesc" nonsense fromsrc/osp/universe/universe.h