Skip to content

Commit 08d4612

Browse files
committed
[C++] Support "to string" without specification of buffer length.
As we use the generated codecs to create a string representation of our DTOs, we don't use Agrona buffers in C++, and there is no concept of resizing, it is necessary to size a temporary buffer during the construction of the string data. Previously, we were letting the user supply this value, which wasn't a very friendly API. Now, we use the `computeLength` methods on the codec to determine how big of a temporary buffer we need. Perhaps the methods will also be useful for avoiding a buffer copy when used in conjunction with Aeron. For example, a developer could use `dto.computeEncodedLength()` to initialise a buffer claim rather than copying via the `offer(...)` API.
1 parent 99dd27e commit 08d4612

File tree

2 files changed

+143
-18
lines changed

2 files changed

+143
-18
lines changed

sbe-tool/src/main/java/uk/co/real_logic/sbe/generation/cpp/CppDtoGenerator.java

Lines changed: 129 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ public void generate() throws IOException
100100
groups, varData, BASE_INDENT + INDENT);
101101
generateMessageEncodeInto(classBuilder, className, codecClassName, fields, groups, varData,
102102
BASE_INDENT + INDENT);
103-
generateDisplay(classBuilder, codecClassName, "wrapForEncode", null, BASE_INDENT + INDENT);
103+
generateComputeEncodedLength(classBuilder, codecClassName, groups, varData, BASE_INDENT + INDENT);
104+
generateDisplay(classBuilder, className, codecClassName, "dto.computeEncodedLength()",
105+
"wrapForEncode", null, BASE_INDENT + INDENT);
104106

