Skip to content

Commit

Permalink
Migrate C# scheduling to the new API
Browse files Browse the repository at this point in the history
  • Loading branch information
RReverser committed Dec 16, 2024
1 parent 0ffdc78 commit f3bdd2c
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 119 deletions.
118 changes: 41 additions & 77 deletions crates/bindings-csharp/Codegen/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,25 @@ namespace SpacetimeDB.Codegen;
using SpacetimeDB.Internal;
using static Utils;

static class IndexOfExt
{
// A LINQ helper to find the index of the first element equal to `searchItem`.
public static int IndexOf<T>(this IEnumerable<T> source, T searchItem)
where T : IEquatable<T>
{
var i = 0;
foreach (var item in source)
{
if (searchItem.Equals(item))
{
return i;
}
i++;
}
throw new InvalidOperationException($"Item {searchItem} not found.");
}
}

readonly record struct ColumnAttr(ColumnAttrs Mask, string? Table = null)
{
private static readonly ImmutableDictionary<string, System.Type> AttrTypes = ImmutableArray
Expand Down Expand Up @@ -127,19 +146,30 @@ public string GenerateColumnDef() =>
$"new (nameof({Name}), BSATN.{Name}.GetAlgebraicType(registrar))";
}

record Scheduled(string ReducerName, int ScheduledAtColumn);

record TableView
{
public readonly string Name;
public readonly bool IsPublic;
public readonly string? Scheduled;
public readonly Scheduled? Scheduled;

public TableView(TableDeclaration table, AttributeData data)
{
var attr = data.ParseAs<TableAttribute>();

Name = attr.Name ?? table.ShortName;
IsPublic = attr.Public;
Scheduled = attr.Scheduled;
if (attr.Scheduled is { } reducer)
{
Scheduled = new(reducer, table.Members.Select(m => m.Name).IndexOf(attr.ScheduledAt));
if (table.GetPrimaryKey(this) is not { } pk || table.Members[pk].Type != "ulong")
{
throw new InvalidOperationException(
$"{Name} is a scheduled table but doesn't have a primary key of type `ulong`."
);
}
}
}
}

