Skip to content
Draft
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
54 changes: 30 additions & 24 deletions crates/bindings-csharp/BSATN.Codegen/Type.cs
Original file line number Diff line number Diff line change
Expand Up @@ -624,21 +624,38 @@

if (Kind is TypeKind.Sum)
{
extensions.BaseTypes.Add("SpacetimeDB.BSATN.IStructuralWrite");
extensions.ExtraAttrs.Add("abstract");

extensions.Contents.Append(
$$"""
private {{ShortNameIdentifier}}() { }

"""
);

extensions.Contents.Append(
string.Join(
"\n",
Members.Select(m =>
// C# puts field names in the same namespace as records themselves, and will complain about clashes if they match.
// To avoid this, we append an underscore to the field name.
// In most cases the field name shouldn't matter anyway as you'll idiomatically use pattern matching to extract the value.
$$"""
public sealed record {{m.Identifier}}({{m.Type.Name}} {{m.Identifier}}_) : {{ShortNameIdentifier}}
{
public override string ToString() =>
$"{{m.Name}}({ SpacetimeDB.BSATN.StringUtil.GenericToString({{m.Identifier}}_) })";
}

"""
Members.Select(
(m, i) =>
// C# puts field names in the same namespace as records themselves, and will complain about clashes if they match.
// To avoid this, we append an underscore to the field name.
// In most cases the field name shouldn't matter anyway as you'll idiomatically use pattern matching to extract the value.
$$"""
public sealed record {{m.Identifier}}({{m.Type.Name}} {{m.Identifier}}_) : {{ShortNameIdentifier}}
{
public override void WriteFields(System.IO.BinaryWriter writer)
{
writer.Write((byte){{i}});
BSATN.{{m.Identifier}}{{TypeUse.BsatnFieldSuffix}}.Write(writer, {{m.Identifier}}_);
}

public override string ToString() =>
$"{{m.Name}}({ SpacetimeDB.BSATN.StringUtil.GenericToString({{m.Identifier}}_) })";
}

"""
)
)
);
Expand All @@ -655,18 +672,7 @@
};
""";

write = $$"""
switch (value) {
{{string.Join(
"\n",
bsatnDecls.Select((m, i) => $"""
case {m.Identifier}(var inner):
writer.Write((byte){i});
{m.Identifier}{TypeUse.BsatnFieldSuffix}.Write(writer, inner);
break;
"""))}}
}
""";
write = "value.WriteFields(writer);";

getHashCode = $$"""
switch (this) {
Expand Down Expand Up @@ -734,7 +740,7 @@
// Using simple code here hopefully helps IL2CPP and Mono do this faster.
read = $$"""
var ___result = new {{FullName}}();
___result.ReadFields(reader);

Check warning on line 743 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / release-csharp

Use local function (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0039)
return ___result;
""";

Expand Down Expand Up @@ -783,7 +789,7 @@

"""
);

Check warning on line 792 in crates/bindings-csharp/BSATN.Codegen/Type.cs

View workflow job for this annotation

GitHub Actions / release-csharp

Use local function (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0039)
if (!Scope.IsRecord)
{
// If we are a reference type, various equality methods need to take nullable references.
Expand Down
9 changes: 7 additions & 2 deletions crates/bindings-csharp/BSATN.Runtime/Attrs.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
namespace SpacetimeDB;

using System.IO;
using System.Runtime.CompilerServices;
using SpacetimeDB.BSATN;

[AttributeUsage(
AttributeTargets.Struct | AttributeTargets.Class | AttributeTargets.Enum,
Expand All @@ -11,5 +13,8 @@ public sealed class TypeAttribute : Attribute { }

// This could be an interface, but using `record` forces C# to check that it can
// only be applied on types that are records themselves.
public abstract record TaggedEnum<Variants>
where Variants : struct, ITuple { }
public abstract record TaggedEnum<Variants> : IStructuralWrite
where Variants : struct, ITuple
{
public abstract void WriteFields(BinaryWriter writer);
}
65 changes: 39 additions & 26 deletions crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,41 @@
/// Implemented by product types marked with [SpacetimeDB.Type].
/// All rows in SpacetimeDB are product types, so this is also implemented by all row types.
/// </summary>
public interface IStructuralReadWrite
public interface IStructuralWrite
{
/// <summary>
/// Write the fields of this type to the writer.
/// Throws an exception if the underlying writer throws.
/// Throws if this value is malformed (i.e. has null values for fields that
/// are not explicitly marked nullable.)
/// </summary>
/// <param name="writer"></param>
void WriteFields(BinaryWriter writer);

public static byte[] ToBytes<RW, T>(RW rw, T value)
where RW : IReadWrite<T>
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream);
rw.Write(writer, value);
return stream.ToArray();
}

public static byte[] ToBytes<T>(T value)
where T : IStructuralWrite
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream);
value.WriteFields(writer);
return stream.ToArray();
}
}

/// <summary>
/// Implemented by product types marked with [SpacetimeDB.Type] that can be read from BSATN.
/// All rows in SpacetimeDB are product types, so this is also implemented by all row types.
/// </summary>
public interface IStructuralReadWrite : IStructuralWrite
{
/// <summary>
/// Initialize this value from the reader.
Expand All @@ -18,15 +52,6 @@
/// <param name="reader"></param>
void ReadFields(BinaryReader reader);

/// <summary>
/// Write the fields of this type to the writer.
/// Throws an exception if the underlying writer throws.
/// Throws if this value is malformed (i.e. has null values for fields that
/// are not explicitly marked nullable.)
/// </summary>
/// <param name="writer"></param>
void WriteFields(BinaryWriter writer);

/// <summary>
/// Get an IReadWrite implementation that can read values of this type.
/// In Rust, this would return <c>IReadWrite&lt;Self&gt;</c>, but the C# type system
Expand Down Expand Up @@ -67,23 +92,11 @@
return result;
}

public static byte[] ToBytes<RW, T>(RW rw, T value)
where RW : IReadWrite<T>
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream);
rw.Write(writer, value);
return stream.ToArray();
}
public static new byte[] ToBytes<RW, T>(RW rw, T value)
where RW : IReadWrite<T> => IStructuralWrite.ToBytes(rw, value);

public static byte[] ToBytes<T>(T value)
where T : IStructuralReadWrite
{
using var stream = new MemoryStream();
using var writer = new BinaryWriter(stream);
value.WriteFields(writer);
return stream.ToArray();
}
public static new byte[] ToBytes<T>(T value)
where T : IStructuralReadWrite => IStructuralWrite.ToBytes(value);
}

/// <summary>
Expand Down Expand Up @@ -116,7 +129,7 @@
/// <returns></returns>
AlgebraicType GetAlgebraicType(ITypeRegistrar registrar);
}

Check warning on line 132 in crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs

View workflow job for this annotation

GitHub Actions / release-csharp

Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305)
/// <summary>
/// Serializer for enums.
/// </summary>
Expand Down Expand Up @@ -166,7 +179,7 @@
if (tag < TagToValue.Length)
{
writer.Write(tag);
}

Check warning on line 182 in crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs

View workflow job for this annotation

GitHub Actions / release-csharp

Collection initialization can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0305)
else
{
throw new ArgumentOutOfRangeException(
Expand Down
66 changes: 41 additions & 25 deletions crates/bindings-csharp/BSATN.Runtime/Builtins.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,10 @@
// See `spacetimedb-sats::AlgebraicType::is_valid_for_client_type_[use|generate]` for more information.
// We don't use [Type] here; instead we manually implement the serialization stuff that would be generated by
// [Type] so that we can override GetAlgebraicType to return types in a special, Ref-less form.
public readonly partial struct Unit
public readonly partial struct Unit : IStructuralWrite
{
public void WriteFields(BinaryWriter writer) { }

public readonly struct BSATN : IReadWrite<Unit>
{
public Unit Read(BinaryReader reader) => default;
Expand All @@ -124,7 +126,8 @@

[StructLayout(LayoutKind.Sequential)]
public readonly record struct ConnectionId
: IEquatable<ConnectionId>,
: IStructuralWrite,
IEquatable<ConnectionId>,
IComparable,
IComparable<ConnectionId>
{
Expand Down Expand Up @@ -192,14 +195,16 @@
return id;
}

public void WriteFields(BinaryWriter writer) =>
new SpacetimeDB.BSATN.U128Stdb().Write(writer, value);

// --- auto-generated ---
public readonly struct BSATN : IReadWrite<ConnectionId>
{
public ConnectionId Read(BinaryReader reader) =>
new(new SpacetimeDB.BSATN.U128Stdb().Read(reader));

public void Write(BinaryWriter writer, ConnectionId value) =>
new SpacetimeDB.BSATN.U128Stdb().Write(writer, value.value);
public void Write(BinaryWriter writer, ConnectionId value) => value.WriteFields(writer);

// --- / auto-generated ---

Expand Down Expand Up @@ -239,7 +244,11 @@
}

[StructLayout(LayoutKind.Sequential)]
public readonly record struct Identity : IEquatable<Identity>, IComparable, IComparable<Identity>
public readonly record struct Identity
: IStructuralWrite,
IEquatable<Identity>,
IComparable,
IComparable<Identity>
{
private readonly U256 value;

Expand Down Expand Up @@ -296,13 +305,15 @@
return FromBigEndian(Util.StringToByteArray(hex));
}

public void WriteFields(BinaryWriter writer) =>
new SpacetimeDB.BSATN.U256().Write(writer, value);

// --- auto-generated ---
public readonly struct BSATN : IReadWrite<Identity>
{
public Identity Read(BinaryReader reader) => new(new SpacetimeDB.BSATN.U256().Read(reader));

public void Write(BinaryWriter writer, Identity value) =>
new SpacetimeDB.BSATN.U256().Write(writer, value.value);
public void Write(BinaryWriter writer, Identity value) => value.WriteFields(writer);

// --- / auto-generated ---

Expand Down Expand Up @@ -346,7 +357,7 @@
/// A timestamp that represents a unique moment in time (in the Earth's reference frame).
///
/// This type may be converted to/from a DateTimeOffset, but the conversion can lose precision.
/// This type has less precision than DateTimeOffset (units of microseconds rather than units of 100ns).

Check warning on line 360 in crates/bindings-csharp/BSATN.Runtime/Builtins.cs

View workflow job for this annotation

GitHub Actions / release-csharp

'new' expression can be simplified (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide0090)
/// </summary>
[StructLayout(LayoutKind.Sequential)] // we should be able to use it in FFI
public record struct Timestamp(long MicrosecondsSinceUnixEpoch)
Expand Down Expand Up @@ -549,7 +560,7 @@
}
}

public partial record ScheduleAt : TaggedEnum<(TimeDuration Interval, Timestamp Time)>
public abstract partial record ScheduleAt : TaggedEnum<(TimeDuration Interval, Timestamp Time)>
{
public static implicit operator ScheduleAt(TimeDuration duration) => new Interval(duration);

Expand All @@ -563,7 +574,7 @@

public static TimeSpan TimeSpanFromMicroseconds(long intervalMicros) =>
(TimeSpan)(new TimeDuration(intervalMicros));

Check warning on line 577 in crates/bindings-csharp/BSATN.Runtime/Builtins.cs

View workflow job for this annotation

GitHub Actions / release-csharp

Naming rule violation: These words must begin with upper case characters: enum (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)
public static long ToMicrosecondsSinceUnixEpoch(DateTimeOffset time) =>
((Timestamp)time).MicrosecondsSinceUnixEpoch;

Expand All @@ -580,9 +591,23 @@
Time,
}

public sealed record Interval(TimeDuration Interval_) : ScheduleAt;
public sealed record Interval(TimeDuration Interval_) : ScheduleAt
{
public override void WriteFields(BinaryWriter writer)
{
BSATN.__enumTag.Write(writer, ScheduleAtVariant.Interval);
BSATN.Interval.Write(writer, Interval_);
}
}

public sealed record Time(Timestamp Time_) : ScheduleAt;
public sealed record Time(Timestamp Time_) : ScheduleAt
{
public override void WriteFields(BinaryWriter writer)
{
BSATN.__enumTag.Write(writer, ScheduleAtVariant.Time);
BSATN.Time.Write(writer, Time_);
}
}

public readonly partial struct BSATN : IReadWrite<ScheduleAt>
{
Expand All @@ -600,21 +625,7 @@
),
};

public void Write(BinaryWriter writer, ScheduleAt value)
{
switch (value)
{
case Interval(var inner):
__enumTag.Write(writer, ScheduleAtVariant.Interval);
Interval.Write(writer, inner);
break;

case Time(var inner):
__enumTag.Write(writer, ScheduleAtVariant.Time);
Time.Write(writer, inner);
break;
}
}
public void Write(BinaryWriter writer, ScheduleAt value) => value.WriteFields(writer);

// --- / auto-generated ---

Expand Down Expand Up @@ -671,7 +682,7 @@
};

public T UnwrapOrElse(Func<E, T> f) =>
this switch

Check warning on line 685 in crates/bindings-csharp/BSATN.Runtime/Builtins.cs

View workflow job for this annotation

GitHub Actions / release-csharp

Naming rule violation: These words must begin with upper case characters: enum (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide1006)
{
OkR(var v) => v,
ErrR(var e) => f(e),
Expand All @@ -682,6 +693,11 @@

private Result() { }

public override void WriteFields(BinaryWriter writer) =>
throw new NotSupportedException(
"Result requires a typed BSATN serializer to write its generic variants."
);

internal enum ResultVariant : byte
{
Ok,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,32 @@
// <auto-generated />
#nullable enable

partial record CustomTaggedEnum : System.IEquatable<CustomTaggedEnum>
abstract partial record CustomTaggedEnum
: System.IEquatable<CustomTaggedEnum>,
SpacetimeDB.BSATN.IStructuralWrite
{
private CustomTaggedEnum() { }

public sealed record IntVariant(int IntVariant_) : CustomTaggedEnum
{
public override void WriteFields(System.IO.BinaryWriter writer)
{
writer.Write((byte)0);
BSATN.IntVariantRW.Write(writer, IntVariant_);
}

public override string ToString() =>
$"IntVariant({SpacetimeDB.BSATN.StringUtil.GenericToString(IntVariant_)})";
}

public sealed record StringVariant(string StringVariant_) : CustomTaggedEnum
{
public override void WriteFields(System.IO.BinaryWriter writer)
{
writer.Write((byte)1);
BSATN.StringVariantRW.Write(writer, StringVariant_);
}

public override string ToString() =>
$"StringVariant({SpacetimeDB.BSATN.StringUtil.GenericToString(StringVariant_)})";
}
Expand All @@ -36,17 +52,7 @@ public CustomTaggedEnum Read(System.IO.BinaryReader reader)

public void Write(System.IO.BinaryWriter writer, CustomTaggedEnum value)
{
switch (value)
{
case IntVariant(var inner):
writer.Write((byte)0);
IntVariantRW.Write(writer, inner);
break;
case StringVariant(var inner):
writer.Write((byte)1);
StringVariantRW.Write(writer, inner);
break;
}
value.WriteFields(writer);
}

public SpacetimeDB.BSATN.AlgebraicType.Ref GetAlgebraicType(
Expand Down
Loading
Loading