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.
Who is this library for?
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.
-
Range-V3:
using namespace ranges;
enum class EFoobar { Foo, Bar };
TArray<int32> myKeys {1, 2, 0, 0, -3, 3, 0, 4, 5};
TSet<EFoobar> myValues {EFoobar::Foo, EFoobar::Bar};
auto myMap = myKeys
| views::filter([](int32 key) { return key > 0; })
| views::transform([](int32 key) { return FString::FromInt(key); })
|
Zip(myValues | views::cycle)
This header is a central include for simpler utilities MCRO offers. Higher level features like errors...
Render a range of tuples or range of ranges with at least 2 elements as a TMap.
Use this namespace for all the common features MCRO has to offer.
auto Zip(Ranges &&...right)
pipeable version of ranges::views::zip
-
Vanilla:
enum class EFoobar { Foo, Bar };
TArray<int32> myKeys {1, 2, 0, 0, -3, 3, 0, 4, 5};
TSet<EFoobar> myValues {EFoobar::Foo, EFoobar::Bar};
TArray<int32> myKeysFiltered = myKeys.FilterByPredicate([](int32 key) { return key > 0; });
TArray<FString> myKeyStrings;
Algo::Transform(myKeysFiltered, myKeyStrings, [](int32 key) { return FString::FromInt(key); });
TMap<FString, EFoobar> myMap;
for (int i = 0; i < myKeyStrings.Num(); ++i)
{
myMap.Add(myKeyStrings[i], myValues[i % myValues.Num()]);
}
Notice how we didn't need to specify the TMap type, where both the key and value types were deduced from how the input ranges were manipulated before.
Not yet impressed? May this snippet change that:
TArray<int32> mySetCopy;
TArray<int32> myArray;
myArray.SetNumUninitialized(6);
auto squares = views::ints(0)
| views::transform([](int32 i) { return i*i; });
auto mySet = squares
| views::take(5)
squares
| views::take(6)
Render a range to an already existing container.
Render a range as the given container.
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.
{
->AsCrashing()
->BreakDebugger()
->WithCppStackTrace()
->Notify(LastError);
try
{
k4a::capture capture;
if (auto color = capture.get_color_image(); ColorStream->Active)
{
}
if (auto depth = capture.get_depth_image(); DepthStream->Active)
{
}
if (auto ir = capture.get_ir_image(); IRStream->Active)
{
}
}
catch (k4a::error const& exception)
{
->AsFatal()->WithCppStackTrace()->WithLocation()
->Notify(LastError);
}
}
This header is a central include for all common utilities MCRO offers including higher level features...
#define PROPAGATE_FAIL(...)
If a function returns a TMaybe or an FCanFail inside another function which may also return another e...
#define ASSERT_RETURN(...)
Similar to check() macro, but return an error instead of crashing.
Use this template for catching STL exceptions to preserve the exception type name.
FORCEINLINE FCanFail Success()
Return an FCanFail or FTrueOrReason indicating a success or truthy output.
A TValueOrError alternative for IError which allows implicit conversion from values and errors (no ne...
Output log of an example test error
Display LogTemp Type: Mcro::Test::FTestSimpleError
Display LogTemp Severity: Recoverable
Display LogTemp Message: |
Display LogTemp This is one test error
Display LogTemp Details: |
Display LogTemp Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Parturient maximus donec penatibus lectus non
Display LogTemp conubia amet condimentum. Tincidunt et iaculis efficitur integer, pulvinar phasellus. Mauris nisl
Display LogTemp parturient pharetra potenti aptent phasellus pharetra pellentesque. Leo aliquam vulputate pellentesque
Display LogTemp sapien gravida aptent facilisis tempus nec. Dolor aenean auctor penatibus iaculis dui justo integer
Display LogTemp porta. Sed vivamus porta sagittis nulla; sollicitudin class convallis mattis. Egestas lobortis nullam
Display LogTemp sed interdum ultricies donec.
Display LogTemp CodeContext: |
Display LogTemp D = A + B + C
Display LogTemp ErrorPropagation:
Display LogTemp - "class TSharedRef<class Mcro::Test::FTestSimpleError,1> __cdecl CommonTestError(void) @ [...]\\Mcro\\Mcro_Yn\\Private\\Mcro\\Tests\\Error.Spec.cpp : 40"
Display LogTemp - "auto __cdecl FMcroError_Spec::Define::<lambda_1>::()::<lambda_1>::operator ()(void) const @ [...]\\Mcro\\Mcro_Yn\\Private\\Mcro\\Tests\\Error.Spec.cpp : 73"
Display LogTemp InnerErrors:
Display LogTemp Appendix Foo: |
Display LogTemp Lorem ipsum
Display LogTemp Appendix Bar: |
Display LogTemp dolor sit amet consectetur
Display LogTemp Mcro::Error::FCppStackTrace: |
Display LogTemp 0x000001adda3b8e69 UnrealEditor-Mcro_Yn-Win64-DebugGame.dll!CommonTestError() [[...]\Mcro\Mcro_Yn\Private\Mcro\Tests\Error.Spec.cpp:27]
Display LogTemp 0x000001adda3b7763 UnrealEditor-Mcro_Yn-Win64-DebugGame.dll!``FMcroError_Spec::Define'::`2'::<lambda_1>::operator()'::`2'::<lambda_1>::operator()() [[...]\Mcro\Mcro_Yn\Private\Mcro\Tests\Error.Spec.cpp:60]
Display LogTemp 0x000001adda3b881b UnrealEditor-Mcro_Yn-Win64-DebugGame.dll!UE::Core::Private::Function::TFunctionRefCaller<``FMcroError_Spec::Define'::`2'::<lambda_1>::operator()'::`2'::<lambda_1>,void>::Call() [D:\ue\UE_5.5\Engine\Source\Runtime\Core\Public\Templates\Function.h:321]
Display LogTemp 0x000001adda393fd8 UnrealEditor-Mcro_Yn-Win64-DebugGame.dll!UE::Core::Private::Function::TFunctionRefBase<UE::Core::Private::Function::TFunctionStorage<0>,void __cdecl(void)>::operator()() [D:\ue\UE_5.5\Engine\Source\Runtime\Core\Public\Templates\Function.h:471]
Display LogTemp 0x000001adda3a6264 UnrealEditor-Mcro_Yn-Win64-DebugGame.dll!FAutomationSpecBase::FSingleExecuteLatentCommand::Update() [D:\ue\UE_5.5\Engine\Source\Runtime\Core\Public\Misc\AutomationTest.h:2813]
Display LogTemp 0x00007ffc4b815270 UnrealEditor-Core.dll!FAutomationTestFramework::ExecuteLatentCommands() [D:\build\++UE5\Sync\Engine\Source\Runtime\Core\Private\Misc\AutomationTest.cpp:626]
Display LogTemp 0x000001ade2471393 UnrealEditor-AutomationWorker.dll!FAutomationWorkerModule::Tick() [D:\build\++UE5\Sync\Engine\Source\Runtime\AutomationWorker\Private\AutomationWorkerModule.cpp:69]
Display LogTemp 0x00007ff6b7bc8118 UnrealEditor-Win64-DebugGame.exe!FEngineLoop::Tick() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\LaunchEngineLoop.cpp:6046]
Display LogTemp 0x00007ff6b7be57ac UnrealEditor-Win64-DebugGame.exe!GuardedMain() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\Launch.cpp:188]
Display LogTemp 0x00007ff6b7be589a UnrealEditor-Win64-DebugGame.exe!GuardedMainWrapper() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:123]
Display LogTemp 0x00007ff6b7be9114 UnrealEditor-Win64-DebugGame.exe!LaunchWindowsStartup() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:277]
Display LogTemp 0x00007ff6b7bfbd04 UnrealEditor-Win64-DebugGame.exe!WinMain() [D:\build\++UE5\Sync\Engine\Source\Runtime\Launch\Private\Windows\LaunchWindows.cpp:317]
Display LogTemp 0x00007ff6b7bff0ca UnrealEditor-Win64-DebugGame.exe!__scrt_common_main_seh() [D:\a\_work\1\s\src\vctools\crt\vcstartup\src\startup\exe_common.inl:288]
Display LogTemp 0x00007ffd072c259d KERNEL32.DLL!UnknownFunction []
Display LogTemp 0x00007ffd0822af38 ntdll.dll!UnknownFunction []
When everything else fails we can display the error to the user
Text macros without parentheses
-
Without parentheses:
FString myVar(
TEXT_"Hello world!");
UE_LOG(LogTemp, Display,
TEXT_"%s", *myVar);
FText myText =
INVTEXT_"Even FText macros are there";
auto myString =
STRING_"Typed literals";
auto myName =
NAME_"FName string literals";
constexpr auto myView =
TEXTVIEW_"String view created in constexpr";
constexpr auto stdView =
STDVIEW_"TCHAR dependent STL string literal";
#define NAME_
A convenience macro to allocate an FName directly.
#define INVTEXT_
A convenience alternative to Unreal's own INVTEXT macro but this one doesn't require parenthesis arou...
#define TEXT_
A convenience alternative to Unreal's own TEXT macro but this one doesn't require parenthesis around ...
#define STDVIEW_
A convenience alternative to Unreal's own TEXTVIEW macro but this one doesn't require parenthesis aro...
#define TEXTVIEW_
A convenience alternative to Unreal's own TEXTVIEW macro but this one doesn't require parenthesis aro...
#define STRING_
A convenience macro to allocate an FString directly.
-
Vanilla:
FString myVar(TEXT("Hello world!"));
UE_LOG(LogTemp, Display, TEXT("%s"), *myVar);
FText myText = INVTEXT("Even FText macros are there");
FString myString(TEXT("Typed literals"));
FName myName (TEXT("FName string literals"));
constexpr auto myView = TEXTVIEW("String view created in constexpr");
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.
The earlier was even a suggestion of Microsoft when they announced their standard compliant new preprocessor, and asked the general public to stop exploiting the behaviors of the old one.
In code these macros are referred to as fake text/string literals.
String formatting literals
There are the FMT fake text literals. They can be both leading or trailing before/after the double quoted text. This is a wrapper around FString::Format, but it can handle much more types automatically.
With ordered arguments:
-
MCRO trailing:
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
);
#define _FMT(...)
Trailing fake text literal which makes using FString::Format(...); much more comfortable.
As a personal opinion this is more readable in majority of cases or with multiple lines, but beauty is in the eye of the beholder.
-
Vanilla:
FName type(TEXT("Foo")); FText comment = INVTEXT("Hello!"); int32 count = 42;
FStringFormatOrderedArguments fmtArgs;
fmtArgs.Add(type.ToString());
fmtArgs.Add(comment.ToString());
fmtArgs.Add(count);
FString fmt = FString::Format(TEXT("Given {0} with comment '{1}' and count {2}"), fmtArgs);
-
MCRO leading:
auto type =
NAME_"Foo";
auto comment =
INVTEXT_"Hello!"; int32 count = 42; EPixelFormat format = PF_Unknown;
FString fmt =
FMT_ (type, comment, count, format)
"Given {0} with comment '{1}' and count {2} with format {3}";
#define FMT_(...)
Leading fake text literal which makes using FString::Format(...); much more comfortable.
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.
With named arguments:
-
MCRO trailing:
auto type =
NAME_"Foo";
auto comment =
INVTEXT_"Hello!"; int32 count = 42; EPixelFormat format = PF_Unknown;
FString fmt =
TEXT_"Given {Type} with comment '{Comment}' and count {Count} with format {Format}" _FMT(
(Type, type)
(Comment, comment)
(Count, count)
(Format, format)
);
As a personal opinion this is more readable in majority of cases or with multiple lines, but beauty is in the eye of the beholder.
-
Vanilla:
FName type(TEXT("Foo")); FText comment = INVTEXT("Hello!"); int32 count = 42;
FStringFormatNamedArguments fmtArgs;
fmtArgs.Add(TEXT("Type"), type.ToString());
fmtArgs.Add(TEXT("Comment"), comment.ToString());
fmtArgs.Add(TEXT("Count"), count);
FString fmt = FString::Format(TEXT("Given {0} with comment '{1}' and count {2}"), fmtArgs);
-
MCRO leading:
auto type =
NAME_"Foo";
auto comment =
INVTEXT_"Hello!"; int32 count = 42; EPixelFormat format = PF_Unknown;
FString fmt =
FMT_((Type, type) (Comment, comment) (Count, count) (Format, format))
"Given {Type} with comment '{Comment}' and count {Count} with format {Format}";
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();
FString result = array | RenderAsString();
FString enumsA =
TEXT_"A list of enums: {0}" _FMT(array);
FString enumsB =
TEXT_"Don't like commas? No problem: {0}" _FMT(
array | views::take(2) | Separator(
TEXT_" and ")
);
FString enumsB =
TEXT_"Don't like square brackets either? {0}" _FMT(
array | views::take(2) | Separator(
TEXT_" and ") | Enclosure(
TEXT_"<",
TEXT_">")
);
FString enumsC =
TEXT_"Or just glued together: {0}" _FMT(
array | views::take(2) | NoDecorators()
);
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&)>;
FOnStateResetTheFirstTimeEvent OnStateResetTheFirstTimeEvent;
using FDefaultInitializerDelegate = TDelegate<FString(FObservable const&)>;
void SetDefaultInitializer(FDefaultInitializerDelegate const& defaultInit)
{
}
}
-
MCRO:
struct FListener : TSharedFromThis<FListener>
{
TMulticastDelegate<void(FObservable const&)> PropagateEvent;
void OnFirstStateReset(FObservable const& observable, FStringView capturedData);
void BindTo(FObservable& observable)
{
observable.SetDefaultInitializer(From(this, [this](FObservable const&) -> FString
{
return TEXT_"Some default value";
}));
observable.OnStateResetTheFirstTimeEvent.Add(From(this, PropagateEvent));
auto firstReset = From(
this, &FListener::OnFirstStateReset,
TEXTVIEW_"capture");
observable.OnStateResetTheFirstTimeEvent.Add(firstReset);
}
}
The extra layer of namespace InferDelegate is there for guarding common vocabulary (From) but still a...
-
Vanilla:
struct FListener : TSharedFromThis<FListener>
{
TMulticastDelegate<void(FObservable const&)> PropagateEvent;
void OnFirstStateReset(FObservable const& observable, FStringView capturedData);
void BindTo(FObservable& observable)
{
observable.SetDefaultInitializer(FDefaultInitializerDelegate::CreateSPLambda(this, [this](FObservable const&) -> FString
{
return TEXT("Some default value");
}));
observable.OnStateResetTheFirstTimeEvent.AddSPLambda(this, [this](FObservable const& observableArg)
{
PropagateEvent.Broadcast(observableArg);
});
observable.OnStateResetTheFirstTimeEvent.AddSP(this, &FListener::OnFirstStateReset, TEXTVIEW("capture"));
}
}
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:
SomeEvent.Broadcast(42);
SomeEvent.Add(From([](int value)
{
UE_LOG(LogTemp, Display,
TEXT_"The last argument this event broadcasted with: %d", value);
}));
SomeOtherEvent.Broadcast(1337);
SomeOtherEvent.Add(
From([](int value)
{
UE_LOG(LogTemp, Display,
TEXT_"The last argument this event broadcasted with: %d", value);
}),
{.Belated = true}
);
"Extension" of a common TMulticastDelegate. It allows to define optional "flags" when adding a bindin...
The extra layer of namespace InferDelegate is there for guarding common vocabulary (From) but still a...
Or your thing only needs to do its tasks on the first time a frequently invoked event is triggered?
SomeFrequentEvent.Add(
From([](int value)
{
UE_LOG(LogTemp, Display,
TEXT_"This value is printed only once: %d", value);
}),
{.Once = true}
);
SomeFrequentEvent.Broadcast(1);
SomeFrequentEvent.Broadcast(2);
Chaining events?
TMulticastDelegate<void(int)> LowerLevelEvent;
HigherLevelEvent.Add(From([](int value)
{
UE_LOG(LogTemp, Display,
TEXT_"Value: %d", value);
}));
LowerLevelEvent.Add(HigherLevelEvent.Delegation(this ));
LoverLevelEvent.Broadcast(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
struct FMyType
{
FStringView GetTypeString() { return TTypeName<FMyType>; }
}
even better with C++ 23 deducing this
struct FMyBaseType
{
template <typename Self>
FString GetTypeString(this Self&&) const { return TTypeString<Self>(); }
}
struct FMyDerivedType : FMyBaseType {}
FMyDerivedType myVar;
UE_LOG(LogTemp, Display,
TEXT_"This is `%s`", *myVar.GetTypeString());
FMyBaseType const& myBaseVar = myVar;
UE_LOG(LogTemp, Display,
TEXT_"This is `%s`", *myBaseVar.GetTypeString());
MCRO provides a base type IHaveType for storing the final type as an FName to avoid situations like above
struct FMyDerivedType : FMyBaseType
{
FMyDerivedType() { SetType(); }
}
FMyDerivedType myVar;
UE_LOG(LogTemp, Display,
TEXT_"This is as expected a `%s`", *myVar.GetType().ToString());
FMyBaseType const& myBaseVar = myVar;
UE_LOG(
LogTemp, Display,
TEXT_"But asking the base type will still preserve `%s`",
*myBaseVar.GetType().ToString()
);
A barebones base class for types which may store their type-name as a string.
Auto modular features
One use of TTypeName is making modular features easier to use:
{
public:
virtual void SolveAllProblems() = 0;
};
{
public:
FProblemSolver() { Register(); }
virtual void SolveAllProblems() override;
};
IProblemSolvingFeature::Get().SolveAllProblems();
Tagging an implementation of a feature.
Auto Modular Features are a workflow with Modular Features where the developer doesn't have to rely o...
A wrapper around a given object which lifespan is bound to given module.
Notice how the feature name has never needed to be explicitly specified as a string, because the type name is used under the hood.
Observable TState
You have data members of your class, but you also want to notify others about how that's changing?
struct FMyStuff
{
TState<int, {.StorePrevious =
true}> State {-1};
FMyStuff()
{
State.
OnChange([](
int next, TOptional<int> previous)
{
UE_LOG(LogTemp, Display,
TEXT_"Changed from %d to %d", previous.Get(-2), next);
});
State.OnChange(
[](
int next) { UE_LOG(LogTemp, Display,
TEXT_"The first changed value is %d", next); },
{.Once = true}
);
}
void Update(int input)
{
if (State.HasChangedFrom(input))
{
}
}
};
struct FFoobar : TSharedFromThis<FFoobar>
{
FMyStuff MyStuff;
{
UE_LOG(LogTemp, Display,
TEXT_"React to %d with %s", change.
Next, *capture);
}
void Do()
{
MyStuff.Update(1);
MyStuff.Update(2);
MyStuff.Update(2);
MyStuff.OnChange(
[](
int next) { UE_LOG(LogTemp, Display,
TEXT_"Arriving late %d", next); },
{.Belated = true}
);
MyStuff.Update(3);
MyStuff.OnChange(
From(
this, &FFoobar::Thing,
TEXT_"a unicorn"));
MyStuff.Update(4)
}
}
TInferredDelegate< Function, Captures... > From(Function func, Captures &&... captures)
Instead of specifying manually a delegate type, infer it from the input function and the extra captur...
FDelegateHandle OnChange(TDelegate< void(TChangeData< T > const &)> onChange, FEventPolicy const &eventPolicy={})
Add a delegate which gets a TChangeData<T> const& if this state has been set.
This struct holds the circumstances of the data change. It cannot be moved or copied and its lifespan...
Storage wrapper for any value which state needs to be tracked or their change needs to be observed....
Multiple states can be also synced together, just declare pull or push sync relation once and they will update accordingly until one of them goes out of scope.
for (auto const& child : Children)
{
LastError.SyncPull(SharedThis(this), child->LastError);
Attempts.SyncPush(child, child.Attempts);
}
This of course only makes sense if the contents of the state is copyable.
Function Traits
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)
{
template <
CFunctorObject Initializer,
CUObject Result = std::decay_t<Argument>
>
requires std::is_lvalue_reference_v<Argument>
{ ... }
}
typename TFunctionTraits< std::decay_t< T > >::template Arg< I > TFunction_Arg
Shorthand for getting a type of a function argument at given position I.
Mirror of FStaticConstructObjectParameters but it's a plain C++ object and doesn't have a constructor...
Extending the Slate declarative syntax
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.
#include "Mcro/Common";
{
return [&](STextBlock::FArguments& args) -> auto&
{
return args
. Text(FText::FromString(text))
. Font(FCoreStyle::GetDefaultFontStyle("Mono", 12));
};
}
{
return [&](STextBlock::FArguments& args) -> auto&
{
return args
/ Text(text);
};
}
auto ExpandableText(
FText const& title,
FString const& text,
{
return [&](SExpandableArea::FArguments& args) -> auto&
{
return args
. AreaTitle(title)
. InitiallyCollapsed(true)
. BodyContent()
[
SNew(STextBlock) / textAttributes
];
};
}
MCRO_API EVisibility IsVisible(bool visible, EVisibility hiddenState=EVisibility::Collapsed)
Convenience function for typing less when widget visibility depends on a boolean.
An attribute block functor which takes in reference of FArguments or FSlotArguments and returns the s...
Or add slots right in the Slate declarative syntax derived from an input range:
auto Row() -> SVerticalBox::FSlot::FSlotArguments
{
return MoveTemp(SVerticalBox::Slot()
. HAlign(HAlign_Fill)
. AutoHeight()
);
}
void Construct(FArguments const& inArgs)
{
ChildSlot
[
SNew(SVerticalBox)
+ Row()[ SNew(STextBlock) / Text(
TEXT_"Items:") ]
+
TSlots(inArgs._Items.Get(), [&](FString
const& item)
{
return MoveTemp(
Row()[ SNew(STextBlock) / Text(item) ]
);
})
+ Row()[ SNew(STextBlock) / Text(
TEXT_"Fin.") ]
];
}
Add multiple slots at the same time with the declarative syntax derived from an input data array.
ISPC parallel tasks support
McroISPC module gives support for task and launch keywords of the ISPC language
task void MakeLookupUVLine(
uniform uint32 buffer[],
uniform uint32 width
) {
uniform uint32 lineStart = taskIndex0 * width;
foreach (i = 0 ... width)
{
varying uint32 address = lineStart + i;
buffer[address] = (i << 16) | taskIndex0;
}
}
export void MakeLookupUV(
uniform uint32 buffer[],
uniform uint32 width,
uniform uint32 height
) {
launch [height] MakeLookupUVLine(buffer, width);
}
Last but not least
Legal
Third-party components used by MCRO
When using MCRO in a plugin distributed via Fab, the components above must be listed upon submission.
In addition the following tools and .NET libraries are used for build tooling:
And documentation:
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.
Full text of the license