Expand Down Expand Up @@ -193,11 +223,9 @@ private ViewIndex(IndexAttribute attr, TypeDeclarationSyntax decl, DiagReporter
public ViewIndex(AttributeData data, TypeDeclarationSyntax decl, DiagReporter diag)
: this(data.ParseAs<IndexAttribute>(), decl, diag) { }

public string GenerateIndexDef(IEnumerable<ColumnDeclaration> columns)
public string GenerateIndexDef(IEnumerable<ColumnDeclaration> allColumns)
{
var colIndices = Columns.Select(c =>
columns.Select((c, i) => (c, i)).First(cd => cd.c.Name == c).i
);
var colIndices = Columns.Select(c => allColumns.Select(cd => cd.Name).IndexOf(c));

return $$"""
new(
Expand All @@ -220,32 +248,14 @@ record TableDeclaration : BaseTypeDeclaration<ColumnDeclaration>
public readonly EquatableArray<TableView> Views;
public readonly EquatableArray<ViewIndex> Indexes;

private static ColumnDeclaration[] ScheduledColumns(string tableName) =>
[
new(
tableName,
"ScheduledId",
"ulong",
"SpacetimeDB.BSATN.U64",
ColumnAttrs.PrimaryKeyAuto,
true
),
new(
tableName,
"ScheduledAt",
"SpacetimeDB.ScheduleAt",
"SpacetimeDB.ScheduleAt.BSATN",
ColumnAttrs.UnSet,
false
),
];

public TableDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter diag)
: base(context, diag)
{
var typeSyntax = (TypeDeclarationSyntax)context.TargetNode;

if (Kind is TypeKind.Sum)
{
diag.Report(ErrorDescriptor.TableTaggedEnum, (TypeDeclarationSyntax)context.TargetNode);
diag.Report(ErrorDescriptor.TableTaggedEnum, typeSyntax);
}

var container = context.TargetSymbol;
Expand All @@ -264,10 +274,7 @@ public TableDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter di
}
break;
default:
diag.Report(
ErrorDescriptor.InvalidTableVisibility,
(TypeDeclarationSyntax)context.TargetNode
);
diag.Report(ErrorDescriptor.InvalidTableVisibility, typeSyntax);
throw new Exception(
"Table row type visibility must be public or internal, including containing types."
);
Expand All @@ -281,27 +288,9 @@ public TableDeclaration(GeneratorAttributeSyntaxContext context, DiagReporter di
context
.TargetSymbol.GetAttributes()
.Where(a => a.AttributeClass?.ToString() == typeof(IndexAttribute).FullName)
.Select(a => new ViewIndex(a, (TypeDeclarationSyntax)context.TargetNode, diag))
.Select(a => new ViewIndex(a, typeSyntax, diag))
.ToImmutableArray()
);

var hasScheduled = Views.Select(t => t.Scheduled is not null).Distinct();

if (hasScheduled.Count() != 1)
{
diag.Report(
ErrorDescriptor.IncompatibleTableSchedule,
(TypeDeclarationSyntax)context.TargetNode
);
}

if (hasScheduled.Any(has => has))
{
// For scheduled tables, we append extra fields early in the pipeline,
// both to the type itself and to the BSATN information, as if they
// were part of the original declaration.
Members = new(Members.Concat(ScheduledColumns(FullName)).ToImmutableArray());
}
}

protected override ColumnDeclaration ConvertMember(IFieldSymbol field, DiagReporter diag) =>
Expand Down Expand Up @@ -432,7 +421,7 @@ public IEnumerable<View> GenerateViews()
Sequences: {{GenConstraintList(v, ColumnAttrs.AutoInc, $"{iTable}.MakeSequence")}},
Schedule: {{(
v.Scheduled is {} scheduled
? $"{iTable}.MakeSchedule(\"{scheduled}\", {/* ScheduledAt is the last column */ Members.Length - 1})"
? $"{iTable}.MakeSchedule(\"{scheduled.ReducerName}\", {scheduled.ScheduledAtColumn})"
: "null"
)}},
TableType: SpacetimeDB.Internal.TableType.User,
Expand Down Expand Up @@ -484,33 +473,8 @@ string makeConstraintFn
]
""";

private int? GetPrimaryKey(TableView view) =>
internal int? GetPrimaryKey(TableView view) =>
GetConstraints(view, ColumnAttrs.PrimaryKey).Select(c => (int?)c.pos).SingleOrDefault();

public override Scope.Extensions ToExtensions()
{
var extensions = base.ToExtensions();

if (Views.Any(v => v.Scheduled is not null))
{
// For scheduled tables we're adding extra fields to the type source.
extensions.Contents.Append(
$$"""
public ulong ScheduledId;
public SpacetimeDB.ScheduleAt ScheduledAt;
"""
);

// When doing so, the compiler will warn about undefined ordering between partial declarations.
// We don't care about ordering as we generate BSATN ourselves and don't use those structs in FFI,
// so we can safely suppress the warning by saying "yes, we're okay with an auto/arbitrary layout".
extensions.ExtraAttrs.Add(
"[System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Auto)]"
);
}

return extensions;
}
}

record ReducerDeclaration
Expand Down
10 changes: 10 additions & 0 deletions crates/bindings-csharp/Runtime/Attrs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,17 @@ public sealed class TableAttribute : Attribute
/// </summary>
public bool Public { get; init; } = false;

/// <summary>
/// If set, the name of the reducer that will be invoked when the scheduled time is reached.
/// </summary>
public string? Scheduled { get; init; }

/// <summary>
/// The name of the column that will be used to store the scheduled time.
///
/// <para>Defaults to <c>ScheduledAt</c>.</para>
/// </summary>
public string ScheduledAt { get; init; } = "ScheduledAt";
}

[AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = true)]
Expand Down
2 changes: 1 addition & 1 deletion crates/bindings-csharp/Runtime/Internal/ITable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ protected static bool DoDelete(T row)
}

protected static RawScheduleDefV9 MakeSchedule(string reducerName, ushort colIndex) =>
new(Name: $"{tableName}_schedule", ReducerName: reducerName, ScheduledAtColumn: colIndex);
new(Name: $"{tableName}_sched", ReducerName: reducerName, ScheduledAtColumn: colIndex);

protected static RawSequenceDefV9 MakeSequence(ushort colIndex) =>
new(
Expand Down
108 changes: 67 additions & 41 deletions modules/sdk-test-cs/Lib.cs
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,10 @@ public partial struct VecEveryPrimitiveStruct
}

[SpacetimeDB.Reducer]
public static void insert_vec_every_primitive_struct(ReducerContext ctx, List<EveryPrimitiveStruct> s)
public static void insert_vec_every_primitive_struct(
ReducerContext ctx,
List<EveryPrimitiveStruct> s
)
{
ctx.Db.vec_every_primitive_struct.Insert(new VecEveryPrimitiveStruct { s = s });
}
Expand Down Expand Up @@ -725,7 +728,10 @@ public partial struct OptionEveryPrimitiveStruct
}