105107
try (Writer out = outputManager.createOutput(className))
106108
{
@@ -227,6 +229,8 @@ private void generateGroups(
227229
fields, groups, varData, indent + INDENT);
228230
generateMessageEncodeInto(
229231
groupClassBuilder, groupClassName, qualifiedCodecClassName, fields, groups, varData, indent + INDENT);
232+
generateComputeEncodedLength(groupClassBuilder, qualifiedCodecClassName, groups, varData,
233+
indent + INDENT);
230234

231235
groupClassBuilder.appendTo(
232236
classBuilder.appendPublic().append("\n").append(generateDocumentation(indent, groupToken))
@@ -266,6 +270,103 @@ private void generateGroups(
266270
}
267271
}
268272

273+
private void generateComputeEncodedLength(
274+
final ClassBuilder classBuilder,
275+
final String qualifiedCodecClassName,
276+
final List<Token> groupTokens,
277+
final List<Token> varDataTokens,
278+
final String indent)
279+
{
280+
final StringBuilder lengthBuilder = classBuilder.appendPublic()
281+
.append("\n")
282+
.append(indent).append("[[nodiscard]] std::size_t computeEncodedLength() const\n")
283+
.append(indent).append("{\n");
284+
285+
final StringBuilder arguments = new StringBuilder();
286+
287+
for (int i = 0, size = groupTokens.size(); i < size; i++)
288+
{
289+
final Token groupToken = groupTokens.get(i);
290+
if (groupToken.signal() != Signal.BEGIN_GROUP)
291+
{
292+
throw new IllegalStateException("tokens must begin with BEGIN_GROUP: token=" + groupToken);
293+
}
294+
295+
i++;
296+
i += groupTokens.get(i).componentTokenCount();
297+
298+
final List<Token> fields = new ArrayList<>();
299+
i = collectFields(groupTokens, i, fields);
300+
final List<Token> subGroups = new ArrayList<>();
301+
i = collectGroups(groupTokens, i, subGroups);
302+
final List<Token> subVarData = new ArrayList<>();
303+
i = collectVarData(groupTokens, i, subVarData);
304+
305+
final String groupName = groupToken.name();
306+
final String fieldName = "m_" + toLowerFirstChar(groupName);
307+
308+
final boolean isConstLength = subGroups.isEmpty() && subVarData.isEmpty();
309+
final String argumentName = isConstLength ?
310+
toLowerFirstChar(groupName) + "Count" :
311+
toLowerFirstChar(groupName) + "Lengths";
312+
313+
if (isConstLength)
314+
{
315+
lengthBuilder
316+
.append(indent).append(INDENT).append("std::size_t ").append(argumentName)
317+
.append(" = ").append(fieldName).append(".size();\n");
318+
}
319+
else
320+
{
321+
final String countName = argumentName + "Count";
322+
323+
lengthBuilder
324+
.append(indent).append(INDENT).append("std::size_t ").append(countName).append(" = ")
325+
.append(fieldName).append(".size();\n")
326+
.append(indent).append(INDENT).append("std::vector<std::tuple<std::size_t>> ")
327+
.append(argumentName).append("(").append(countName).append(");\n")
328+
.append(indent).append(INDENT).append("for (std::size_t i = 0; i < ").append(countName)
329+
.append("; i++)\n")
330+
.append(indent).append(INDENT).append("{\n")
331+
.append(indent).append(INDENT).append(INDENT)
332+
.append("auto& group = ").append(fieldName).append("[i];\n")
333+
.append(indent).append(INDENT).append(INDENT)
334+
.append(argumentName).append("[i] = group.computeEncodedLength();\n")
335+
.append(indent).append(INDENT).append("}\n")
336+
.append("\n");
337+
}
338+
339+
arguments.append(argumentName).append(", ");
340+
}
341+
342+
for (int i = 0, size = varDataTokens.size(); i < size; i++)
343+
{
344+
final Token token = varDataTokens.get(i);
345+
if (token.signal() == Signal.BEGIN_VAR_DATA)
346+
{
347+
final String propertyName = token.name();
348+
final Token varDataToken = Generators.findFirst("varData", varDataTokens, i);
349+
final String fieldName = "m_" + toLowerFirstChar(propertyName);
350+
final String argumentName = toLowerFirstChar(propertyName) + "Length";
351+
352+
lengthBuilder.append(indent).append(INDENT).append("std::size_t ").append(argumentName)
353+
.append(" = ").append(fieldName).append(".size() * sizeof(")
354+
.append(cppTypeName(varDataToken.encoding().primitiveType())).append(");\n");
355+
356+
arguments.append(argumentName).append(", ");
357+
}
358+
}
359+
360+
if (arguments.length() >= 2)
361+
{
362+
arguments.setLength(arguments.length() - 2);
363+
}
364+
365+
lengthBuilder.append(indent).append(INDENT).append("return ").append(qualifiedCodecClassName)
366+
.append("::computeLength(").append(arguments).append(");\n")
367+
.append(indent).append("}\n");
368+
}
369+
269370
private void generateCompositeDecodeFrom(
270371
final ClassBuilder classBuilder,
271372
final String dtoClassName,
@@ -944,8 +1045,8 @@ private void generateGroupsEncodeInto(
9441045
.append("Count(").append(formattedPropertyName).append(".size());\n\n")
9451046
.append(indent).append("for (const auto& group: ").append(formattedPropertyName).append(")\n")
9461047
.append(indent).append("{\n")
947-
.append(indent).append(INDENT).append(groupDtoTypeName).append("::encodeWith(").append(groupCodecVarName)
948-
.append(".next(), group);\n")
1048+
.append(indent).append(INDENT).append(groupDtoTypeName)
1049+
.append("::encodeWith(").append(groupCodecVarName).append(".next(), group);\n")
9491050
.append(indent).append("}\n\n");
9501051

9511052
i++;
@@ -1002,32 +1103,44 @@ private void generateVarDataEncodeInto(
10021103

10031104
private void generateDisplay(
10041105
final ClassBuilder classBuilder,
1106+
final String dtoClassName,
10051107
final String codecClassName,
1108+
final String lengthExpression,
10061109
final String wrapMethod,
10071110
final String actingVersion,
10081111
final String indent)
10091112
{
1010-
final StringBuilder toStringBuilder = classBuilder.appendPublic()
1113+
final StringBuilder streamBuilder = classBuilder.appendPublic()
10111114
.append("\n")
1012-
.append(indent).append(
1013-
"std::string string(char* tempBuffer, std::uint64_t offset, std::uint64_t length) const\n")
1115+
.append(indent).append("friend std::ostream& operator << (std::ostream& stream, const ")
1116+
.append(dtoClassName).append("& dto)\n")
10141117
.append(indent).append("{\n")
10151118
.append(indent).append(INDENT).append(codecClassName).append(" codec;\n")
1016-
.append(indent).append(INDENT).append("codec.");
1017-
1018-
toStringBuilder.append(wrapMethod).append("(tempBuffer, offset");
1119+
.append(indent).append(INDENT).append("const std::size_t length = ")
1120+
.append(lengthExpression).append(";\n")
1121+
.append(indent).append(INDENT).append("std::vector<char> buffer(length);\n")
1122+
.append(indent).append(INDENT).append("codec.").append(wrapMethod)
1123+
.append("(buffer.data(), 0");
10191124

10201125
if (null != actingVersion)
10211126
{
1022-
toStringBuilder.append(", ").append(actingVersion);
1127+
streamBuilder.append(", ").append(actingVersion);
10231128
}
10241129

1025-
toStringBuilder.append(", ").append("length);\n");
1130+
streamBuilder.append(", ").append("length);\n");
1131+
1132+
streamBuilder.append(indent).append(INDENT).append("encodeWith(codec, dto);\n")
1133+
.append(indent).append(INDENT).append("stream << codec;\n")
1134+
.append(indent).append(INDENT).append("return stream;\n")
1135+
.append(indent).append("}\n");
10261136

1027-
toStringBuilder.append(indent).append(INDENT).append("encodeWith(codec, *this);\n")
1028-
.append(indent).append(INDENT).append("std::ostringstream oss;\n")
1029-
.append(indent).append(INDENT).append("oss << codec;\n")
1030-
.append(indent).append(INDENT).append("return oss.str();\n")
1137+
classBuilder.appendPublic()
1138+
.append("\n")
1139+
.append(indent).append("[[nodiscard]] std::string string() const\n")
1140+
.append(indent).append("{\n")
1141+
.append(indent).append(INDENT).append("std::ostringstream stream;\n")
1142+
.append(indent).append(INDENT).append("stream << *this;\n")
1143+
.append(indent).append(INDENT).append("return stream.str();\n")
10311144
.append(indent).append("}\n");
10321145
}
10331146

@@ -1547,7 +1660,7 @@ private void generateComposite(final List<Token> tokens) throws IOException
15471660
generateCompositeDecodeFrom(classBuilder, className, codecClassName, compositeTokens,
15481661
BASE_INDENT + INDENT);
15491662
generateCompositeEncodeInto(classBuilder, className, codecClassName, compositeTokens, BASE_INDENT + INDENT);
1550-
generateDisplay(classBuilder, codecClassName, "wrap",
1663+
generateDisplay(classBuilder, className, codecClassName, codecClassName + "::encodedLength()", "wrap",
15511664
codecClassName + "::sbeSchemaVersion()", BASE_INDENT + INDENT);
15521665

15531666
classBuilder.appendTo(out);

sbe-tool/src/test/cpp/DtoTest.cpp

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ TEST_F(DtoTest, shouldRoundTripCar)
174174
ExtendedCar encoder1;
175175
encoder1.wrapForEncode(input, 0, BUFFER_LEN);
176176
const std::uint64_t encodedCarLength = encodeCar(encoder1);
177+
177178
ExtendedCar decoder;
178179
decoder.wrapForDecode(
179180
input,
@@ -182,14 +183,25 @@ TEST_F(DtoTest, shouldRoundTripCar)
182183
ExtendedCar::sbeSchemaVersion(),
183184
encodedCarLength);
184185
ExtendedCarDto dto;
185-
ExtendedCarDto::decode(decoder, dto);
186+
ExtendedCarDto::decodeWith(decoder, dto);
187+
186188
char output[BUFFER_LEN];
187189
std::memset(output, 0, BUFFER_LEN);
188190
ExtendedCar encoder2;
189191
encoder2.wrapForEncode(output, 0, BUFFER_LEN);
190-
ExtendedCarDto::encode(encoder2, dto);
192+
ExtendedCarDto::encodeWith(encoder2, dto);
191193
const std::uint64_t encodedCarLength2 = encoder2.encodedLength();
192194

195+
decoder.sbeRewind();
196+
std::ostringstream originalStringStream;
197+
originalStringStream << decoder;
198+
std::string originalString = originalStringStream.str();
199+
200+
std::ostringstream dtoStringStream;
201+
dtoStringStream << dto;
202+
std::string dtoString = dtoStringStream.str();
203+
193204
EXPECT_EQ(encodedCarLength, encodedCarLength2);
194205
EXPECT_EQ(0, std::memcmp(input, output, encodedCarLength2));
206+
EXPECT_EQ(originalString, dtoString);
195207
}

0 commit comments

Comments
 (0)