Coding style
Coding style and best practices to preserve maintainability and consistent style across whole project.
Please note that if you have a good excuse to either break the rules or modify them, feel free to do it (and update this guide accordingly, if appropriate). Nothing is worse than rule that hurts productivity instead of improving it.
This guide builds upon Corrade's coding style guide and extends it where needed. You are encouraged to read it first.
C++ code
Headers
Headers shouldn't have using
declarations inside them (unless there is good excuse, see Magnum.h).
Headers have *.h
extension, template implementation headers have *.hpp
extension (hinting that they are something between *.h
and *.cpp
files).
Code format
Builtin types
Use Magnum's own type aliases for public API (e.g. UnsignedInt, see Type system for more information), but use specific types when interacting with third party libraries and OpenGL (e.g. GLuint
) and rely only on implicit conversions when converting between them. This helps avoiding sign, truncation and other issues, e.g. Math::Vector2<GLsizei>
will implicitly convert to Vector2i if and only if Int is the same type as GLsizei
.
Naming
When writing wrappers for OpenGL functions and defines, try to match the original name as closely as possible, although expanding abbrevations (and removing redundant prefixes) is encouraged.
Forward declarations and forward declaration headers
When a namespace has classes which are commonly forward-declared, consider making a forward declaration header — it should have the same name as the namespace itself and contain foward declarations for all classes, enums and copies of all meaningful typedefs. See Forward declarations instead of includes for more information.
Compatibility with various OpenGL editions
If any class, function or part of code depends on particular OpenGL edition (e.g. only for desktop), use conditional compilation to avoid erors on other platforms (see Target-specific code for more information). Put related documentation also into the conditional compilation block and don't forget to appropriately mark the class/function (see below). Example:
#ifndef MAGNUM_TARGET_GLES /** @brief Set polygon mode @requires_gl Polygon mode is not available in OpenGL ES. */ void setPolygonMode(PolygonMode mode); #endif
Doxygen documentation
Special documentation commands
Additionally to @todoc
, @debugoperator
, @debugoperatorenum
, @debugoperatorclassenum
, @configurationvalue
, @configurationvalueref
, @cb
, @ce
, @cpp
, @cmake
defined the same as in Corrade and all @m_*
commands from the m.css theme, these are defined:
Code
The @glsl
command expands to @cb{.glsl}
, making it possible to have inline highlighted GLSL code snippets, similarly to @cpp
and @cmake
.
Shape collision operators
Out-of-class operators for collision and collision occurence in Shapes namespace should be marked with @collisionoperator
and @collisionoccurenceoperator
, e.g.:
/** @collisionoccurenceoperator{Point,Sphere} */ inline bool operator%(const Point& a, const Sphere& b) { return b % a; } /** @collisionoperator{Point,Sphere} */ inline Collision operator/(const Point& a, const Sphere& b) { return (b/a).reverted(); }
They will appear as related functions within documentation of class for which the operator is implemented (not of class in which the operator is implemented), thus efficiently connecting the two classes together in the documentation.
Links to OpenGL and OpenAL extensions
If an OpenGL extension is referenced in the documentation, it should be done with @gl_extension
command:
/** @gl_extension{ARB,timer_query} */
It produces a link to the specification of the extension in OpenGL registry:
With command @gl_extension2
you can specify extension filename, if the previous command gives 404 error. For example
/** @gl_extension2{NV,read_buffer_front,GL_NV_read_buffer} */
produces this link:
OpenAL extensions can be referenced using @al_extension
, OpenAL context extension using @alc_extension
. For example
/** @al_extension{EXT,float32}, @alc_extension{SOFT,HRTF} */
produces the following:
Links to related OpenGL, OpenAL functions and definitions
If an function touches OpenGL, related OpenGL functions should be documented in @see
block with @fn_gl
command. If only specific definition is used in the function, document it with @def_gl
command. Example usage:
/** ... @see @fn_gl{Enable}/@fn_gl{Disable} with @def_gl{TEXTURE_CUBE_MAP_SEAMLESS} */ static void setSeamless(bool enabled) { enabled ? glEnable(GL_TEXTURE_CUBE_MAP_SEAMLESS) : glDisable(GL_TEXTURE_CUBE_MAP_SEAMLESS); }
It produces a link to the online manual:
glEnable()/ glDisable() with
GL_TEXTURE_CUBE_MAP_SEAMLESS
Some functions are documented together in a single page. For this case there's the @fn_gl2
command that takes the actual page name as second parameter:
/** @fn_gl2_keyword{CopyTextureSubImage2D,CopyTexSubImage2D} */
For functions which are not part of OpenGL core specification, but only as extensions, use @fn_gl_extension
command, e.g.
/** @fn_gl_extension{NamedCopyBufferSubData,EXT,direct_state_access} */
First parameter is function name without the suffix, the second two parameters are the same as in @gl_extension
command. It produced link to extension specification, with function name as link text:
For OpenAL, the commands are @fn_al
, @fn_alc
, @def_al
and @def_alc
. Example:
/** ... @see @fn_al{Listenerfv} with @def_al{VELOCITY} */ static void setListenerVelocity(const Vector3& velocity) { alListenerfv(AL_VELOCITY, velocity.data()); }
Produces:
alListenerfv()
withAL_VELOCITY
There are additional commands that combine @fn_*
/ @def_*
and @m_keywords
together (i.e., provide a link to given GL/AL API and also add that name as an search keyword for current documented symbol). In particular, it's @fn_gl_keyword
, @fn_gl2_keyword
, @def_gl_keyword
, @fn_al_keyword
, @fn_alc_keyword
, @def_al_keyword
and @def_alc_keyword
. Their usage is equivalent to usage of commands without the _keyword
suffix.
Classes and functions requiring specific OpenGL, OpenAL version or extensions
If any class or function requires specific OpenGL version above 2.1, it should be marked with appropriate command @requires_glXX
, where XX
is version number (e.g. 42
for OpenGL 4.2) or @requires_extension
for specific extension which is not in any core OpenGL version. It should be used in conjunction with @gl_extension
command, if there is an extension providing the same functionality. For example:
/** @requires_gl33 Extension @gl_extension{ARB,timer_query} */
If class is marked with the command, member and related functions shouldn't be marked. On the other hand, if the version/extension is needed only by one function, only the function should be marked. If the extension is needed only for some functionality (not related to any member function), it should be noted in the description.
Similarly for OpenGL ES there is command @requires_gl
for functionality not available in OpenGL ES at all, @requires_gles30
for functionality requiring OpenGL ES 3.0 (i.e. not part of OpenGL 2.0) and @requires_es_extension
for specific extensions not part of OpenGL ES specification. When there is both required desktop OpenGL version/extension and OpenGL ES version/extension, first come desktop requirements, then ES requirements.
All classes and functions using those commands are cross-referenced in page Version and extension requirements.
For OpenAL, the command is @requires_al_extension:
/** @requires_al_extension Extension @al_extension{EXT,MCFORMATS} */
Section ordering
In detailed documentation the text should be always first, the blocks are then ordered by their importance. Various @note
, @attention
and @warning
blocks to highlight some information are always first, then @see
block with links to related stuff, where related Magnum functions are first and links to related GL API last, then various support information such as @requires_glXX
, @requires_es_extension
etc. (first desktop GL, then ES, then WebGL), after that @deprecated_gl
and @deprecated
information and @todo
, @todoc
and @bug
always last, as they are the least important and in most cases only for internal use.
Unit tests
All unit tests use Corrade's TestSuite.
Don't forget to test all constexpr
methods — many compilers don't implicitly check whether the constexpr
keyword can be used but then complain when you force the expression to be constant. It's better not to have given method marked as constexpr
than have it marked it errorneously. It's usually not desirable to have special test case for constexpr
behaviors, add constexpr
keywords to existing test cases to avoid duplicated testing of the same thing. Example (testing a copy constructor):
constexpr Vector3 a(1.5f, 2.0f, 0.4f); constexpr Vector3 b(a); CORRADE_COMPARE(b, Vector3(1.5f, 2.0f, 0.4f));
Don't forget to test implicit/explicit constructors and conversion operators where it matters (i.e. all low-level and frequently used types like vectors, matrices etc.). If the constructor/operator is implicit, test it in the context where explicit one would fail to compile, if it is explicit, test its explicitness with std::false
). These tests might catch various ambiguous call errors which would otherwise be unnoticed:
Vector2 a = {1.5f, 0.5f}; // Explicit constructor would fail to compile here CORRADE_COMPARE(a, Vector2(1.5f, 0.5f)); Vector2i b(a); // Implicit conversion operator would return true in 2nd check CORRADE_COMPARE(b, Vector2(1, 0)); CORRADE_VERIFY(!(std::is_convertible<Vector2, Vector2i>::value));
If some type should be constructible also from base type (additionaly to copy constructor), don't forget to test that too. The test is also usually needed only for low-level frequently used types (vectors, matrices) where such error would do largest harm. Depending on how copy constructor is implemented, you probably don't need to test classic copy construction, as it would be handled by the already tested one. Example (copy construction from base type):
Vector<3, Float> a(1.5f, 2.0f, 0.4f); Vector3 b(a); CORRADE_COMPARE(b, Vector3(1.5f, 2.0f, 0.4f));