[SpacetimeDB.Reducer]
public static void insert_option_every_primitive_struct(ReducerContext ctx, EveryPrimitiveStruct? s)
public static void insert_option_every_primitive_struct(
ReducerContext ctx,
EveryPrimitiveStruct? s
)
{
ctx.Db.option_every_primitive_struct.Insert(new OptionEveryPrimitiveStruct { s = s });
}
Expand Down Expand Up @@ -1615,7 +1621,9 @@ public static void insert_caller_one_identity(ReducerContext ctx)
[SpacetimeDB.Reducer]
public static void insert_caller_vec_identity(ReducerContext ctx)
{
ctx.Db.vec_identity.Insert(new VecIdentity { i = new List<Identity> { ctx.CallerIdentity } });
ctx.Db.vec_identity.Insert(
new VecIdentity { i = new List<Identity> { ctx.CallerIdentity } }
);
}

[SpacetimeDB.Reducer]
Expand All @@ -1633,7 +1641,7 @@ public static void insert_caller_pk_identity(ReducerContext ctx, int data)
[SpacetimeDB.Reducer]
public static void insert_caller_one_address(ReducerContext ctx)
{
ctx.Db.one_address.Insert(new OneAddress { a = (Address)ctx.CallerAddress!, });
ctx.Db.one_address.Insert(new OneAddress { a = (Address)ctx.CallerAddress! });
}

[SpacetimeDB.Reducer]
Expand All @@ -1650,7 +1658,9 @@ public static void insert_caller_vec_address(ReducerContext ctx)
[SpacetimeDB.Reducer]
public static void insert_caller_unique_address(ReducerContext ctx, int data)
{
ctx.Db.unique_address.Insert(new UniqueAddress { a = (Address)ctx.CallerAddress!, data = data });
ctx.Db.unique_address.Insert(
new UniqueAddress { a = (Address)ctx.CallerAddress!, data = data }
);
}

[SpacetimeDB.Reducer]
Expand Down Expand Up @@ -1713,43 +1723,47 @@ public static void insert_large_table(
EveryVecStruct v
)
{
ctx.Db.large_table.Insert(new LargeTable
{
a = a,
b = b,
c = c,
d = d,
e = e,
f = f,
g = g,
h = h,
i = i,
j = j,
k = k,
l = l,
m = m,
n = n,
o = o,
p = p,
q = q,
r = r,
s = s,
t = t,
u = u,
v = v,
});
ctx.Db.large_table.Insert(
new LargeTable
{
a = a,
b = b,
c = c,
d = d,
e = e,
f = f,
g = g,
h = h,
i = i,
j = j,
k = k,
l = l,
m = m,
n = n,
o = o,
p = p,
q = q,
r = r,
s = s,
t = t,
u = u,
v = v,
}
);
}

[SpacetimeDB.Reducer]
public static void insert_primitives_as_strings(ReducerContext ctx, EveryPrimitiveStruct s)
{
ctx.Db.vec_string.Insert(new VecString
{
s = typeof(EveryPrimitiveStruct)
.GetFields()
.Select(f => f.GetValue(s)!.ToString()!.ToLowerInvariant())
.ToList()
});
ctx.Db.vec_string.Insert(
new VecString
{
s = typeof(EveryPrimitiveStruct)
.GetFields()
.Select(f => f.GetValue(s)!.ToString()!.ToLowerInvariant())
.ToList(),
}
);
}

[SpacetimeDB.Table(Name = "table_holds_table", Public = true)]
Expand All @@ -1768,17 +1782,26 @@ public static void insert_table_holds_table(ReducerContext ctx, OneU8 a, VecU8 b
[SpacetimeDB.Reducer]
public static void no_op_succeeds(ReducerContext ctx) { }

[SpacetimeDB.Table(Name = "scheduled_table", Scheduled = nameof(send_scheduled_message))]
[SpacetimeDB.Table(
Name = "scheduled_table",
Scheduled = nameof(send_scheduled_message),
ScheduledAt = nameof(scheduled_at),
Public = true
)]
public partial struct ScheduledTable
{
[PrimaryKey]
[AutoInc]
public ulong scheduled_id;
public ScheduleAt scheduled_at;
public string text;
}

[SpacetimeDB.Reducer]
public static void send_scheduled_message(ReducerContext ctx, ScheduledTable arg)
{
ulong id = arg.ScheduledId;
SpacetimeDB.ScheduleAt scheduleAt = arg.ScheduledAt;
ulong id = arg.scheduled_id;
SpacetimeDB.ScheduleAt scheduleAt = arg.scheduled_at;
string text = arg.text;
}

Expand All @@ -1790,7 +1813,10 @@ public partial struct IndexedTable
}

[SpacetimeDB.Table(Name = "indexed_table_2")]
[SpacetimeDB.Index(Name = "player_id_snazz_index", BTree = [nameof(player_id), nameof(player_snazz)])]
[SpacetimeDB.Index(
Name = "player_id_snazz_index",
BTree = [nameof(player_id), nameof(player_snazz)]
)]
public partial struct IndexedTable2
{
uint player_id;
Expand Down

0 comments on commit f3bdd2c

Please sign in to comment.