A C++23 templating and utilities Unreal Engine plugin, for a more civilized age.
Attention
This library is still under active development, nothing is set to stone yet in its updates. Use in production only if you know very well what you're doing.
MCRO (pronounced 'em-cro') is made for C++ developers who may view Unreal Engine as a C++ framework first and as a content creation tool second. It is meant to support development of other plugins, so MCRO on its own has no primary purpose, but to provide building blocks for its dependant plugins.
It also embraces quite elaborate C++ techniques involving templates and synergic combination of language features. The documentation also makes some effort explaining these techniques when used by MCRO, but the library users themselves don't need to go that deep, in order to enjoy many features of MCRO.
What MCRO can do?
Here are some code appetizers without going too deep into their details. The demonstrated features usually can do a lot more than what's shown here.
(symbols in source code are clickable!)
Range-V3 for Unreal Containers
In vanilla Unreal working with containers is an imperative endeavor where if we need to manipulate them intermediate steps are usually stored in other containers. MCRO on the other hand brings in lazy-evaluated declarative range manipulation via the great Range-V3 library.
Notice how RenderAs could deduce the full type of TSet from its preceeding operations. This works with many Unreal containers.
This is just the very tip of the iceberg what this pattern of collection handling can introduce.
Error handling
An elegant workflow for dealing with code which can and probably will fail. It allows the developer to record the circumstances of an error, record its propagation accross components which are affected, add suggestions to the user or fellow developers for fixing the error, and display that with a modal Slate window, or print it into logs following a YAML structure.
FText myText = INVTEXT("Even FText macros are there");
FString myString(TEXT("Typed literals"));
FName myName (TEXT("FName string literals"));
constexprauto myView = TEXTVIEW("String view created in constexpr"); // -> FStringView
// there are no vanilla utilities for cross-TCHAR STL string handling
One of the oldest eye-sores I have with Unreal Engine source code is the TEXT() macro, especially the extra pair of parentheses it requires around string literals. Unfortunately it is impossible to achieve the same goal without any preprocessor, that would be ideal, but one can exploit C++ string literal concatenation rules for simple TCHAR strings, or operator overloads for FText or FName.
In code these macros are referred to as fake text/string literals.
String formatting literals
There are two groups of macros to express string formatting/interpolation with a "nice" syntax. These are also fake text literals but they can be both leading or trailing after the double quoted text.
Wrapper around FString::Printf:
auto type = NAME_"Foo"; auto comment = STRING_"Hello!"; int32 count = 42;
FString printf = TEXT_"Given %s with comment '%s' and count %d"_PRINTF(
*type.ToString(),
*comment,
count
);
// -> "Given Foo with comment 'Hello!' and count 42"
As a personal opinion this is only readable when there's a simple text and maximum of two arguments, but beauty is in the eye of the beholder.
But for more modern and more automatic text conversion there's also a wrapper around FString::Format, but it can handle much more types automatically than FString::Format.
With ordered arguments:
auto type = NAME_"Foo"; auto comment = INVTEXT_"Hello!"; int32 count = 42; EPixelFormat format = PF_Unknown;
FString fmt = TEXT_"Given {0} with comment '{1}' and count {2} with format {3}"_FMT(
type, comment, count, format
);
// -> "Given Foo with comment 'Hello!' and count 42 with format PF_Unknown"
"Given {Type} with comment '{Comment}' and count {Count} with format {Format}";
// -> "Given Foo with comment 'Hello!' and count 42 with format PF_Unknown"
// ^ works with any undecorated enum type
// no need to be UENUM
As a personal opinion this is very rarely more readable than the trailing counterpart, but beauty is in the eye of the beholder.
Notice how FMT macros decide named vs. ordered arguments based on the argument listing syntax alone, and the developer doesn't have to type out if they want named or ordered formatting.
Ranges as strings
But wait there's more, remember ranges? They can be automatically converted to string as well:
using namespace ranges;
enum class EFoo { Foo, Bar, Wee, Yo };
TArray<EFoo> array = MyEnumList(); // imagine this function just lists all the entries in EFoo
FString result = array | RenderAsString();
// -> "[Foo, Bar, Wee, Yo]"
FString enumsA = TEXT_"A list of enums: {0}"_FMT(array);
// -> "A list of enums: [Foo, Bar, Wee, Yo]"
FString enumsB = TEXT_"Don't like commas? No problem: {0}"_FMT(
// -> "Don't like commas? No problem: [Foo and Bar]"
FString enumsB = TEXT_"Don't like square brackets either? {0}"_FMT(
array | views::take(2) | Separator(TEXT_" and ") | Enclosure(TEXT_"<", TEXT_">")
);
// -> "Don't like square brackets either? <Foo and Bar>"
FString enumsC = TEXT_"Or just glued together: {0}"_FMT(
array | views::take(2) | NoDecorators()
);
// -> "Or just glued together: FooBar"
Delegate type inference
In Mcro::Delegates namespace there's a bunch of overloads of From function which can infer a delegate type and its intended binding only based on its input arguments. It means a couple of things:
The potentially lengthy delegate types are no longer needed to be repeated.
Creating an alias for each (multicast) delegate is no longer a must have.
For example given an imaginary type with a terrible API for some reason
struct FObservable
{
using FOnStateResetTheFirstTimeEvent = TMulticastDelegate<void(FObservable const&)>;
There's also a dynamic / native (multicast) delegate interop including similar chaining demonstrated here.
Advanced TEventDelegate
Did your thing ever load after an event which your thing depends on, but now you have to somehow detect that the event has already happened and you have to execute your thing manually? With Mcro::Delegates::TEventDelegate this situation has a lot less friction:
LowerLevelEvent.Add(HigherLevelEvent.Delegation(this/* optionally bind an object */));
LoverLevelEvent.Broadcast(42);
// -> Value: 42
Of course the above chaining can be combined with belated~ or one-time invocations.
TTypeName
C++ 20 can do string manipulation in compile time, including regex. With that, compiler specific "pretty function" macros become a key tool for simple static reflection. Based on that MCRO has TTypeName
Make templates dealing with function types more readable and yet more versatile via the Mcro::FunctionTraits namespace. This is the foundation of many intricate teemplates Mcro can offer.
Constraint/infer template parameters from the signature of an input function. (This is an annotated exempt from Mcro::UObjects::Init)
TAttributeBlock adds the / operator to be used in Slate UI declarations, which can work with functions describing a common block of attributes for given widget.
This library is distributed under the Mozilla Public License Version 2.0 open-source software license. Each source code file where this license is applicable contains a comment about this.
Modifying those files or the entire library for commercial purposes is allowed but those modifications should also be published free and open-source under the same license. However files added by third-party for commercial purposes interfacing with the original files under MPL v2.0 are not affected by MPL v2.0 and therefore can have any form of copyright the third-party may choose.
Using this library in any product without modifications doesn't limit how that product may be licensed.