Skip to content
PRODUCT GITHUB  ↗

Stream Builder

A stream builder is a lightweight wrapper that gives you fast, type-safe access to a stream. It lets you treat a stream much like a TArray, with the common operations — Add, RemoveAt, Append, Reserve, and indexed access — plus a few specialized helpers. A builder can also work with a subset of a stream: for example, one channel of a texture-coordinate stream that holds several.

Stream builders come in two forms, both aliases of the same underlying template TRealtimeMeshStreamBuilderBase in the RealtimeMesh namespace:

  • TRealtimeMeshStreamBuilder<AccessType, BufferType = AccessType> — access a whole stream row.
  • TRealtimeMeshStridedStreamBuilder<AccessType, BufferType = AccessType> — access one element (channel) of a multi-element row.

The two template parameters control the access type (what you read and write in your code) and the buffer type (what’s actually stored). When they differ, the builder converts between them for you.


using namespace RealtimeMesh;
// A stream with the key `Vertex:Position` and the data type FVector3f.
FRealtimeMeshStream PositionStream = FRealtimeMeshStream::Create<FVector3f>(FRealtimeMeshStreams::Position);
// A direct builder where you know the stored type is FVector3f. This asserts if the types don't match.
// This is the fastest interaction, as no conversion is performed.
TRealtimeMeshStreamBuilder<FVector3f> PositionBuilder(PositionStream);
// A builder where you want to work as though the data were FVector3d, but the stream actually stores FVector3f.
// This incurs a slight overhead to convert both ways, but it's all done through templates,
// so it's the least-overhead conversion available.
TRealtimeMeshStreamBuilder<FVector3d, FVector3f> PositionBuilderConverting(PositionStream);
// A builder where you want to treat the data as FVector3d but you don't know the stored layout (void buffer type).
// This performs a dynamic conversion internally; the formats must be compatible.
// Here FVector3d or FVector3f are both safe, as are custom types with the same layout.
TRealtimeMeshStreamBuilder<FVector3d, void> PositionBuilderDynamic(PositionStream);

A strided builder targets a single element within each row, letting you work with one channel of a multi-channel stream as if it were its own array. The second constructor argument is the element offset (0-based) into the row.

// A texcoords stream with 4 channels, using the packed type FVector2DHalf.
FRealtimeMeshStream TexCoordsStream =
FRealtimeMeshStream::Create<TRealtimeMeshTexCoords<FVector2DHalf, 4>>(FRealtimeMeshStreams::TexCoords);
// A normal builder can still work with all 4 channels at once.
TRealtimeMeshStreamBuilder<TRealtimeMeshTexCoords<FVector2DHalf, 4>> AllChannelsBuilder(TexCoordsStream);
// A strided builder works with a single channel — here element offset 1 (the second channel) — as though it
// were an array of FVector2f, letting the builder do the conversion internally. This supports the same
// no-conversion / direct-conversion / dynamic-conversion variations as a normal builder.
TRealtimeMeshStridedStreamBuilder<FVector2f, FVector2DHalf> SecondChannelBuilder(TexCoordsStream, 1);

// Stream builders behave much like a TArray, with all the usual functions plus some specialized helpers.
// Add an element, returning its index.
int32 Index = PositionBuilder.Add(FVector3f(0, 0, 0));
// Emplace a row from its individual elements (useful for multi-channel streams).
AllChannelsBuilder.Emplace({ FVector2f(0, 0), FVector2f(1, 1), FVector2f(2, 2), FVector2f(3, 3) });
// Reserve capacity up front, preallocating the internal storage.
PositionBuilder.Reserve(100);
// Append an initializer list of elements.
PositionBuilder.Append({ FVector3f(0, 0, 0), FVector3f(1, 1, 1), FVector3f(2, 2, 2) });
// Overwrite the value at an existing index.
PositionBuilder.Set(1, FVector3f(5, 5, 5));
// Remove element 1.
PositionBuilder.RemoveAt(1);
// Set the row count, filling any new rows with the supplied value.
PositionBuilder.SetNumWithFill(128, FVector3f(0, 0, 1));

You can see per-stream builders driving a full mesh in the ARealtimeMeshExample_Simple_StreamBuilders example (Source/RealtimeMeshExamples/Private/Simple/RealtimeMeshExample_Simple_StreamBuilders.cpp).


Runtime-determined layouts: FRealtimeMeshDynamicBuilder

Section titled “Runtime-determined layouts: FRealtimeMeshDynamicBuilder”

The builders above are typed at compile time. When the mesh format isn’t known until runtime — for example, layout driven by Blueprint input, or code that needs to pass a builder by reference into non-templated utility functions — use FRealtimeMeshDynamicBuilder (Interface/Core/RealtimeMeshDynamicBuilder.h) instead.

It is a non-templated builder over a stream set. You decide the format at enable time, and per-row writes pay a small layout-dispatch cost in exchange for that flexibility:

using namespace RealtimeMesh;
FRealtimeMeshStreamSet StreamSet;
// Constructs the Position and Triangles streams up front (FVector3f position, uint16 indices).
FRealtimeMeshDynamicBuilder Builder(StreamSet);
// Enable optional streams, choosing storage types at runtime. The no-argument overloads
// default to FPackedNormal tangents and FVector2DHalf tex coords.
Builder.EnableTangents();
Builder.EnableTexCoords(/* NumChannels */ 1);
Builder.EnableColors();
Builder.EnablePolyGroups(GetRealtimeMeshDataElementType<uint16>());
// Add a vertex and set its attributes by row index.
const int32 V0 = Builder.AddVertex(FVector3f(-50.0f, 0.0f, 0.0f));
Builder.SetTangents(V0, FVector3f(0.0f, -1.0f, 0.0f), FVector3f(1.0f, 0.0f, 0.0f));
Builder.SetColor(V0, FColor::Red);
Builder.SetTexCoord(V0, /* Channel */ 0, FVector2f(0.0f, 0.0f));
// Add a triangle, optionally tagging it with a poly group.
Builder.AddTriangle(V0, V1, V2, /* PolyGroup */ 0);

If your format is known at compile time and you’re in a hot path, prefer the local mesh builder, whose accessors are format-matched and devirtualized.