Skip to content

Commit

Permalink
[C#] Generate DTOs from SBE IR for non-perf-sensitive usecases.
Browse files Browse the repository at this point in the history
In some applications performance is not cricital. Some users would like
to use SBE across their whole "estate", but don't want the "sharp edges"
associated with using flyweight codecs, e.g., accidental escape.

In this commit, I've added a first cut of DTO generation for C# and a
simple test based on the Car Example.

The DTOs support encoding and decoding via the generated codecs using
`EncodeInto(CodecT codec)` and `DecodeFrom(CodecT codec)` methods.

Currently there is no support for equality/comparison or read-only views
over the data; although, these have been requested.

Here are some points that we may or may not wish to change in the
future:

1. Non-present (due to the encoded version) string/array data and
repeating groups are represented as `null` rather than empty.

2. Non-present primitive values are represented as their associated null
value rather than using nullable types.

3. Non-present bitsets are represented as `0`.

4. DTOs are generated via a separate `CodeGenerator` rather than a flag
to the existing C# `CodeGenerator`.
  • Loading branch information
ZachBray committed Sep 27, 2023
1 parent 510d3e8 commit 8d3b4c5
Show file tree
Hide file tree
Showing 7 changed files with 1,352 additions and 116 deletions.
16 changes: 14 additions & 2 deletions build.gradle
Expand Up @@ -723,7 +723,7 @@ tasks.register('generateCSharpCodecsWithXIncludes', JavaExec) {
'sbe-samples/src/main/resources/example-extension-schema.xml']
}

tasks.register('generateCSharpCodecsTests', JavaExec) {
tasks.register('generateCSharpTestCodecs', JavaExec) {
mainClass.set('uk.co.real_logic.sbe.SbeTool')
classpath = project(':sbe-tool').sourceSets.main.runtimeClasspath
systemProperties(
Expand All @@ -740,9 +740,21 @@ tasks.register('generateCSharpCodecsTests', JavaExec) {
'sbe-benchmarks/src/main/resources/fix-message-samples.xml']
}

tasks.register('generateCSharpTestDtos', JavaExec) {
mainClass.set('uk.co.real_logic.sbe.SbeTool')
classpath = project(':sbe-tool').sourceSets.main.runtimeClasspath
systemProperties(
'sbe.output.dir': 'csharp/sbe-generated',
'sbe.target.language': 'uk.co.real_logic.sbe.generation.csharp.CSharpDtos',
'sbe.xinclude.aware': 'true',
'sbe.validation.xsd': validationXsdPath)
args = ['sbe-samples/src/main/resources/example-extension-schema.xml']
}

tasks.register('generateCSharpCodecs') {
description = 'Generate csharp codecs'
dependsOn 'generateCSharpCodecsTests',
dependsOn 'generateCSharpTestCodecs',
'generateCSharpTestDtos',
'generateCSharpCodecsWithXIncludes'
}

Expand Down
7 changes: 1 addition & 6 deletions csharp/sbe-dll/DirectBuffer.cs
Expand Up @@ -694,21 +694,16 @@ public int SetBytes(int index, ReadOnlySpan<byte> src)

/// <summary>
/// Writes a string into the underlying buffer, encoding using the provided <see cref="System.Text.Encoding"/>.
/// If there is not enough room in the buffer for the bytes it will throw IndexOutOfRangeException.
/// </summary>
/// <param name="encoding">encoding to use to write the bytes from the string</param>
/// <param name="src">source string</param>
/// <param name="index">index in the underlying buffer to start writing bytes</param>
/// <returns>count of bytes written</returns>
public unsafe int SetBytesFromString(Encoding encoding, string src, int index)
{
int available = _capacity - index;
int byteCount = encoding.GetByteCount(src);

if (byteCount > available)
{
ThrowHelper.ThrowIndexOutOfRangeException(_capacity);
}
CheckLimit(index + byteCount);

fixed (char* ptr = src)
{
Expand Down
110 changes: 110 additions & 0 deletions csharp/sbe-tests/DtoTests.cs
@@ -0,0 +1,110 @@
using Extension;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Org.SbeTool.Sbe.Dll;

namespace Org.SbeTool.Sbe.Tests
{
[TestClass]
public class DtoTests
{
[TestMethod]
public void ShouldRoundTripCar()
{
var inputByteArray = new byte[1024];
var inputBuffer = new DirectBuffer(inputByteArray);
EncodeCar(inputBuffer);
var decoder = new Car();
decoder.WrapForDecode(inputBuffer, 0, Car.BlockLength, Car.SchemaVersion);
var decoderString = decoder.ToString();
var dto = new CarDto();
dto.DecodeFrom(decoder);
var outputByteArray = new byte[1024];
var outputBuffer = new DirectBuffer(outputByteArray);
var encoder = new Car();
encoder.WrapForEncode(outputBuffer, 0);
dto.EncodeInto(encoder);
var dtoString = dto.ToString();
CollectionAssert.AreEqual(inputByteArray, outputByteArray);
Assert.AreEqual(decoderString, dtoString);
}

private static void EncodeCar(DirectBuffer buffer)
{
var car = new Car();
car.WrapForEncode(buffer, 0);
car.SerialNumber = 1234;
car.ModelYear = 2013;
car.Available = BooleanType.T;
car.Code = Model.A;
car.SetVehicleCode("ABCDEF");

for (int i = 0, size = Car.SomeNumbersLength; i < size; i++)
{
car.SetSomeNumbers(i, (uint)i);
}

car.Extras = OptionalExtras.CruiseControl | OptionalExtras.SportsPack;

car.CupHolderCount = 119;

car.Engine.Capacity = 2000;
car.Engine.NumCylinders = 4;
car.Engine.SetManufacturerCode("ABC");
car.Engine.Efficiency = 35;
car.Engine.BoosterEnabled = BooleanType.T;
car.Engine.Booster.BoostType = BoostType.NITROUS;
car.Engine.Booster.HorsePower = 200;

var fuelFigures = car.FuelFiguresCount(3);
fuelFigures.Next();
fuelFigures.Speed = 30;
fuelFigures.Mpg = 35.9f;
fuelFigures.SetUsageDescription("this is a description");

fuelFigures.Next();
fuelFigures.Speed = 55;
fuelFigures.Mpg = 49.0f;
fuelFigures.SetUsageDescription("this is a description");

fuelFigures.Next();
fuelFigures.Speed = 75;
fuelFigures.Mpg = 40.0f;
fuelFigures.SetUsageDescription("this is a description");

Car.PerformanceFiguresGroup perfFigures = car.PerformanceFiguresCount(2);
perfFigures.Next();
perfFigures.OctaneRating = 95;

Car.PerformanceFiguresGroup.AccelerationGroup acceleration = perfFigures.AccelerationCount(3).Next();
acceleration.Mph = 30;
acceleration.Seconds = 4.0f;

acceleration.Next();
acceleration.Mph = 60;
acceleration.Seconds = 7.5f;

acceleration.Next();
acceleration.Mph = 100;
acceleration.Seconds = 12.2f;

perfFigures.Next();
perfFigures.OctaneRating = 99;
acceleration = perfFigures.AccelerationCount(3).Next();

acceleration.Mph = 30;
acceleration.Seconds = 3.8f;

acceleration.Next();
acceleration.Mph = 60;
acceleration.Seconds = 7.1f;

acceleration.Next();
acceleration.Mph = 100;
acceleration.Seconds = 11.8f;

car.SetManufacturer("Ford");
car.SetModel("Fiesta");
car.SetActivationCode("1234");
}
}
}

0 comments on commit 8d3b4c5

Please sign in to comment.