1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
|
OSPRay Style Guide
==================
This document discusses the project's expectations on C++ and ISPC
style. In general, the items discussed are meant to be a guide and not
be overly strict. However, deviations from this document should be
supported with technical reasoning.
This document is relatively simple and meant as a "quick overview" of
expectations for OSPRay. A more complete set of guidelines for all of C++
can be found in the [ISO C++ Core
Guidelines](https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md).
Documentation
-------------
In general, comments in code should describe "why" not "what". Code
documentation should be in terms of C++ style single-line comments or
multi-line C style comments. Avoid Doxygen-style code documentation,
this is deprecated.
When adding or updating public API definitions, update the corresponding
`doc/api.md` documentation as part of your merge request. This keeps our
documentation always current, and minimizes the need for audits at
release time. Markdown files (`.md`) should use a 72 column limit for
running text, exceptions are URLs or example code (i.e. preformatted
sections in backticks).
General Code Goals
------------------
In all code, the overall goal is to make interfaces "easy to use
correctly" and "difficult to use incorrectly". Some characteristics that
make code better are:
- Keep reasoning as local as possible
- Descriptive names
- Short functions
- Single functions/abstractions for repetitive concepts (less
copy/paste)
- Compile errors over runtime errors
We have a number of small, single-purpose abstractions in `rkcommon`
that aide in the above goals. It is expected for contributions to first
try and use those constructs before implementing their own.
Lastly, it is expected that code is formatted using the provided
`.clang-format` config found in the root of the repository, e.g. by
running `git-clang-format` (which also avoids changes which are only due
to formatting). Consider to pre-format very long strings using C++11 raw
string literals.
NOTE: Both C++ and ISPC files can be formatted with clang-format.
Naming conventions for public API
---------------------------------
- Prefer enums over string parameters.
- Parameter and object names are camelCase (e.g. `metallicPaint`)
- Parameter names are singular, even if it is an array (e.g. `index`,
`material`)
- Data belonging together like the members of an array of structs,
which are passed as struct of arrays, have a common prefix. For
example `vertex.color` and `vertex.normals`
- Switches (bools) name just what they switch, e.g. `shadows` (not
`shadowsEnabled` nor `enableShadows`, nor `useShadows`)
C++ Usage Guidelines
--------------------
C++ is a big language, so the following are things that we prefer to see
in OSPRay:
### Memory Management, Object Lifetimes, and Pointers
- For collections, prefer STL containers over C arrays, specifically
`std::vector` and `rkcommon::containers::AlignedVector`.
- Prefer smart pointers to manage memory over raw `new` and `delete`.
- Prefer the usage of `std::unique_ptr` to manage heap objects, until
memory lifetimes must be managed by more than one observer.
- Prefer constructing smart pointers with `rkcommon::make_unique<>`
and `std::make_shared<>` over using `new`.
- Avoid globals and `std::shared_ptr` that cross translation units.
- Prefer global scope variables to be marked `static`.
- Prefer functions to access global data in another translation unit
over marking them as `extern`.
- Keep raw pointer usage local, either in a function or in a single
cpp file.
- Prefer the usage of references over pointers for cases where
`nullptr` is not a valid value.
- Prefer using `rkcommon::utility::OnScopeExit` to robustly cleanup
non-trivial objects in a function.
- Avoid constructing `static` data that requires initialization
ordering with other `static` data.
### Designing Types (classes/structs)
- Define empty default constructors with `= default;`, not with an
empty `{}`.
- Use the `virtual` keyword only to mark that child classes are
expected to override, not that the function is overriding a parent
method.
- Always use the `override` keyword where appropriate.
- Prefer to write `const` functions by default.
- Prefer implementing inline member functions below the definition of
the class or struct. This makes classes easier to read by avoiding
unnecessary implementation details when reading the class interface.
- Implement `begin()` and `end()` iterators for types which abstract
collections.
- Consider whether the type should be move-only (ex:
`std::unique_ptr`) or copyable.
- Prefer `= delete;` syntax to delete constructors over marking them
`private`.
- Prefer giving all applicable member values inlined default values.
- Only populate values in constructor initialization if the value is
different than the default or is being set by a constructor
parameter.
- Only mark methods as `virtual` if the class is intended to be in an
inheritance hierarchy.
- Always implement a `virtual` destructor, even if it's `= default;`.
### Loops, STL Algorithms, and Threading
- Prefer the usage of an algorithm call over implementing a raw loop.
- Prefer making multi-STL algorithm functions their own function with
a name.
- Prefer using `rkcommon::tasking` constructs instead of raw threads.
- Prefer range-based for loops over indexing for loops where possible.
- Avoid shared mutable state in parallel code (requires
synchronization).
- Capture type preference for lambda functions should be `[]` → `[&]`
→ `[=]`.
### Templates
- Prefer to constrain template types to only relevant types using
`static_assert()` and type traits.
- Use `std::forward` when passing universal references (`T&&`) to
other templates.
- Only use SFINAE when necessary, and try to keep its usage as simple
as possible.
ISPC Usage
----------
The following are concerns specific to ISPC:
- Prefer using `uniform` variables. The point of ISPC is to have
varying data, but uniform values are more efficient than replicated
varying values.
- Avoid writing unnecessary `uniform` and `varying` keywords.
Examples:
``` {.cpp}
varying float f; // 'varying' is redundant, variables are varying by default
uniform float *p; // 'uniform' is redundant, pointers point to uniform by default
```
- Prefer omitting `uniform`/`varying` qualifiers for structs. Only
apply them if they need to be consistently the same for both
`uniform` and `varying` instances of the struct.
### OSPRay ISPCDevice Specific Design Choices
- Prefer to use Embree C API bindings over ISPC where possible.
- Prefer to throw exceptions when an object is found to be invalid
during `commit()`.
- Prefer setting ISPC-side values in a single `set()` call over many
single value setting functions.
- Minimize the number of alias names given to both object types and
parameters.
|