diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5d2513904f1..038334613ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1047,7 +1047,8 @@ jobs: run: dotnet restore --configfile ../../../NuGet.Config blackholio.csproj - name: Build Godot project - run: godot --headless --verbose --path demo/Blackholio/client-godot --build-solutions --quit + working-directory: demo/Blackholio/client-godot + run: godot --headless --verbose --build-solutions --quit - name: Start SpacetimeDB run: | @@ -1061,7 +1062,8 @@ jobs: bash ./publish.sh - name: Run Godot tests - run: godot --headless --path demo/Blackholio/client-godot --scene res://tests/GodotPlayModeTests.tscn + working-directory: demo/Blackholio/client-godot + run: godot --headless --scene res://tests/GodotPlayModeTests.tscn csharp-testsuite: needs: [merge_queue_noop, lints] diff --git a/crates/bindings-csharp/BSATN.Codegen/Type.cs b/crates/bindings-csharp/BSATN.Codegen/Type.cs index c639b6db8ae..537c1c9f2dc 100644 --- a/crates/bindings-csharp/BSATN.Codegen/Type.cs +++ b/crates/bindings-csharp/BSATN.Codegen/Type.cs @@ -740,13 +740,13 @@ public override string ToString() => write = "value.WriteFields(writer);"; - var declHashName = (MemberDeclaration decl) => $"___hash{decl.Name}"; + static string DeclHashName(MemberDeclaration decl) => $"___hash{decl.Name}"; getHashCode = $$""" - {{string.Join("\n", bsatnDecls.Select(decl => decl.Type.GetHashCodeStatement(decl.Identifier, declHashName(decl))))}} + {{string.Join("\n", bsatnDecls.Select(decl => decl.Type.GetHashCodeStatement(decl.Identifier, DeclHashName(decl))))}} return {{JoinOrValue( " ^\n ", - bsatnDecls.Select(declHashName), + bsatnDecls.Select(DeclHashName), "0" // if there are no members, the hash is 0. )}}; """; @@ -789,7 +789,7 @@ public override int GetHashCode() // If we are a reference type, various equality methods need to take nullable references. // If we are a value type, everything is pleasantly by-value. var fullNameMaybeRef = $"{FullName}{(Scope.IsStruct ? "" : "?")}"; - var declEqualsName = (MemberDeclaration decl) => $"___eq{decl.Name}"; + static string DeclEqualsName(MemberDeclaration decl) => $"___eq{decl.Name}"; extensions.Contents.Append( $$""" @@ -798,10 +798,10 @@ public override int GetHashCode() public bool Equals({{fullNameMaybeRef}} that) { {{(Scope.IsStruct ? "" : "if (((object?)that) == null) { return false; }\n ")}} - {{string.Join("\n", bsatnDecls.Select(decl => decl.Type.EqualsStatement($"this.{decl.Identifier}", $"that.{decl.Identifier}", declEqualsName(decl))))}} + {{string.Join("\n", bsatnDecls.Select(decl => decl.Type.EqualsStatement($"this.{decl.Identifier}", $"that.{decl.Identifier}", DeclEqualsName(decl))))}} return {{JoinOrValue( " &&\n ", - bsatnDecls.Select(declEqualsName), + bsatnDecls.Select(DeclEqualsName), "true" // if there are no elements, the structs are equal :) )}}; } diff --git a/crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs b/crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs index eeba596929b..e7519e22765 100644 --- a/crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs +++ b/crates/bindings-csharp/BSATN.Runtime/BSATN/Runtime.cs @@ -129,7 +129,9 @@ public interface IReadWrite /// Note: the [Type] macro rejects enums with explicitly set values (see Codegen.Tests), /// so this array is guaranteed to be continuous and indexed starting from 0. /// +#pragma warning disable CA2263, IDE0305 // netstandard2.1 lacks the generic Enum overloads. private static readonly T[] TagToValue = Enum.GetValues(typeof(T)).Cast().ToArray(); +#pragma warning restore CA2263, IDE0305 public T Read(BinaryReader reader) { @@ -177,9 +179,12 @@ public AlgebraicType GetAlgebraicType(ITypeRegistrar registrar) => registrar.RegisterType( (_) => new AlgebraicType.Sum( - Enum.GetNames(typeof(T)) - .Select(name => new AggregateElement(name, AlgebraicType.Unit)) - .ToArray() +#pragma warning disable CA2263 // netstandard2.1 lacks the generic Enum overloads. + [ + .. Enum.GetNames(typeof(T)) + .Select(name => new AggregateElement(name, AlgebraicType.Unit)), + ] +#pragma warning restore CA2263 ) ); } diff --git a/crates/bindings-csharp/BSATN.Runtime/BSATN/U128.cs b/crates/bindings-csharp/BSATN.Runtime/BSATN/U128.cs index 936e4756d1b..5158d9fc213 100644 --- a/crates/bindings-csharp/BSATN.Runtime/BSATN/U128.cs +++ b/crates/bindings-csharp/BSATN.Runtime/BSATN/U128.cs @@ -45,8 +45,8 @@ public static U128 FromBytesBigEndian(ReadOnlySpan bytes) ); } - var upper = BinaryPrimitives.ReadUInt64BigEndian(bytes.Slice(0, 8)); - var lower = BinaryPrimitives.ReadUInt64BigEndian(bytes.Slice(8, 8)); + var upper = BinaryPrimitives.ReadUInt64BigEndian(bytes[..8]); + var lower = BinaryPrimitives.ReadUInt64BigEndian(bytes[8..16]); return new U128(upper, lower); } diff --git a/crates/bindings-csharp/BSATN.Runtime/Builtins.cs b/crates/bindings-csharp/BSATN.Runtime/Builtins.cs index e204985b54a..f8cb375f284 100644 --- a/crates/bindings-csharp/BSATN.Runtime/Builtins.cs +++ b/crates/bindings-csharp/BSATN.Runtime/Builtins.cs @@ -357,7 +357,7 @@ public static implicit operator DateTimeOffset(Timestamp t) => DateTimeOffset.UnixEpoch.AddTicks(t.MicrosecondsSinceUnixEpoch * Util.TicksPerMicrosecond); public static implicit operator Timestamp(DateTimeOffset offset) => - new Timestamp(offset.Subtract(DateTimeOffset.UnixEpoch).Ticks / Util.TicksPerMicrosecond); + new(offset.Subtract(DateTimeOffset.UnixEpoch).Ticks / Util.TicksPerMicrosecond); // For backwards-compatibility. public readonly DateTimeOffset ToStd() => this; @@ -373,7 +373,7 @@ public override readonly string ToString() public static readonly Timestamp UNIX_EPOCH = new(0); public static Timestamp FromTimeDurationSinceUnixEpoch(TimeDuration timeDuration) => - new Timestamp(timeDuration.Microseconds); + new(timeDuration.Microseconds); public readonly TimeDuration ToTimeDurationSinceUnixEpoch() => TimeDurationSince(UNIX_EPOCH); @@ -383,13 +383,13 @@ public static Timestamp FromTimeSpanSinceUnixEpoch(TimeSpan timeSpan) => public readonly TimeSpan ToTimeSpanSinceUnixEpoch() => (TimeSpan)ToTimeDurationSinceUnixEpoch(); public readonly TimeDuration TimeDurationSince(Timestamp earlier) => - new TimeDuration(checked(MicrosecondsSinceUnixEpoch - earlier.MicrosecondsSinceUnixEpoch)); + new(checked(MicrosecondsSinceUnixEpoch - earlier.MicrosecondsSinceUnixEpoch)); public static Timestamp operator +(Timestamp point, TimeDuration interval) => - new Timestamp(checked(point.MicrosecondsSinceUnixEpoch + interval.Microseconds)); + new(checked(point.MicrosecondsSinceUnixEpoch + interval.Microseconds)); public static Timestamp operator -(Timestamp point, TimeDuration interval) => - new Timestamp(checked(point.MicrosecondsSinceUnixEpoch - interval.Microseconds)); + new(checked(point.MicrosecondsSinceUnixEpoch - interval.Microseconds)); public readonly int CompareTo(Timestamp that) { @@ -492,10 +492,10 @@ public static implicit operator TimeDuration(TimeSpan timeSpan) => new(timeSpan.Ticks / Util.TicksPerMicrosecond); public static TimeDuration operator +(TimeDuration lhs, TimeDuration rhs) => - new TimeDuration(checked(lhs.Microseconds + rhs.Microseconds)); + new(checked(lhs.Microseconds + rhs.Microseconds)); public static TimeDuration operator -(TimeDuration lhs, TimeDuration rhs) => - new TimeDuration(checked(lhs.Microseconds + rhs.Microseconds)); + new(checked(lhs.Microseconds + rhs.Microseconds)); // For backwards-compatibility. public readonly TimeSpan ToStd() => this; @@ -574,7 +574,7 @@ long microsSinceUnixEpoch // --- auto-generated --- private ScheduleAt() { } - internal enum @enum : byte + internal enum ScheduleAtVariant : byte { Interval, Time, @@ -586,15 +586,15 @@ public sealed record Time(Timestamp Time_) : ScheduleAt; public readonly partial struct BSATN : IReadWrite { - internal static readonly SpacetimeDB.BSATN.Enum<@enum> __enumTag = new(); + internal static readonly SpacetimeDB.BSATN.Enum __enumTag = new(); internal static readonly TimeDuration.BSATN Interval = new(); internal static readonly Timestamp.BSATN Time = new(); public ScheduleAt Read(BinaryReader reader) => __enumTag.Read(reader) switch { - @enum.Interval => new Interval(Interval.Read(reader)), - @enum.Time => new Time(Time.Read(reader)), + ScheduleAtVariant.Interval => new Interval(Interval.Read(reader)), + ScheduleAtVariant.Time => new Time(Time.Read(reader)), _ => throw new InvalidOperationException( "Invalid tag value, this state should be unreachable." ), @@ -605,12 +605,12 @@ public void Write(BinaryWriter writer, ScheduleAt value) switch (value) { case Interval(var inner): - __enumTag.Write(writer, @enum.Interval); + __enumTag.Write(writer, ScheduleAtVariant.Interval); Interval.Write(writer, inner); break; case Time(var inner): - __enumTag.Write(writer, @enum.Time); + __enumTag.Write(writer, ScheduleAtVariant.Time); Time.Write(writer, inner); break; } @@ -682,7 +682,7 @@ public T UnwrapOrElse(Func f) => private Result() { } - internal enum @enum : byte + internal enum ResultVariant : byte { Ok, Err, @@ -702,15 +702,15 @@ private enum Variant : byte where OkRW : struct, IReadWrite where ErrRW : struct, IReadWrite { - private static readonly SpacetimeDB.BSATN.Enum<@enum> __enumTag = new(); + private static readonly SpacetimeDB.BSATN.Enum __enumTag = new(); private static readonly OkRW okRW = new(); private static readonly ErrRW errRW = new(); public Result Read(BinaryReader reader) => __enumTag.Read(reader) switch { - @enum.Ok => new OkR(okRW.Read(reader)), - @enum.Err => new ErrR(errRW.Read(reader)), + ResultVariant.Ok => new OkR(okRW.Read(reader)), + ResultVariant.Err => new ErrR(errRW.Read(reader)), _ => throw new InvalidOperationException(), }; @@ -719,12 +719,12 @@ public void Write(BinaryWriter writer, Result value) switch (value) { case OkR(var v): - __enumTag.Write(writer, @enum.Ok); + __enumTag.Write(writer, ResultVariant.Ok); okRW.Write(writer, v); break; case ErrR(var e): - __enumTag.Write(writer, @enum.Err); + __enumTag.Write(writer, ResultVariant.Err); errRW.Write(writer, e); break; } diff --git a/crates/bindings-csharp/BSATN.Runtime/QueryBuilder.cs b/crates/bindings-csharp/BSATN.Runtime/QueryBuilder.cs index db16bd56cd7..e8d52a084e6 100644 --- a/crates/bindings-csharp/BSATN.Runtime/QueryBuilder.cs +++ b/crates/bindings-csharp/BSATN.Runtime/QueryBuilder.cs @@ -1,5 +1,3 @@ -#nullable enable - namespace SpacetimeDB; using System; @@ -68,14 +66,9 @@ public interface IQuery string ToSql(); } -public readonly struct BoolExpr +public readonly struct BoolExpr(string sql) { - public string Sql { get; } - - public BoolExpr(string sql) - { - Sql = sql; - } + public string Sql { get; } = sql; public BoolExpr And(BoolExpr other) => new($"({Sql} AND {other.Sql})"); @@ -120,18 +113,9 @@ internal IxJoinEq(string leftRefSql, string rightRefSql) } } -public readonly struct Col +public readonly struct Col(string tableName, string columnName) where TValue : notnull { - private readonly string tableName; - private readonly string columnName; - - public Col(string tableName, string columnName) - { - this.tableName = tableName; - this.columnName = columnName; - } - internal string RefSql => $"{SqlFormat.QuoteIdent(tableName)}.{SqlFormat.QuoteIdent(columnName)}"; @@ -162,18 +146,9 @@ public Col(string tableName, string columnName) public override string ToString() => RefSql; } -public readonly struct IxCol +public readonly struct IxCol(string tableName, string columnName) where TValue : notnull { - private readonly string tableName; - private readonly string columnName; - - public IxCol(string tableName, string columnName) - { - this.tableName = tableName; - this.columnName = columnName; - } - internal string RefSql => $"{SqlFormat.QuoteIdent(tableName)}.{SqlFormat.QuoteIdent(columnName)}"; @@ -187,19 +162,9 @@ public IxJoinEq Eq(IxCol other) = public override string ToString() => RefSql; } -public sealed class Table : IQuery +public sealed class Table(string tableName, TCols cols, TIxCols ixCols) + : IQuery { - private readonly string tableName; - private readonly TCols cols; - private readonly TIxCols ixCols; - - public Table(string tableName, TCols cols, TIxCols ixCols) - { - this.tableName = tableName; - this.cols = cols; - this.ixCols = ixCols; - } - internal string TableRefSql => SqlFormat.QuoteIdent(tableName); internal TCols Cols => cols; @@ -229,7 +194,7 @@ public LeftSemiJoin L >( Table right, Func> on - ) => new(this, right, on(ixCols, right.ixCols), whereExpr: null); + ) => new(this, right, on(ixCols, right.IxCols), whereExpr: null); public RightSemiJoin RightSemijoin< TRightRow, @@ -238,7 +203,7 @@ public RightSemiJoin >( Table right, Func> on - ) => new(this, right, on(ixCols, right.ixCols), leftWhereExpr: null); + ) => new(this, right, on(ixCols, right.IxCols), leftWhereExpr: null); } public sealed class FromWhere : IQuery @@ -889,7 +854,7 @@ public static string FormatHexLiteral(string hex) var s = hex; if (s.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) { - s = s.Substring(2); + s = s[2..]; } s = s.Replace("-", string.Empty); diff --git a/crates/bindings-csharp/Codegen.Tests/Tests.cs b/crates/bindings-csharp/Codegen.Tests/Tests.cs index a70a1bd3482..4133819ef9c 100644 --- a/crates/bindings-csharp/Codegen.Tests/Tests.cs +++ b/crates/bindings-csharp/Codegen.Tests/Tests.cs @@ -26,18 +26,11 @@ public static class GeneratorSnapshotTests record struct StepOutput(string Key, IncrementalStepRunReason Reason, object Value); - private class Fixture + private class Fixture(string projectDir, CSharpCompilation sampleCompilation) { - private readonly string projectDir; - public CSharpCompilation SampleCompilation { get; } - public CSharpParseOptions ParseOptions { get; } - - public Fixture(string projectDir, CSharpCompilation sampleCompilation) - { - this.projectDir = projectDir; - SampleCompilation = sampleCompilation; - ParseOptions = (CSharpParseOptions)sampleCompilation.SyntaxTrees.First().Options; - } + public CSharpCompilation SampleCompilation { get; } = sampleCompilation; + public CSharpParseOptions ParseOptions { get; } = + (CSharpParseOptions)sampleCompilation.SyntaxTrees.First().Options; public static async Task Compile(string name) { diff --git a/crates/bindings-csharp/Codegen/Module.cs b/crates/bindings-csharp/Codegen/Module.cs index 2ded9cb3412..34040525b94 100644 --- a/crates/bindings-csharp/Codegen/Module.cs +++ b/crates/bindings-csharp/Codegen/Module.cs @@ -762,15 +762,15 @@ public sealed class {{{identifierName}}}Index /// Represents a generated accessor for a table, providing different access patterns /// and visibility levels for the underlying table data. /// - /// Name of the generated accessor type - /// Fully qualified name of the table type - /// C# source code for the accessor implementation - /// C# property getter for accessing the accessor + /// Name of the generated accessor type + /// Fully qualified name of the table type + /// C# source code for the accessor implementation + /// C# property getter for accessing the accessor public record struct GeneratedTableAccessor( - string tableAccessorName, - string tableName, - string tableAccessor, - string getter + string TableAccessorName, + string TableName, + string TableAccessor, + string Getter ); /// @@ -862,10 +862,10 @@ v.Scheduled is { } scheduled } public record struct GeneratedReadOnlyAccessor( - string tableAccessorName, - string tableName, - string readOnlyAccessor, - string readOnlyGetter + string TableAccessorName, + string TableName, + string ReadOnlyAccessor, + string ReadOnlyGetter ); public IEnumerable GenerateReadOnlyAccessors() @@ -1023,14 +1023,14 @@ public readonly partial struct QueryBuilder /// /// Represents a default value for a table field, used during table creation. /// - /// Name of the table containing the field - /// Index of the column in the table - /// String representation of the default value + /// Name of the table containing the field + /// Index of the column in the table + /// String representation of the default value /// BSATN Type name of the default value public record struct FieldDefaultValue( - string tableName, - string columnId, - string value, + string TableName, + string ColumnId, + string Value, string BSATNTypeName ); @@ -1060,7 +1060,7 @@ public IEnumerable GenerateDefaultValues() ); var withDefaultValues = - fieldsWithDefaultValues as ColumnDeclaration[] ?? fieldsWithDefaultValues.ToArray(); + fieldsWithDefaultValues as ColumnDeclaration[] ?? [.. fieldsWithDefaultValues]; foreach (var fieldsWithDefaultValue in withDefaultValues) { if ( @@ -1722,49 +1722,48 @@ public string GenerateClass() if (HasWrongSignature) { - bodyLines = new[] - { + bodyLines = + [ "throw new System.InvalidOperationException(\"Invalid procedure signature.\");", - }; + ]; } else if (HasTxWrapper) { - var successLines = txPayloadIsUnit - ? new[] { "return System.Array.Empty();" } - : new[] - { + string[] successLines = txPayloadIsUnit + ? ["return System.Array.Empty();"] + : + [ "using var output = new MemoryStream();", "using var writer = new BinaryWriter(output);", "__txReturnRW.Write(writer, outcome.Value!);", "return output.ToArray();", - }; + ]; - bodyLines = new[] - { + bodyLines = + [ $"var outcome = {invocation};", "if (!outcome.IsSuccess)", "{", " throw outcome.Error ?? new System.InvalidOperationException(\"Transaction failed.\");", "}", - } - .Concat(successLines) - .ToArray(); + .. successLines, + ]; } else if (ReturnType.Name == "SpacetimeDB.Unit") { - bodyLines = new[] { $"{invocation};", "return System.Array.Empty();" }; + bodyLines = [$"{invocation};", "return System.Array.Empty();"]; } else { var serializer = $"new {ReturnType.ToBSATNString()}()"; - bodyLines = new[] - { + bodyLines = + [ $"var result = {invocation};", "using var output = new MemoryStream();", "using var writer = new BinaryWriter(output);", $"{serializer}.Write(writer, result);", "return output.ToArray();", - }; + ]; } var invokeBody = string.Join("\n", bodyLines.Select(line => $" {line}")); @@ -2348,8 +2347,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) tables .SelectMany((t, ct) => t.GenerateTableAccessors()) .WithTrackingName("SpacetimeDB.Table.GenerateTableAccessors"), - v => v.tableAccessorName, - v => v.tableName + v => v.TableAccessorName, + v => v.TableName ); var readOnlyAccessors = CollectDistinct( @@ -2358,8 +2357,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) tables .SelectMany((t, ct) => t.GenerateReadOnlyAccessors()) .WithTrackingName("SpacetimeDB.Table.GenerateReadOnlyAccessors"), - v => v.tableAccessorName + "ReadOnly", - v => v.tableName + v => v.TableAccessorName + "ReadOnly", + v => v.TableName ); var rlsFilters = context @@ -2391,8 +2390,8 @@ public void Initialize(IncrementalGeneratorInitializationContext context) tables .SelectMany((t, ct) => t.GenerateDefaultValues()) .WithTrackingName("SpacetimeDB.Table.GenerateDefaultValues"), - v => v.tableName + "_" + v.columnId, - v => v.tableName + "_" + v.columnId + v => v.TableName + "_" + v.ColumnId, + v => v.TableName + "_" + v.ColumnId ); var moduleOutputInputs = tableAccessors @@ -2749,7 +2748,7 @@ internal HandlerTxContext(Internal.TxContext inner) : base(inner) {} } public sealed class Local : global::SpacetimeDB.LocalBase { - {{string.Join("\n", tableAccessors.Select(v => v.getter))}} + {{string.Join("\n", tableAccessors.Select(v => v.Getter))}} } public sealed record ViewContext : DbContext, Internal.IViewContext @@ -2775,7 +2774,7 @@ internal AnonymousViewContext(Internal.LocalReadOnly db) } namespace SpacetimeDB.Internal.TableHandles { - {{string.Join("\n", tableAccessors.Select(v => v.tableAccessor))}} + {{string.Join("\n", tableAccessors.Select(v => v.TableAccessor))}} } {{string.Join("\n", @@ -2788,12 +2787,12 @@ namespace SpacetimeDB.Internal.TableHandles { )}} namespace SpacetimeDB.Internal.ViewHandles { - {{string.Join("\n", readOnlyAccessors.Array.Select(v => v.readOnlyAccessor))}} + {{string.Join("\n", readOnlyAccessors.Array.Select(v => v.ReadOnlyAccessor))}} } namespace SpacetimeDB.Internal { public sealed partial class LocalReadOnly { - {{string.Join("\n", readOnlyAccessors.Select(v => v.readOnlyGetter))}} + {{string.Join("\n", readOnlyAccessors.Select(v => v.ReadOnlyGetter))}} } } @@ -2865,7 +2864,7 @@ public static void Main() { {{string.Join( "\n", - tableAccessors.Select(t => $"SpacetimeDB.Internal.Module.RegisterTable<{t.tableName}, SpacetimeDB.Internal.TableHandles.{EscapeIdentifier(t.tableAccessorName)}>();") + tableAccessors.Select(t => $"SpacetimeDB.Internal.Module.RegisterTable<{t.TableName}, SpacetimeDB.Internal.TableHandles.{EscapeIdentifier(t.TableAccessorName)}>();") )}} {{( httpRouters.Array.FirstOrDefault(r => r.IsValid) is { } router @@ -2883,9 +2882,9 @@ public static void Main() { + $"var value = new {d.BSATNTypeName}();\n" + "__memoryStream.Position = 0;\n" + "__memoryStream.SetLength(0);\n" - + $"value.Write(__writer, {d.value});\n" + + $"value.Write(__writer, {d.Value});\n" + "var array = __memoryStream.ToArray();\n" - + $"SpacetimeDB.Internal.Module.RegisterTableDefaultValue(\"{d.tableName}\", {d.columnId}, array);" + + $"SpacetimeDB.Internal.Module.RegisterTableDefaultValue(\"{d.TableName}\", {d.ColumnId}, array);" + "\n}\n") )}} } diff --git a/crates/bindings-csharp/Directory.Build.props b/crates/bindings-csharp/Directory.Build.props index 0b47ecdad6b..42d61d11810 100644 --- a/crates/bindings-csharp/Directory.Build.props +++ b/crates/bindings-csharp/Directory.Build.props @@ -25,12 +25,6 @@ true $(NoWarn);CS1591;CS1574 - - $(WarningsNotAsErrors); - CA1822;CA2263; - IDE0005;IDE0028;IDE0039;IDE0057;IDE0065;IDE0078;IDE0090; - IDE0240;IDE0290;IDE0300;IDE0301;IDE0305;IDE0306;IDE1006 - diff --git a/crates/bindings-csharp/Runtime/AuthCtx.cs b/crates/bindings-csharp/Runtime/AuthCtx.cs index a99ebe7d21c..7bf844719fc 100644 --- a/crates/bindings-csharp/Runtime/AuthCtx.cs +++ b/crates/bindings-csharp/Runtime/AuthCtx.cs @@ -4,6 +4,8 @@ namespace SpacetimeDB; public sealed class AuthCtx { + private static byte[] jwtBuffer = new byte[0x10_000]; + private readonly bool _isInternal; private readonly Lazy _jwtLazy; @@ -47,11 +49,12 @@ private static AuthCtx FromConnectionId(ConnectionId connectionId, Identity iden { var result = SpacetimeDB.Internal.FFI.get_jwt(ref connectionId, out var source); SpacetimeDB.Internal.FFI.CheckedStatus.Marshaller.ConvertToManaged(result); - var bytes = SpacetimeDB.Internal.Module.Consume(source); - if (bytes == null || bytes.Length == 0) + using var stream = SpacetimeDB.Internal.Module.Consume(source, ref jwtBuffer); + if (stream.Length == 0) { return null; } + var bytes = stream.ToArray(); var jwt = System.Text.Encoding.UTF8.GetString(bytes); return jwt != null ? new JwtClaims(jwt, identity) : null; } diff --git a/crates/bindings-csharp/Runtime/Http.cs b/crates/bindings-csharp/Runtime/Http.cs index be98b704fcc..78850782590 100644 --- a/crates/bindings-csharp/Runtime/Http.cs +++ b/crates/bindings-csharp/Runtime/Http.cs @@ -2,6 +2,7 @@ namespace SpacetimeDB; using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using System.Text; @@ -57,7 +58,7 @@ public HttpHeader(string name, string value) /// public readonly record struct HttpBody(byte[] Bytes) { - public static HttpBody Empty => new(Array.Empty()); + public static HttpBody Empty => new([]); public byte[] ToBytes() => Bytes; @@ -82,7 +83,7 @@ public sealed class HttpRequest public HttpMethod Method { get; init; } = HttpMethod.Get; /// HTTP headers to include with the request. - public List Headers { get; init; } = new(); + public List Headers { get; init; } = []; /// Request body bytes. public HttpBody Body { get; init; } = HttpBody.Empty; @@ -154,6 +155,9 @@ public sealed class HttpError(string message) : Exception(message) public sealed class HttpClient { private static readonly TimeSpan MaxTimeout = TimeSpan.FromMilliseconds(500); + private static byte[] responseWireBuffer = new byte[0x10_000]; + private static byte[] responseBodyBuffer = new byte[0x10_000]; + private static byte[] errorWireBuffer = new byte[0x10_000]; /// /// Send a simple GET request to with no headers. @@ -271,6 +275,11 @@ public Result Get(string uri, TimeSpan? timeout = null) /// } /// /// + [SuppressMessage( + "Performance", + "CA1822", + Justification = "Public instance API exposed through ProcedureContext.Http." + )] public Result Send(HttpRequest request) { // The host syscall expects BSATN-encoded spacetimedb_lib::http::Request bytes. @@ -308,7 +317,7 @@ public Result Send(HttpRequest request) Method = ToWireMethod(request.Method), Headers = new HttpHeadersWire { - Entries = request.Headers.Select(ToWireHeader).ToArray(), + Entries = [.. request.Headers.Select(ToWireHeader)], }, Timeout = timeout is null ? null @@ -335,10 +344,10 @@ out var out_ { case Errno.OK: { - var responseWireBytes = out_.A.Consume(); + var responseWireBytes = out_.A.Consume(ref responseWireBuffer).ToArray(); var responseWire = FromBytes(new HttpResponseWire.BSATN(), responseWireBytes); - var body = new HttpBody(out_.B.Consume()); + var body = new HttpBody(out_.B.Consume(ref responseBodyBuffer).ToArray()); var (statusCode, version, headers) = FromWireResponse(responseWire); return Result.Ok( @@ -347,7 +356,7 @@ out var out_ } case Errno.HTTP_ERROR: { - var errorWireBytes = out_.A.Consume(); + var errorWireBytes = out_.A.Consume(ref errorWireBuffer).ToArray(); var err = FromBytes(new SpacetimeDB.BSATN.String(), errorWireBytes); return Result.Err(new HttpError(err)); } @@ -423,9 +432,10 @@ internal static HttpRequest FromWire(HttpRequestWire requestWire, byte[] body) = { Uri = requestWire.Uri, Method = FromWireMethod(requestWire.Method), - Headers = requestWire - .Headers.Entries.Select(h => new HttpHeader(h.Name, h.Value, false)) - .ToList(), + Headers = + [ + .. requestWire.Headers.Entries.Select(h => new HttpHeader(h.Name, h.Value, false)), + ], Body = new HttpBody(body), Version = FromWireVersion(requestWire.Version), }; @@ -436,7 +446,7 @@ internal static (HttpResponseWire Response, byte[] Body) ToWire(HttpResponse res { Headers = new HttpHeadersWire { - Entries = response.Headers.Select(ToWireHeader).ToArray(), + Entries = [.. response.Headers.Select(ToWireHeader)], }, Version = ToWireVersion(response.Version), Code = response.StatusCode, diff --git a/crates/bindings-csharp/Runtime/Internal/Bounds.cs b/crates/bindings-csharp/Runtime/Internal/Bounds.cs index 2bdd42c3aa1..f3e302aa8de 100644 --- a/crates/bindings-csharp/Runtime/Internal/Bounds.cs +++ b/crates/bindings-csharp/Runtime/Internal/Bounds.cs @@ -1,6 +1,3 @@ -using System.IO; -using SpacetimeDB.BSATN; - namespace SpacetimeDB { public readonly struct Bound(T min, T max) @@ -16,6 +13,8 @@ public readonly struct Bound(T min, T max) namespace SpacetimeDB.Internal { + using SpacetimeDB.BSATN; + enum BoundVariant : byte { Inclusive, diff --git a/crates/bindings-csharp/Runtime/Internal/FFI.cs b/crates/bindings-csharp/Runtime/Internal/FFI.cs index aa2ebf6f3be..96984a4f281 100644 --- a/crates/bindings-csharp/Runtime/Internal/FFI.cs +++ b/crates/bindings-csharp/Runtime/Internal/FFI.cs @@ -50,6 +50,11 @@ public enum Errno : short HTTP_ERROR = 21, } +internal static class ErrnoExtensions +{ + public static void Check(this Errno status) => FFI.ErrnoHelpers.ThrowIfError(status); +} + #pragma warning disable IDE1006 // Naming Styles - Not applicable to FFI stuff. internal static partial class FFI { @@ -369,7 +374,7 @@ private ConsoleTimerId(uint id) )] internal static class ConsoleTimerIdMarshaller { - public static ConsoleTimerId ConvertToManaged(uint id) => new ConsoleTimerId(id); + public static ConsoleTimerId ConvertToManaged(uint id) => new(id); public static uint ConvertToUnmanaged(ConsoleTimerId id) => id.timer_id; } diff --git a/crates/bindings-csharp/Runtime/Internal/IIndex.cs b/crates/bindings-csharp/Runtime/Internal/IIndex.cs index 33525924f2d..5b0b9e330f4 100644 --- a/crates/bindings-csharp/Runtime/Internal/IIndex.cs +++ b/crates/bindings-csharp/Runtime/Internal/IIndex.cs @@ -44,7 +44,7 @@ out ReadOnlySpan rend } protected IEnumerable DoFilter(Bounds bounds) - where Bounds : IBTreeIndexBounds => new RawTableIter(indexId, bounds).Parse(); + where Bounds : IBTreeIndexBounds => new RawTableIter(indexId, bounds); protected uint DoDelete(Bounds bounds) where Bounds : IBTreeIndexBounds @@ -129,7 +129,7 @@ out var numDeleted new RW().Write(w, key); var point = s.ToArray(); - using var e = new RawPointIter(indexId, point).Parse().GetEnumerator(); + using var e = new RawPointIter(indexId, point).GetEnumerator(); if (!e.MoveNext()) { return null; @@ -192,7 +192,7 @@ out var numDeleted new RW().Write(w, key); var point = s.ToArray(); - using var e = new RawPointIter(indexId, point).Parse().GetEnumerator(); + using var e = new RawPointIter(indexId, point).GetEnumerator(); if (!e.MoveNext()) { return null; @@ -241,7 +241,7 @@ protected override void IterStart(out FFI.RowIter handle) => new RW().Write(w, key); var point = s.ToArray(); - using var e = new RawPointIter(indexId, point).Parse().GetEnumerator(); + using var e = new RawPointIter(indexId, point).GetEnumerator(); if (!e.MoveNext()) { return null; @@ -280,7 +280,7 @@ protected override void IterStart(out FFI.RowIter handle) => new RW().Write(w, key); var point = s.ToArray(); - using var e = new RawPointIter(indexId, point).Parse().GetEnumerator(); + using var e = new RawPointIter(indexId, point).GetEnumerator(); if (!e.MoveNext()) { return null; @@ -319,5 +319,5 @@ protected ulong DoCount() return count; } - protected IEnumerable DoIter() => new TableIter(tableId).Parse(); + protected IEnumerable DoIter() => new TableIter(tableId); } diff --git a/crates/bindings-csharp/Runtime/Internal/ITable.cs b/crates/bindings-csharp/Runtime/Internal/ITable.cs index 5294ce0e7fe..7b284f5eb66 100644 --- a/crates/bindings-csharp/Runtime/Internal/ITable.cs +++ b/crates/bindings-csharp/Runtime/Internal/ITable.cs @@ -1,34 +1,26 @@ namespace SpacetimeDB.Internal; using System.Buffers; +using System.Collections; using SpacetimeDB.BSATN; -internal abstract class RawTableIterBase +internal abstract class RawTableIterBase : IEnumerable where T : IStructuralReadWrite, new() { - public sealed class Enumerator(FFI.RowIter handle) : IDisposable - { - private const int InitialBufferSize = 1024; - private byte[]? buffer = ArrayPool.Shared.Rent(InitialBufferSize); - public ArraySegment Current { get; private set; } = ArraySegment.Empty; - - public bool MoveNext() - { - if (handle == FFI.RowIter.INVALID) - { - return false; - } + private const int InitialBufferSize = 1024; - if (buffer is null) - { - return false; - } + protected abstract void IterStart(out FFI.RowIter handle); - uint buffer_len; - while (true) + public IEnumerator GetEnumerator() + { + IterStart(out var handle); + var buffer = ArrayPool.Shared.Rent(InitialBufferSize); + try + { + while (handle != FFI.RowIter.INVALID) { var requested_len = (uint)buffer.Length; - buffer_len = requested_len; + var buffer_len = requested_len; var ret = FFI.row_iter_bsatn_advance(handle, buffer, ref buffer_len); if (ret == Errno.EXHAUSTED) { @@ -38,82 +30,50 @@ public bool MoveNext() buffer_len = 0; } } + // On success, the only way `buffer_len == 0` is for the iterator to be exhausted. // This happens when the host iterator was empty from the start. System.Diagnostics.Debug.Assert(!(ret == Errno.OK && buffer_len == 0)); switch (ret) { - // Iterator advanced and may also be `EXHAUSTED`. - // When `OK`, we'll need to advance the iterator in the next call to `MoveNext`. - // In both cases, update `Current` to point at the valid range in the scratch `buffer`. case Errno.EXHAUSTED or Errno.OK: - Current = new ArraySegment(buffer, 0, (int)buffer_len); - return buffer_len != 0; - // Couldn't find the iterator, error! - case Errno.NO_SUCH_ITER: - throw new NoSuchIterException(); - // The scratch `buffer` is too small to fit a row / chunk. - // Grow `buffer` and try again. - // The `buffer_len` will have been updated with the necessary size. + { + using var stream = new MemoryStream( + buffer, + 0, + (int)buffer_len, + writable: false, + publiclyVisible: true + ); + using var reader = new BinaryReader(stream); + while (stream.Position < stream.Length) + { + yield return IStructuralReadWrite.Read(reader); + } + break; + } case Errno.BUFFER_TOO_SMALL: ArrayPool.Shared.Return(buffer); buffer = ArrayPool.Shared.Rent((int)buffer_len); - continue; + break; default: - throw new UnknownException(ret); + ret.Check(); + break; } } } - - public void Dispose() + finally { if (handle != FFI.RowIter.INVALID) { FFI.row_iter_bsatn_close(handle); - handle = FFI.RowIter.INVALID; } - - if (buffer is not null) - { - ArrayPool.Shared.Return(buffer); - buffer = null; - } - } - - public void Reset() - { - throw new NotImplementedException(); + ArrayPool.Shared.Return(buffer); } } - protected abstract void IterStart(out FFI.RowIter handle); - - // Note: using the GetEnumerator() duck-typing protocol instead of IEnumerable to avoid extra boxing. - public Enumerator GetEnumerator() - { - IterStart(out var handle); - return new(handle); - } - - public IEnumerable Parse() - { - foreach (var chunk in this) - { - using var stream = new MemoryStream( - chunk.Array!, - chunk.Offset, - chunk.Count, - writable: false, - publiclyVisible: true - ); - using var reader = new BinaryReader(stream); - while (stream.Position < stream.Length) - { - yield return IStructuralReadWrite.Read(reader); - } - } - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } public interface ITableView @@ -146,7 +106,9 @@ protected override void IterStart(out FFI.RowIter handle) => return out_; }); +#pragma warning disable IDE1006 // Used by static interface member call sites. internal static FFI.TableId tableId => tableId_.Value; +#pragma warning restore IDE1006 ulong Count { get; } @@ -164,7 +126,7 @@ protected static ulong DoCount() return count; } - protected static IEnumerable DoIter() => new RawTableIter(tableId).Parse(); + protected static IEnumerable DoIter() => new RawTableIter(tableId); protected static T DoInsert(T row) { diff --git a/crates/bindings-csharp/Runtime/Internal/Module.cs b/crates/bindings-csharp/Runtime/Internal/Module.cs index 4d15f0e93cc..14653026220 100644 --- a/crates/bindings-csharp/Runtime/Internal/Module.cs +++ b/crates/bindings-csharp/Runtime/Internal/Module.cs @@ -31,11 +31,11 @@ partial class RawModuleDefV10 // Fix it up to a different mangling scheme if it causes problems. private static string GetFriendlyName(Type type) => type.IsGenericType - ? $"{type.Name.Remove(type.Name.IndexOf('`'))}_{string.Join("_", type.GetGenericArguments().Select(GetFriendlyName))}" + ? $"{type.Name[..type.Name.IndexOf('`')]}_{string.Join("_", type.GetGenericArguments().Select(GetFriendlyName))}" : type.Name; private static RawScopedTypeNameV10 MakeScopedTypeName(Type type) => - new(new List(), GetFriendlyName(type)); + new([], GetFriendlyName(type)); internal AlgebraicType.Ref RegisterType(Func makeType) { @@ -84,7 +84,7 @@ internal void RegisterTable(RawTableDefV10 table, RawScheduleDefV10? schedule) internal void RegisterView(RawViewDefV10 view) => viewDefs.Add(view); internal void RegisterViewPrimaryKey(string viewSourceName, IEnumerable columns) => - viewPrimaryKeyDefs.Add(new RawViewPrimaryKeyDefV10(viewSourceName, columns.ToList())); + viewPrimaryKeyDefs.Add(new RawViewPrimaryKeyDefV10(viewSourceName, [.. columns])); internal void RegisterRowLevelSecurity(RawRowLevelSecurityDefV9 rls) => rowLevelSecurityDefs.Add(rls); @@ -96,7 +96,7 @@ internal void RegisterTableDefaultValue(string table, ushort colId, byte[] value defaults = []; defaultValuesByTable.Add(table, defaults); } - defaults.Add(new RawColumnDefaultValueV10(colId, new List(value))); + defaults.Add(new RawColumnDefaultValueV10(colId, [.. value])); } internal void SetCaseConversionPolicy(SpacetimeDB.CaseConversionPolicy policy) => @@ -129,9 +129,7 @@ internal RawModuleDefV10 BuildModuleDefinition() Sequences: table.Sequences, TableType: table.TableType, TableAccess: table.TableAccess, - DefaultValues: defaults is null - ? [] - : new List(defaults), + DefaultValues: defaults is null ? [] : [.. defaults], IsEvent: table.IsEvent ) ); @@ -211,9 +209,7 @@ internal RawModuleDefV10 BuildModuleDefinition() if (explicitNames.Count > 0) { sections.Add( - new RawModuleDefV10Section.ExplicitNames( - new ExplicitNames(new List(explicitNames)) - ) + new RawModuleDefV10Section.ExplicitNames(new ExplicitNames([.. explicitNames])) ); } if (rowLevelSecurityDefs.Count > 0) @@ -244,7 +240,9 @@ private static void EnsureNativeAotTypeRoots() // These constructions are never executed at runtime — they exist solely // to make the IL scanner compute vtables for TaggedEnum subtypes. // The condition is always false but the scanner must assume it could be true. +#pragma warning disable IDE0078 // Keep this opaque to the NativeAOT IL scanner. if (Environment.TickCount < 0 && Environment.TickCount > 0) +#pragma warning restore IDE0078 { _ = new RawIndexAlgorithm.BTree(null!); _ = new RawConstraintDataV9.Unique(null!); @@ -441,11 +439,11 @@ public static void RegisterExplicitFunctionName(string sourceName, string canoni public static void RegisterExplicitIndexName(string sourceName, string canonicalName) => moduleDef.RegisterExplicitIndexName(sourceName, canonicalName); - public static byte[] Consume(this BytesSource source) + internal static MemoryStream Consume(this BytesSource source, ref byte[] buffer) { if (source == BytesSource.INVALID) { - return []; + return new(); } var len = (uint)0; @@ -460,42 +458,28 @@ public static byte[] Consume(this BytesSource source) throw new UnknownException(ret); } - var buffer = new byte[len]; + if (buffer.Length < len) + { + Array.Resize(ref buffer, (int)len); + } + var written = 0U; - // Because we've reserved space in our buffer already, this loop should be unnecessary. - // We expect the first call to `bytes_source_read` to always return `-1`. - // I (pgoldman 2025-09-26) am leaving the loop here because there's no downside to it, - // and in the future we may want to support `BytesSource`s which don't have a known length ahead of time - // (i.e. put arbitrary streams in `BytesSource` on the host side rather than just `Bytes` buffers), - // at which point the loop will become useful again. while (true) { - // Write into the spare capacity of the buffer. var spare = buffer.AsSpan((int)written); var buf_len = (uint)spare.Length; ret = FFI.bytes_source_read(source, spare, ref buf_len); written += buf_len; switch (ret) { - // Host side source exhausted, we're done. case Errno.EXHAUSTED: - Array.Resize(ref buffer, (int)written); - return buffer; - // Wrote the entire spare capacity. - // Need to reserve more space in the buffer. + return new(buffer, 0, (int)written); case Errno.OK when written == buffer.Length: Array.Resize(ref buffer, buffer.Length + 1024); break; - // Host didn't write as much as possible. - // Try to read some more. - // The host will likely not trigger this branch (current host doesn't), - // but a module should be prepared for it. case Errno.OK: + ret.Check(); break; - case Errno.NO_SUCH_BYTES: - throw new NoSuchBytesException(); - default: - throw new UnknownException(ret); } } } @@ -512,6 +496,14 @@ private static void Write(this BytesSink sink, byte[] bytes) } } + // __call_reducer__ is not invoked in parallel because modules do not support multithreading in Wasm. + private static byte[] reducerArgsBuffer = new byte[0x10_000]; + private static byte[] procedureArgsBuffer = new byte[0x10_000]; + private static byte[] httpRequestBuffer = new byte[0x10_000]; + private static byte[] httpRequestBodyBuffer = new byte[0x10_000]; + private static byte[] viewArgsBuffer = new byte[0x10_000]; + private static byte[] anonymousViewArgsBuffer = new byte[0x10_000]; + #pragma warning disable IDE1006 // Naming Styles - methods below are meant for FFI. public static void __describe_module__(BytesSink description) @@ -556,7 +548,7 @@ BytesSink error var ctx = newReducerContext!(senderIdentity, connectionId, random, time); - using var stream = new MemoryStream(args.Consume()); + using var stream = args.Consume(ref reducerArgsBuffer); using var reader = new BinaryReader(stream); reducers[(int)id].Invoke(reader, ctx); if (stream.Position != stream.Length) @@ -600,7 +592,7 @@ BytesSink resultSink var ctx = newProcedureContext!(sender, connectionId, random, time); - using var stream = new MemoryStream(args.Consume()); + using var stream = args.Consume(ref procedureArgsBuffer); using var reader = new BinaryReader(stream); var bytes = procedures[(int)id].Invoke(reader, ctx); if (stream.Position != stream.Length) @@ -636,8 +628,7 @@ BytesSink responseBodySink var time = timestamp.ToStd(); var ctx = newHandlerContext!(random, time); - var requestBytes = request.Consume(); - using var stream = new MemoryStream(requestBytes); + using var stream = request.Consume(ref httpRequestBuffer); using var reader = new BinaryReader(stream); var requestWire = new HttpRequestWire.BSATN().Read(reader); if (stream.Position != stream.Length) @@ -646,7 +637,13 @@ BytesSink responseBodySink } var response = httpHandlers[(int)id] - .Invoke(ctx, SpacetimeDB.HttpClient.FromWire(requestWire, requestBody.Consume())); + .Invoke( + ctx, + SpacetimeDB.HttpClient.FromWire( + requestWire, + requestBody.Consume(ref httpRequestBodyBuffer).ToArray() + ) + ); var (responseWire, responseBody) = SpacetimeDB.HttpClient.ToWire(response); responseSink.Write( IStructuralReadWrite.ToBytes(new HttpResponseWire.BSATN(), responseWire) @@ -702,7 +699,7 @@ BytesSink rows MemoryMarshal.AsBytes([sender_0, sender_1, sender_2, sender_3]).ToArray() ); var ctx = newViewContext!(sender); - using var stream = new MemoryStream(args.Consume()); + using var stream = args.Consume(ref viewArgsBuffer); using var reader = new BinaryReader(stream); var bytes = viewDispatchers[(int)id].Invoke(reader, ctx); rows.Write(bytes); @@ -740,7 +737,7 @@ public static Errno __call_view_anon__(uint id, BytesSource args, BytesSink rows try { var ctx = newAnonymousViewContext!(); - using var stream = new MemoryStream(args.Consume()); + using var stream = args.Consume(ref anonymousViewArgsBuffer); using var reader = new BinaryReader(stream); var bytes = anonymousViewDispatchers[(int)id].Invoke(reader, ctx); rows.Write(bytes); diff --git a/crates/bindings-csharp/Runtime/JwtClaims.cs b/crates/bindings-csharp/Runtime/JwtClaims.cs index 6e656cca202..3ca3e11e029 100644 --- a/crates/bindings-csharp/Runtime/JwtClaims.cs +++ b/crates/bindings-csharp/Runtime/JwtClaims.cs @@ -72,11 +72,13 @@ private List ExtractAudience() return aud.ValueKind switch { - JsonValueKind.String => new List { aud.GetString()! }, - JsonValueKind.Array => aud.EnumerateArray() - .Where(e => e.ValueKind == JsonValueKind.String) - .Select(e => e.GetString()!) - .ToList(), + JsonValueKind.String => [aud.GetString()!], + JsonValueKind.Array => + [ + .. aud.EnumerateArray() + .Where(e => e.ValueKind == JsonValueKind.String) + .Select(e => e.GetString()!), + ], _ => throw new InvalidOperationException("Unexpected type for 'aud' claim in JWT"), }; } diff --git a/crates/bindings-csharp/Runtime/Router.cs b/crates/bindings-csharp/Runtime/Router.cs index e146bd5057d..7377d7b2561 100644 --- a/crates/bindings-csharp/Runtime/Router.cs +++ b/crates/bindings-csharp/Runtime/Router.cs @@ -91,7 +91,7 @@ private Router AddRoute(MethodOrAny method, string path, Handler handler) return new Router(merged); } - private List CloneRoutes() => new(routes); + private List CloneRoutes() => [.. routes]; private static void AddRoute( List routes, @@ -160,5 +160,5 @@ private static void AssertValidPath(string path) } private static bool CharacterIsAcceptableForRoutePath(char c) => - (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c is '-' or '_' or '~' or '/'; + c is (>= 'a' and <= 'z') or (>= '0' and <= '9') or '-' or '_' or '~' or '/'; } diff --git a/crates/bindings-csharp/Runtime/TransactionalContextState.cs b/crates/bindings-csharp/Runtime/TransactionalContextState.cs index ef0d72de410..b6734b83875 100644 --- a/crates/bindings-csharp/Runtime/TransactionalContextState.cs +++ b/crates/bindings-csharp/Runtime/TransactionalContextState.cs @@ -79,48 +79,25 @@ Func> body } } - private long StartMutTx() + private static long StartMutTx() { var status = FFI.procedure_start_mut_tx(out var micros); FFI.ErrnoHelpers.ThrowIfError(status); return micros; } - private void CommitMutTx() + private static void CommitMutTx() { var status = FFI.procedure_commit_mut_tx(); FFI.ErrnoHelpers.ThrowIfError(status); } - private void AbortMutTx() + private static void AbortMutTx() { var status = FFI.procedure_abort_mut_tx(); FFI.ErrnoHelpers.ThrowIfError(status); } - private bool CommitMutTxWithRetry(Func retryBody) - { - try - { - CommitMutTx(); - return true; - } - catch (TransactionNotAnonymousException) - { - return false; - } - catch (StdbException) - { - Log.Warn("Committing anonymous transaction failed; retrying once."); - if (retryBody()) - { - CommitMutTx(); - return true; - } - return false; - } - } - private Result RunWithRetry( Func> body ) @@ -132,18 +109,28 @@ Func> body return result; } - bool Retry() + try { - result = RunOnce(body); - return result is Result.OkR; + CommitMutTx(); + return result; } - - if (!CommitMutTxWithRetry(Retry)) + catch (TransactionNotAnonymousException) { return result; } + catch (StdbException) + { + Log.Warn("Committing anonymous transaction failed; retrying once."); - return result; + var retryResult = RunOnce(body); + if (retryResult is Result.ErrR) + { + return retryResult; + } + + CommitMutTx(); + return retryResult; + } } private Result RunOnce( diff --git a/sdks/csharp/src/SpacetimeDBClient.cs b/sdks/csharp/src/SpacetimeDBClient.cs index ef202ed168b..6df6c6155e8 100644 --- a/sdks/csharp/src/SpacetimeDBClient.cs +++ b/sdks/csharp/src/SpacetimeDBClient.cs @@ -1,5 +1,4 @@ using System; -using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; @@ -133,6 +132,8 @@ public abstract class DbConnectionBase : IDbConne where DbConnection : DbConnectionBase, new() where Tables : RemoteTablesBase { + internal static bool IsTesting { get; set; } = false; + public static DbConnectionBuilder Builder() => new(); internal event Action? onConnect; @@ -281,7 +282,6 @@ internal struct ParsedMessage private readonly BlockingCollection _applyQueue = new(new ConcurrentQueue()); - internal static bool IsTesting; internal bool HasMessageToApply => _applyQueue.Count > 0; private readonly CancellationTokenSource _parseCancellationTokenSource = new();