![]() |
MCRO
C++23 utilities for Unreal Engine.
|
Any contributions to MCRO are welcome and appreciated. If you want to contribute back to its source code we will do our best that your work will consistently fit in to the code base. In order to make this process easier please consider the following guide for contribution.
MCRO currently is only big enough for one main branch, which is locked from direct commits. Before contributing your change make a feature or a bugfix branch about what you'd like to add / fix. For example feature/shiny-new-stuff or bugfix/annoying-stuff. Please keep your commits focused on the subject of what the branches name. If you find dependency fixes or add dependency features which your subject relies on, it can still be part of the same branch and the same PR, but please explicitly list that in the PR description and title. PR's should always target main branch.
When you create a PR please describe your train-of-thought behind it and describe the root cause why you made it. The length of this description is usually proportional to the amount of changes or additions introduced (unless they're repetitive). In most cases it will be required to write unit tests for your changes. If you feel like that's too much work, submit your PR anyway and we may discuss how we could help with that.
MCRO is still a small project so there are not much rules for contribution, but please consider common-sense and cooperation with others. Also most importantly be kind, understanding, professional and assertive when phrasing the text of any PR, its contents, or any discussion point around MCRO and its broader vicinity. Offensive tones, discriminatory language, any form of bigotry, slender, racism, sexism, xenophobia, anti-LGBTQ, anti-semitism will not be tolerated, even in the unlikely event that the "contribution" behind such foul language is "valuable".
MCRO is slightly deviating in code-style from the usual Unreal Coding Style-guides. This is chosen for personal preference and couple of pragmatic things. In my opinion the Unreal Guidelines are too strictly followed in the industry, to the degree that it is actively working against code readability, and promotes verbosity and bloat. For example the total ban on auto keyword or the ban on implicit conversion constructors.
This is matter of personal opinions and tastes and I'm sure many of the readers will have perfectly valid reasoning against all the style changes MCRO brings with itself. I'm trying to justify my changes and reason why they're practical.
The following sections are either changes from the Unreal coding style or specifications where it is not specified.
Indentation is done with tab characters (as in Unreal).
Header files should be placed in explicit subfolders of their topics/modules. This may result in deeper folder structure but it allows to have simpler header file names without the risk of clashing and a better understanding of where they come from in the source file. For example don't place them directly in public module folders:
Do place them in a subfolder:
This seems redundant but provides immediate feedback about the origin of a header file when included.
Epic games unfortunately doesn't believe in namespaces as language features (or do they now? idk anymore), and so they force unreal developers to use smurf-naming almost everywhere.
I on the other hand believe in namespaces and that they've been invented for a good reason. That's why almost everything in MCRO should be under topical namespaces where that is allowed. Obviously for UClasses and other things processed by UHT namespaces are not usable.
This means MCRO features should be imported this way:
This can be even done in a header file as the namespace usage has a clear scope boundary.
When using the MCRO library, a cumulated header file is used Mcro/CommonCore.h and Mcro/Common.h the latter containing more elaborate extra features. Both declare the Mcro::Common namespace which collects all MCRO features into a single using directive. When adding new headers and namespaces to MCRO, you should also add those to Mcro/CommonCore.h or Mcro/Common.h depending on how heavy said feature is.
Using MCRO features in headers which are processed by UHT, so in UClasses, UStructs and so on, is a bit trickier. You may use fully qualified names like Mcro::Common::IError or shorten the namespace with an alias namespace mcro = Mcro::Common both are meh but "it is what it is".
Once Unreal will support C++ modules for all platforms and all major compilers (not only on MSVC) then namespaces from MCRO may be omitted. That however will be a breaking major version change.
In addition to the pascal-case Hungarian notation for types, templates and enums the following rules are used in MCRO:
Template declaration should have a space between template and <.
If the template parameter lists or template arguments get long, they should be organized into multiple lines with "egyptian" arrow-brackets, with the closing bracket un-indented.
C++ 20 concepts are a new metaprogramming feature, but Unreal actually had a naming convention for them even before that became part of C++. So MCRO takes this onwards and uses the C prefix for concepts:
Template parameters should follow PascalCase casing and shouldn't have a prefix
In templating empty structs are often used as functions to transform one type to another, or to test something on a type using specialization. However the result of this is either an inner type alias or an inner constexpr value. For example in STL type traits remove_pointer is a struct and you would use its result through a type alias typename remove_pointer<T>::type. Except you would not do this and use the alias remove_pointer_t<T>. However this eventually makes remove_pointer an implementation detail, as it's not meant to be used outside of STL headers (unless for type traits where specialization makes sense). remove_pointer_t<T> is used much more frequently than remove_pointer in that regard.
For that reason similar functional templating structs are postfixed with _Struct in MCRO, and its alias is used without any appendages:
All names in Unreal should start with a capital letter, however that can reduce visual clarity about what is a local variable and what is something outside of a function body scope, like a class member or a global variable. For this reason MCRO advocates for camelCasing local variables or function parameters:
Notice how the b prefix also disappeared from the local bool, and notice how there's no need to have In appendages for the parameters. This is even more useful with constructors:
If the function parameter lists or function invocation arguments get long, they should be organized into multiple lines with "egyptian" parenthesis, with the closing bracket un-indented.
If function definition follows directly the opening curly-bracket can be on the same line as the parameter list closing bracket.
Related to this, if a function exceeds three parameters, declare a struct instead for passing arguments. A POCO struct is also really useful for argument passing because C++20's designated initializer syntax, that can serve as named arguments. That notion also makes passing data to lower-level API also much easier.
Global type aliases should behave like they're actual types when it comes to naming, so prefixed PascalCase names as usual. using keyword must be used instead of typedef.
Inner type aliases however must have PascalCase casing without prefix
This is particularly important for templated types where aliases may store or transform template type parameters for later use.
C++ native TDelegate's and TMulticastDelegate's should be aliased with using keyword, as TDelegate is a simple enough template. The DECLARE_(MULTICAST)_DELEGATE(_RetVal)_(N)Param(s) macros do the same, so they should be avoided for native C++ delegate types as they're much more verbose. Delegate aliases should follow regular type alias naming rules
In MCRO curly brackets around single expression scoped statements (like if, for, while, etc...) are not mandatory. This is not enforced though. The original intention of the Unreal Coding convention with mandatory brackets were to not break macros like UE_LOG which may have multiple expressions.
It is super annoying to type the names of compatible variables when they're convertible between each-other so MCRO doesn't enforce explicit constructors on its features. The original intention of enforcing explicit converting constructors is visualizing a conversion step from one type to another type which may be expensive. However in many cases it makes the code unnecessarily bloated.
It also makes templating less intuitive to write in some particular cases.
MCRO uses
FMyType& for mutable reference (no space between)FMyType const& for const referenceconst FMyType&const after the type because we're qualifying the mode of the reference, not the type itself. This is especially important distinction with const pointers, as the type const FMyType* is not const actually. The only way to have a const reference to a const pointer is with the postfixed const-ref const FMyType* const&.FMyType const* for const pointer to keep it consistent with const&int const* const&.Note that preceeding const should be only used in simple scenarios like an array of const items TArray<const FMyType>.
auto keyword in Unreal is discouraged, and in general C++ developers are not so unanimous about its benefits. Indeed it has its foot-guns for example how it doesn't preserve reference qualifiers on itself. However some templating libraries and even MCRO itself can get really elaborate and long on templated type names, in which case auto is not only a blessing, it is necessary. For this reason auto is encouraged in MCRO.
If you take auto as short for template <typename T> T ... then you may also use its reference qualifiers in the same way. For example auto const& will avoid copying. auto&& or equivalent decltype(auto) is the same as "universal" or "forwarding" reference on template arguments (meaning that qualifiers are preserved).
In templated, constexpr or inline functions auto can be used as return type as well, especially when a lambda functor or some elaborate templated type is returned.
MCRO encourages to use trailing return types when that would be longer than 2 or 3 words, or longer than the function name. Trailing return types also can use scoped aliases as well in function definitions whereas preceeding return types cannot.
MCRO uses Doxygen to generate documentation. All public facing API needs to have a JavaDoc style comment associated with it (in 99% of cases on their top).
If the functionality is trivial a one liner is enough:
Note that @brief is mandatory, as I didn't like personally the automatic understanding of what brief should be in Doxygen (the first sentence of the first paragraph without an operator). In MCRO 'brief' can be multiple sentences or lines even, but maximum 2-3 lines and 3 sentences. Paragraphs without operators are part of detailed descriptions.
The following is an example elaborate documentation comment. Note that each line has a space aligned * character and a TAB afterwards. The tab character is chosen so the tab indentation of code examples are not broken.
Single line operators should be separated by 2 spaces from their text. Parameters can be done in blocks of single line documentations or if they have more than one line worth of information, as entire paragraphs annotated with the @(t)param operator. When blocks of single line documentation is used their parameter names should be right-aligned to the 2 spaces separating the documentation text. Many text-editors and IDE's have features to help with that (I use Rider or VS Code, both have such features (or extensions)).
Multiple instances of the same operators will be combined into the same section on the generated webpage for the item, even when they're separated by other operators, or are in different blocks of comment.
This is especially useful with the @file operator.
When the usage of the header is not trivial or the header represents a more elaborate system of API components, use the @file operator in a block comment after includes but before the start of any other code.
Unless the header mainly declares one namespace. Documenting namespaces is done the same way as anything else, including the mandatory @brief.
Unlike in Unreal, MCRO encourages to describe what a particular module is and how it is used in its module-rule file, documenting its module rule class. Doxygen supports the XML Docs convention popular in .NET, so in C# that is used in compatible places. Unless for @file or the "license comment" which keeps using the JavaDoc comment blocks.
MCRO is licensed under MPL 2.0. MPL recognises individual files as separate entities instead of GPL wich treats an entire project as a single big entity. For this reason it is very important that each source file in MCRO starts with a license comment on its top.
@noop License Comment part is important so other tooling can recognize the special usage of this comment block. This is also recognized and processed accordingly by Doxygen. C# files use the same style of this comment block as C++ files. If you've edited/created the file please add yourself to the comma separated list of authors.