Monado OpenXR Runtime
|
Here are some general code style guidelines we follow.
Note that we aim to "code with respect", to avoid terminology that may limit our community or hurt those in it, as well as to conform with emerging industry standards. Good guidelines to look to include the Android Coding with Respect policy and the Write Inclusive Documentation page from the Google developer documentation style guide. The latter also links to a word list for clear documentation, which, while not binding on this project, is useful in making sure your code, comments, and docs are understandable by the worldwide Monado community.
In Monado we strongly prefer if all MRs merged into the main Monado repository also includes changelog fragments. A changelog fragment is a small file detailing the changes in the MR on a per area basis. They are slightly more detailed then a commit subject line, but usually just one or two lines, usually providing a little bit of context to the change. Sometimes for "big" changes they are more detailed and provide paragraph of description, such as for adding of new drivers, or large refactors. The changelog fragments are used to generate the Changelog for Monado file, updated on each release (and running on the CI). The changelog is generated with proclamation.
The changelog fragments are located in the doc/changes
folder, organised into sub-categories in subfolders. There isn't a 1-to-1 mapping of changelog fragment to commit, but instead they are per change. A changelog fragment file is named mr.
+ MR number + .md
, for MR 1234 it would be named mr.1234.md
. If a MR has multiple changes for one sub-category a number is added between the MR number and file extension, example mr.1234.1.md
and mr.1234.2.md
. If a change spans multiple MRs, such as fixing a feature introduced in a earlier MR we imply the use of YAML headers to mark a changelog fragment applying to multiple MRs, can also be used to link issues.
Generally the last commit in a MR adds the changelog fragments, as unfortunately MR numbers are allocated when opened, also provides a nice readable separation between MRs in the git history. Examples for commits adding changelog fragments can be seen here, here and here.
Internal APIs, when it makes sense, should be C APIs. Headers that define general communication interfaces between modules (not only use of utilities) belong in the xrt/include/xrt
directory, and should not depend on any other module outside that directory. (As a historical note: this directory gets its name from a compressed version of the phrase "XR RunTime", a generic term for Monado and an early development codename. Also, it's shorter than monado_
and so nicer to use in code.)
What follows are some basic API usage rules. Note that all the module usage relations must be expressed in the build system, so module usage should form a directed-acyclic-graph.
xrt/include/xrt
xrt
interface headers themselves) can (and should!) use APIs declared in xrt/auxiliary/util
.auxiliary/util
and the xrt
interface headers themselves can use APIs declared in other xrt/auxiliary
modules.lower_snake_case
for types and functions.UPPER_SNAKE_CASE
for macros. e.g. U_TYPED_CALLOC (which is how all allocations in C code should be performed)xrt_
belong in the xrt/include/xrt
directory, and nothing named starting with xrt_
should be declared anywhere else. (Interfaces declared in xrt/include/xrt
are implemented in other modules.)struct
and enum
types, but instead refer to them in long form, saying struct
or enum
then the name. The exception to not using typedefs is function pointers used as function arguments as these become very hard to both read and type out._t
. Function pointer typedefs should end with _func_t
.lower_snake_case
or acronyms.out_
._
-delimited) word in the structure type name. Sometimes, it is an abbreviated form of that name instead. Relevant examples:xcn
. It creates an xrt_swapchain, which it populates in the parameter named out_xscn
: out_
because it's a purely output parameter, xscn
from xrt_swapchain_native specifically the letters Xrt_SwapChain_Native
. xrt_swapchain and related types are a small exception to the rules - there are only 2 words if you go by the _
delimiters, but for clarity we treat swapchain as if it were two words when abbreviating. A few other places in the xrt
headers use x
+ an abbreviated name form, like xinst
for xrt_instance, xdev
for xrt_device, xsysd
sometimes used for xrt_system_devices.create
and destroy
are used when the functions actually perform allocation and return the new object, or deallocation of the passed-in object.init
and, if needed, one of cleanup
/fini
/teardown
. (We are not yet consistent on these names.) One common example is when there is some shared code and a structure partially implementing an interface: a further-derived object may need to call an init
function on the shared structure, but it was allocated by the derived object and held by value.xrt::
.xrt/include/xrt
, by design, so this is not ambiguous.detail
namespace, as seen elsewhere in the C++ ecosystem.CamelCase
lowerCamelCase
kCamelCase
.hpp
to signify this.T
between two entities A
and B
, try to use variable names like T_A_B
to express the transform such that B = T_A_B * A
. This is equivalent to "`B` expressed w.r.t. `A`" and "the
transform that converts a point in `B` coordinates into `A` coordinates". T
can be used for 4x4 isometry matrices, but you can use others like P
for poses, R
for 3x3 rotations, Q
for quaternion rotations, t
for translations, etc.This is an incomplete list of conventional idioms used in the Monado codebase.
Despite being in C, the design is fairly object-oriented. Types implement interfaces and derive from other types typically by placing a field of that parent type/interface as their first element, conventionally named base
. This means that a pointer to the derived type, and a pointer to the base type, have the same value.
For example, consider client_gl_swapchain
xrt_
prefix.)Structures/types that represent "objects" are often passed as the first parameter to many functions, which serve as their "member functions". Sometimes, these types are opaque and not related to other types in the system in a user-visible way: they should have a _create
and _destroy
function. See time_state, time_state_create, time_state_destroy
In other cases, an interface will have function pointers defined as fields in the interface structure. (A type implementing these may be opaque, but would begin with a member of the interface/base type.) These interface function pointers must still take in a self pointer as their first parameter, because there is no implied this
pointer in C. This would result in awkward calls with repeated, error-prone mentions of the object pointer, such as this example calling the xrt_device::update_inputs interface: xdev->update_inputs(xdev)
. These are typically wrapped by inline free functions that make the call through the function pointer. Considering again the xrt_device example, the way you would call xrt_device::update_inputs is actually xrt_device_update_inputs().
Destroy free functions should take a pointer to a pointer, performing null checks before destruction, and setting null. They always succeed (void return): a failure when destroying an object has little meaning in most cases. For a sample, see xrt_images_destroy. It would be used like this:
Note that this pattern is used in most cases but not all in the codebase: we are gradually migrating those that don't fit this pattern. If you call a destroy function that does not take a pointer-to-a-pointer, make sure to do null checks before calling and set it to null after it returns.
Also note: when an interface includes a "destroy" function pointer, it takes the normal pointer to an object: The free function wrapper is the one that takes a pointer-to-a-pointer and handles the null checks. See for example xrt_instance_destroy takes the pointer-to-a-pointer, while the interface method xrt_instance::destroy takes the single pointer.