Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions Schema.Cpp.Test/QuantityCppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,59 @@ public void ADefaultIsConstructedTheWayTheVocabularyAcceptsIt()
AssertCompiles(configured, TargetOptions);
}

/// <summary>
/// The table says what a quantity's bytes are, which for a vector form is not its storage.
/// </summary>
/// <remarks>
/// A magnitude is one number and reports the number. A vector form is two to four of them side
/// by side, and reporting <c>Float</c> for one would be false about the bytes rather than
/// merely vague - false in the way that reads as true, since a consumer asks the representation
/// how many lanes a member has. Told <c>Float</c>, it takes a <c>Velocity3D</c>'s first four
/// bytes for the whole value and checks one component while the other two go unexamined.
/// <para>
/// All five arities the vocabulary has are pinned rather than the two that would carry the
/// argument. Every one of them is a real quantity - 148 magnitudes, 27 signed scalars, and 8,
/// 22 and 7 of two, three and four components - so none of these arms is defensive code, and
/// a reader of this test can see the whole rule instead of inferring it from a sample of it.
/// </para>
/// </remarks>
[TestMethod]
public void TheTableSaysWhatAQuantitysBytesAre()
{
Schema schema = new();
SchemaClass body = schema.AddClass("Body".As<ClassName>())!;

// A magnitude and a signed scalar are both one number, which is why they report the same
// thing from different arities: what the representation answers is the shape of the bytes,
// not how many directions the quantity has.
(string Member, string Quantity, string Representation)[] members =
[
("Mass", "Mass", "Float"),
("Heading", "Heading", "Float"),
("Drift", "Velocity2D", "Vector2"),
("Velocity", "Velocity3D", "Vector3"),
("Worldline", "Velocity4D", "Vector4"),
];

foreach ((string member, string quantity, _) in members)
{
body.AddMember(member.As<MemberName>())!
.SetType(new Quantity { QuantityName = quantity.As<QuantityName>() });
}

string table = Generate(Configured(schema), TargetOptions with { Reflection = true })
.Single(file => file.Key.Contains("reflection", StringComparison.Ordinal)).Value;

// Every one of them is declared a Quantity; what differs is what the bytes are.
Assert.AreEqual(members.Length, table.Split(".kind = TypeKind::Quantity").Length - 1);

foreach ((string member, _, string representation) in members)
{
Assert.Contains($".representation = TypeKind::{representation}", table,
StringComparison.Ordinal, $"{member} should be {representation}");
}
}

/// <summary>
/// A schema of one class holding one quantity.
/// </summary>
Expand Down
31 changes: 28 additions & 3 deletions Schema.Cpp/CppReflection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,38 @@ internal static string Kind(BaseType type) =>
{
Semantic { Declaration: SchemaSemanticType declaration } => Kind(declaration.Representation()),

// A quantity says what it is stored in directly, so there is no chain to walk: a
// Velocity3D over a float is three floats, and the table's reader needs the float.
Quantity quantity => Kind(quantity.Storage),
Quantity quantity => QuantityRepresentation(quantity),

_ => Kind(type),
};

/// <summary>
/// What the bytes of a quantity are.
/// </summary>
/// <remarks>
/// <para>
/// A magnitude or a signed scalar is one number, so it is the number it is stored in. A vector
/// form is two to four of them laid out side by side, which is a <c>Vector2</c>, <c>Vector3</c>
/// or <c>Vector4</c> - and saying <c>Float</c> for one would be false about the bytes, not
/// merely less specific.
/// </para>
/// <para>
/// It is false in a way that reads as true, which is why it is worth spelling out. A consumer
/// walking the table asks the representation how many lanes a member has; told <c>Float</c>, it
/// takes a <c>Velocity3D</c>'s first four bytes for the whole value and checks one component
/// against the member's range while the other two go unexamined. A check that quietly examines
/// a third of what it was given is worse than one that declines.
/// </para>
/// </remarks>
private static string QuantityRepresentation(Quantity quantity) =>
quantity.Resolved?.Components switch
{
2 => Kind(new Vector2()),
3 => Kind(new Vector3()),
4 => Kind(new Vector4()),
_ => Kind(quantity.Storage),
};

/// <summary>
/// How a member's interpolation is named in the table.
/// </summary>
Expand Down