Update NativeAOT-LLVM infrastructure to current ABI (#4515)

## Summary

- Update the experimental NativeAOT-LLVM build path
(`EXPERIMENTAL_WASM_AOT=1`) to include all host function imports from
ABI versions 10.0 through 10.4
- Fix the compiler package reference to work on both Windows x64 and
Linux x64 (was hardcoded to Windows only)
- Add a CI smoketest to verify AOT builds work on Linux x64

## Context

See #4514 for the full writeup on the C# AOT situation. The
`wasi-experimental` workload that all C# module builds depend on is
deprecated and removed from .NET 9+. NativeAOT-LLVM is the recommended
path forward for ahead-of-time compilation of C# to WebAssembly.

The existing NativeAOT-LLVM support (added by RReverser in #713) was
stale: missing imports added since then and a Windows-only package
reference.

## Changes

**`SpacetimeDB.Runtime.targets`:**
- Add 10 missing `WasmImport` declarations across spacetime_10.0 through
10.4
- Replace `runtime.win-x64.Microsoft.DotNet.ILCompiler.LLVM` with
`runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM`
so it resolves correctly on Linux x64 as well
- Use explicit version strings instead of the `$(SpacetimeNamespace)`
variable

**`ci.yml`:**
- Add AOT build smoketest step in the `csharp-testsuite` job

## Test plan

- [x] CI smoketest passes: `EXPERIMENTAL_WASM_AOT=1 dotnet publish -c
Release` builds successfully on Linux x64
- [ ] Existing C# tests continue to pass (no changes to the default
interpreter path)

---------

Signed-off-by: Ryan <r.ekhoff@clockworklabs.io>
Co-authored-by: Ryan <r.ekhoff@clockworklabs.io>
Co-authored-by: Jason Larabie <jason@clockworklabs.io>
Co-authored-by: John Detter <4099508+jdetter@users.noreply.github.com>
This commit is contained in:
Tyler Cloutier
2026-04-21 17:47:16 -04:00
committed by GitHub
parent 59ac77970f
commit 78d6b6f7dd
43 changed files with 2465 additions and 45 deletions
+4
View File
@@ -98,6 +98,10 @@ jobs:
cd $env:USERPROFILE\emsdk
.\emsdk install 4.0.21
.\emsdk activate 4.0.21
# Add emscripten to PATH for subsequent steps and subprocesses
$emsdkPath = "$env:USERPROFILE\emsdk\upstream\emscripten"
Add-Content -Path $env:GITHUB_PATH -Value $emsdkPath
Write-Host "Added $emsdkPath to PATH"
- name: Install psql (Windows)
if: runner.os == 'Windows'
+174
View File
@@ -0,0 +1,174 @@
# Using NativeAOT-LLVM with SpacetimeDB C# Modules
This guide provides instructions for enabling NativeAOT-LLVM compilation for C# SpacetimeDB modules, which can provide performance improvements.
## Overview
NativeAOT-LLVM compiles C# modules to native WebAssembly (WASM) instead of using the Mono runtime.
> [!WARNING]
> This is currently only supported for Windows server modules and is experimental.
## Prerequisites
- **.NET SDK 8.x** (same version used by SpacetimeDB)
- **Emscripten SDK (EMSDK)** installed (must contain `upstream/emscripten/emcc.bat`)
- **(Optional) Binaryen (wasm-opt)** installed and on `PATH` (recommended: `version_116`)
- **Windows** - NativeAOT-LLVM is currently only supported for Windows server modules
## Prerequisites Installation
### Install Emscripten SDK (EMSDK)
The Emscripten SDK is required for NativeAOT-LLVM compilation:
1. **Download and extract** the Emscripten SDK from `https://github.com/emscripten-core/emsdk`
- Example path: `D:\Tools\emsdk`
2. **Set environment variable** (optional - the CLI will detect it automatically):
```
$env:EMSDK="D:\Tools\emsdk"
```
### Install Binaryen (Optional)
Binaryen provides `wasm-opt` for WASM optimization (recommended for performance):
1. Download Binaryen https://github.com/WebAssembly/binaryen/releases/tag/version_116 for Windows
2. Extract to e.g. `D:\Tools\binaryen`
3. Add `D:\Tools\binaryen\bin` to `PATH`
To temporarily add to your current PowerShell session:
```
$env:PATH += ";D:\Tools\binaryen\bin"
```
4. Verify:
```
wasm-opt --version
```
## Creating a New NativeAOT Project
When creating a new C# project, use the `--native-aot` flag:
```
spacetime init --lang csharp --native-aot my-native-aot-project
```
This automatically:
- Creates a C# project with the required package references
- Generates a `spacetime.json` with `"native-aot": true`
- Configures the project for NativeAOT-LLVM compilation
## Converting an Existing Project
1. **Update spacetime.json**
Add `"native-aot": true` to your `spacetime.json`:
```json
{
"module": "your-module-name",
"native-aot": true
}
```
**Note:** Once `spacetime.json` has `"native-aot": true`, you can simply run `spacetime publish` without the `--native-aot` flag. The CLI will automatically detect the configuration and use NativeAOT compilation.
2. **Ensure NuGet feed is configured**
NativeAOT-LLVM packages come from **dotnet-experimental**. Add to `NuGet.Config`:
```xml
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
```
3. **Add NativeAOT package references**
Add this `ItemGroup` to your `.csproj`:
```xml
<ItemGroup Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'">
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0-*" Condition="'$(ILLinkTargetsPath)' == ''" />
<PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
<PackageReference Include="runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
</ItemGroup>
```
Your complete `.csproj` should look like:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>wasi-wasm</RuntimeIdentifier>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SpacetimeDB.Runtime" Version="2.0.*" />
</ItemGroup>
<ItemGroup Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'">
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0-*" Condition="'$(ILLinkTargetsPath)' == ''" />
<PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
<PackageReference Include="runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
</ItemGroup>
</Project>
```
## Publishing Your NativeAOT Module
After completing either the **Creating a New NativeAOT Project** or **Converting an Existing Project** steps above, you can publish your module normally:
```
# From your project directory
spacetime publish your-database-name
```
If you have `"native-aot": true` in your `spacetime.json`, the CLI will automatically detect this and use NativeAOT compilation. Alternatively, you can use:
```
spacetime publish --native-aot your-database-name
```
The CLI will display "Using NativeAOT-LLVM compilation (experimental)" when NativeAOT is enabled.
## Troubleshooting
### Package source mapping enabled
If you have **package source mapping** enabled in `NuGet.Config`, add mappings for the LLVM packages:
```xml
<packageSourceMapping>
<packageSource key="bsatn-runtime">
<package pattern="SpacetimeDB.BSATN.Runtime" />
</packageSource>
<packageSource key="SpacetimeDB.Runtime">
<package pattern="SpacetimeDB.Runtime" />
</packageSource>
<packageSource key="dotnet-experimental">
<package pattern="Microsoft.DotNet.ILCompiler.LLVM" />
<package pattern="runtime.*" />
</packageSource>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
```
### wasi-experimental workload install fails
If the CLI cannot install the `wasi-experimental` workload automatically, install it manually:
```
dotnet workload install wasi-experimental
```
### Duplicate PackageReference warning
You may see a `NU1504` warning about duplicate `PackageReference` items. This is expected and non-blocking.
### Code generation failed
If you see errors like "Code generation failed for method", ensure:
1. You're using `SpacetimeDB.Runtime` version 2.0.4 or newer
2. All required package references are in your `.csproj`
3. The `dotnet-experimental` feed is configured in `NuGet.Config`
@@ -206,6 +206,36 @@ partial class RawModuleDefV10
public static class Module
{
// Workaround for NativeAOT-LLVM IL scanner bug:
// The scanner fails to compute vtables for TaggedEnum<T> base types when
// concrete subtypes are only encountered indirectly (e.g., through Equals
// calls on types containing TaggedEnum fields). This occurs when no user
// table has indexes/primary keys, so RawIndexAlgorithm is never directly
// constructed in user code.
// By referencing concrete TaggedEnum subtypes here, we ensure the IL scanner
// always processes their vtables. One variant per TaggedEnum is sufficient.
[System.Runtime.CompilerServices.MethodImpl(
System.Runtime.CompilerServices.MethodImplOptions.NoInlining
)]
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.
if (Environment.TickCount < 0 && Environment.TickCount > 0)
{
_ = new RawIndexAlgorithm.BTree(null!);
_ = new RawConstraintDataV9.Unique(null!);
_ = new RawModuleDef.V10(null!);
_ = new RawModuleDefV10Section.Typespace(null!);
_ = new ExplicitNameEntry.Table(null!);
_ = new MiscModuleExport.TypeAlias(null!);
_ = new RawMiscModuleExportV9.ColumnDefaultValue(null!);
_ = new SpacetimeDB.Filter.Sql(null!);
_ = new ViewResultHeader.RowData(default);
}
}
private static readonly RawModuleDefV10 moduleDef = new();
private static readonly List<IReducer> reducers = [];
@@ -419,6 +449,7 @@ public static class Module
public static void __describe_module__(BytesSink description)
{
EnsureNativeAotTypeRoots();
try
{
var module = moduleDef.BuildModuleDefinition();
@@ -12,6 +12,7 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RootNamespace>SpacetimeDB</RootNamespace>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<RestoreAdditionalProjectSources Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'">https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json;$(RestoreAdditionalProjectSources)</RestoreAdditionalProjectSources>
</PropertyGroup>
<ItemGroup>
@@ -25,6 +26,13 @@
<ProjectReference Include="../Codegen/Codegen.csproj" ReferenceOutputAssembly="false" />
</ItemGroup>
<!-- These must be explicit package dependencies so NuGet consumers can resolve the LLVM toolchain. -->
<ItemGroup Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'">
<PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" IncludeAssets="All" />
<PackageReference Include="runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" IncludeAssets="All" />
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0-*" Condition="'$(ILLinkTargetsPath)' == ''" IncludeAssets="All" />
</ItemGroup>
<ItemGroup>
<None Include="README.md" Pack="true" PackagePath="" />
<None Include="build/*" Pack="true" PackagePath="build" />
@@ -1,32 +1,50 @@
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import
Project="$(PkgMicrosoft_DotNet_ILCompiler_LLVM)\build\Microsoft.DotNet.ILCompiler.LLVM.targets"
Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1' and '$(ILCompilerTargetsPath)' == '' and '$(PkgMicrosoft_DotNet_ILCompiler_LLVM)' != '' and Exists('$(PkgMicrosoft_DotNet_ILCompiler_LLVM)\build\Microsoft.DotNet.ILCompiler.LLVM.targets')" />
<ItemGroup Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'">
<NativeLibrary Include="$(MSBuildThisFileDirectory)..\bindings.c" />
<UnmanagedEntryPointsAssembly Include="SpacetimeDB.Runtime" />
<WasmImport Include="$(SpacetimeNamespace)!table_id_from_name" />
<WasmImport Include="$(SpacetimeNamespace)!index_id_from_name" />
<WasmImport Include="$(SpacetimeNamespace)!datastore_table_row_count" />
<WasmImport Include="$(SpacetimeNamespace)!datastore_table_scan_bsatn" />
<WasmImport Include="$(SpacetimeNamespace)!datastore_index_scan_range_bsatn" />
<WasmImport Include="$(SpacetimeNamespace)!datastore_btree_scan_bsatn" />
<WasmImport Include="$(SpacetimeNamespace)!row_iter_bsatn_advance" />
<WasmImport Include="$(SpacetimeNamespace)!row_iter_bsatn_close" />
<WasmImport Include="$(SpacetimeNamespace)!datastore_insert_bsatn" />
<WasmImport Include="$(SpacetimeNamespace)!datastore_delete_by_index_scan_range_bsatn" />
<WasmImport Include="$(SpacetimeNamespace)!datastore_delete_by_btree_scan_bsatn" />
<WasmImport Include="$(SpacetimeNamespace)!datastore_delete_all_by_eq_bsatn" />
<WasmImport Include="$(SpacetimeNamespace)!bytes_source_read" />
<WasmImport Include="$(SpacetimeNamespace)!bytes_sink_write" />
<WasmImport Include="$(SpacetimeNamespace)!console_log" />
<WasmImport Include="$(SpacetimeNamespace)!console_timer_start" />
<WasmImport Include="$(SpacetimeNamespace)!console_timer_end" />
<WasmImport Include="$(SpacetimeNamespace)!volatile_nonatomic_schedule_immediate" />
<WasmImport Include="$(SpacetimeNamespace)!datastore_clear" />
<!-- spacetime_10.0 imports -->
<WasmImport Include="spacetime_10.0!table_id_from_name" />
<WasmImport Include="spacetime_10.0!index_id_from_name" />
<WasmImport Include="spacetime_10.0!datastore_table_row_count" />
<WasmImport Include="spacetime_10.0!datastore_table_scan_bsatn" />
<WasmImport Include="spacetime_10.0!datastore_index_scan_range_bsatn" />
<WasmImport Include="spacetime_10.0!datastore_btree_scan_bsatn" />
<WasmImport Include="spacetime_10.0!row_iter_bsatn_advance" />
<WasmImport Include="spacetime_10.0!row_iter_bsatn_close" />
<WasmImport Include="spacetime_10.0!datastore_insert_bsatn" />
<WasmImport Include="spacetime_10.0!datastore_update_bsatn" />
<WasmImport Include="spacetime_10.0!datastore_delete_by_index_scan_range_bsatn" />
<WasmImport Include="spacetime_10.0!datastore_delete_by_btree_scan_bsatn" />
<WasmImport Include="spacetime_10.0!datastore_delete_all_by_eq_bsatn" />
<WasmImport Include="spacetime_10.0!bytes_source_read" />
<WasmImport Include="spacetime_10.0!bytes_sink_write" />
<WasmImport Include="spacetime_10.0!console_log" />
<WasmImport Include="spacetime_10.0!console_timer_start" />
<WasmImport Include="spacetime_10.0!console_timer_end" />
<WasmImport Include="spacetime_10.0!volatile_nonatomic_schedule_immediate" />
<WasmImport Include="spacetime_10.0!identity" />
<PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
<PackageReference Include="runtime.win-x64.Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0-*" Condition="'$(ILLinkTargetsPath)' == ''" />
<!-- spacetime_10.1 imports -->
<WasmImport Include="spacetime_10.1!bytes_source_remaining_length" />
<!-- spacetime_10.2 imports -->
<WasmImport Include="spacetime_10.2!get_jwt" />
<!-- spacetime_10.3 imports -->
<WasmImport Include="spacetime_10.3!procedure_start_mut_tx" />
<WasmImport Include="spacetime_10.3!procedure_commit_mut_tx" />
<WasmImport Include="spacetime_10.3!procedure_abort_mut_tx" />
<WasmImport Include="spacetime_10.3!procedure_http_request" />
<!-- spacetime_10.4 imports -->
<WasmImport Include="spacetime_10.4!datastore_index_scan_point_bsatn" />
<WasmImport Include="spacetime_10.4!datastore_delete_by_index_scan_point_bsatn" />
<CustomLinkerArg Include="-DEXPERIMENTAL_WASM_AOT" />
</ItemGroup>
+120 -3
View File
@@ -116,6 +116,7 @@ pub struct TemplateConfig {
pub github_repo: Option<String>,
pub template_def: Option<TemplateDefinition>,
pub use_local: bool,
pub native_aot: bool,
}
#[derive(Debug, Clone, Default)]
@@ -131,6 +132,8 @@ pub struct InitOptions {
pub non_interactive: bool,
/// When true, suppress the "Next steps" message after init (e.g. when called from `spacetime dev`).
pub skip_next_steps: bool,
/// When true, configure C# projects for NativeAOT-LLVM compilation.
pub native_aot: bool,
}
impl InitOptions {
@@ -146,6 +149,7 @@ impl InitOptions {
local: args.get_flag("local"),
non_interactive: args.get_flag("non-interactive"),
skip_next_steps: false,
native_aot: args.get_flag("native-aot"),
}
}
}
@@ -189,6 +193,12 @@ pub fn cli() -> clap::Command {
.action(clap::ArgAction::SetTrue)
.help("Run in non-interactive mode"),
)
.arg(
Arg::new("native-aot")
.long("native-aot")
.action(clap::ArgAction::SetTrue)
.help("Configure C# project for NativeAOT-LLVM compilation (experimental, Windows only)"),
)
}
pub async fn fetch_templates_list() -> anyhow::Result<Vec<TemplateDefinition>> {
@@ -346,6 +356,7 @@ fn create_template_config_from_template_str(
github_repo: None,
template_def: Some(template.clone()),
use_local: true,
native_aot: false,
})
} else {
// GitHub template
@@ -358,6 +369,7 @@ fn create_template_config_from_template_str(
github_repo: Some(template_str.to_string()),
template_def: None,
use_local: true,
native_aot: false,
})
}
}
@@ -525,7 +537,13 @@ pub async fn exec_with_options(config: &mut Config, options: &InitOptions) -> an
)?;
init_from_template(&template_config, &template_config.project_path, is_server_only).await?;
if let Some(path) = create_default_spacetime_config_if_missing(&project_path)? {
// Add NativeAOT-LLVM package references to C# projects if --native-aot was specified
if options.native_aot && template_config.server_lang == Some(ServerLanguage::Csharp) {
let server_dir = template_config.project_path.join("spacetimedb");
add_native_aot_packages_to_csproj(&server_dir)?;
}
if let Some(path) = create_default_spacetime_config_if_missing(&project_path, options.native_aot)? {
println!("{} Created {}", "".green(), path.display());
}
@@ -605,7 +623,10 @@ fn get_local_database_name(options: &InitOptions, project_name: &str, is_interac
Ok(database_name)
}
fn create_default_spacetime_config_if_missing(project_path: &Path) -> anyhow::Result<Option<PathBuf>> {
fn create_default_spacetime_config_if_missing(
project_path: &Path,
native_aot: bool,
) -> anyhow::Result<Option<PathBuf>> {
let config_path = project_path.join(CONFIG_FILENAME);
if config_path.exists() {
return Ok(None);
@@ -622,6 +643,10 @@ fn create_default_spacetime_config_if_missing(project_path: &Path) -> anyhow::Re
.insert("module-path".to_string(), json!("./spacetimedb"));
}
if native_aot {
config.additional_fields.insert("native-aot".to_string(), json!(true));
}
Ok(Some(config.save_to_dir(project_path)?))
}
@@ -696,6 +721,7 @@ async fn get_template_config_non_interactive(
github_repo: None,
template_def: None,
use_local: true,
native_aot: false,
})
}
@@ -761,6 +787,7 @@ async fn get_template_config_interactive(
github_repo: None,
template_def: None,
use_local: true,
native_aot: false,
});
}
@@ -845,6 +872,7 @@ async fn get_template_config_interactive(
github_repo: None,
template_def: Some(template.clone()),
use_local: true,
native_aot: false,
});
} else if client_selection == github_clone_index {
return loop {
@@ -889,6 +917,7 @@ async fn get_template_config_interactive(
github_repo: None,
template_def: None,
use_local: true,
native_aot: false,
});
} else {
unreachable!("Invalid selection index");
@@ -1648,6 +1677,21 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> anyhow::Result<PathB
anyhow::bail!("Cannot specify both --template and --lang. Language is determined by the template.");
}
// Validate that --native-aot is only used with C# projects
if options.native_aot {
if let Some(lang) = server_lang
&& lang.to_lowercase() != "csharp"
&& lang.to_lowercase() != "c#"
{
anyhow::bail!("--native-aot is only supported for C# projects (--lang csharp)");
}
// Print warning about Windows-only support
println!(
"{}",
"Note: NativeAOT-LLVM is experimental and building for this platform is currently only supported on Windows.".yellow()
);
}
if !is_interactive {
// In non-interactive mode, validate all required args are present
if project_name_arg.is_none() {
@@ -1713,6 +1757,62 @@ pub fn init_csharp_project(project_path: &Path) -> anyhow::Result<()> {
Ok(())
}
/// Adds NativeAOT-LLVM package references to an existing C# .csproj file and creates NuGet.Config.
/// This is called when `--native-aot` is specified during `spacetime init`.
fn add_native_aot_packages_to_csproj(project_path: &Path) -> anyhow::Result<()> {
let csproj_path = project_path.join("StdbModule.csproj");
if !csproj_path.exists() {
anyhow::bail!("Could not find StdbModule.csproj at {}", csproj_path.display());
}
let content = std::fs::read_to_string(&csproj_path)?;
// The NativeAOT-LLVM ItemGroup to add
let native_aot_item_group = r#"
<ItemGroup Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'">
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0-*" Condition="'$(ILLinkTargetsPath)' == ''" />
<PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
<PackageReference Include="runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
</ItemGroup>
"#;
// Insert the ItemGroup before the closing </Project> tag
let new_content = if let Some(pos) = content.rfind("</Project>") {
let (before, after) = content.split_at(pos);
format!("{}{}{}", before.trim_end(), native_aot_item_group, after)
} else {
anyhow::bail!("Invalid .csproj file: missing </Project> tag");
};
std::fs::write(&csproj_path, new_content)?;
println!(
"{} Added NativeAOT-LLVM package references to {}",
"".green(),
csproj_path.display()
);
// Create NuGet.Config with the dotnet-experimental feed required for NativeAOT-LLVM packages
let nuget_config_path = project_path.join("NuGet.Config");
let nuget_config_content = r#"<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<clear />
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
</packageSources>
</configuration>
"#;
std::fs::write(&nuget_config_path, nuget_config_content)?;
println!(
"{} Created {} with dotnet-experimental feed",
"".green(),
nuget_config_path.display()
);
Ok(())
}
pub fn init_typescript_project(project_path: &Path) -> anyhow::Result<()> {
let export_files = vec![
(
@@ -2015,7 +2115,7 @@ mod tests {
let project_path = temp.path();
std::fs::create_dir_all(project_path.join("spacetimedb")).unwrap();
let created = create_default_spacetime_config_if_missing(project_path)
let created = create_default_spacetime_config_if_missing(project_path, false)
.unwrap()
.expect("expected config to be created");
assert_eq!(created, project_path.join("spacetime.json"));
@@ -2028,6 +2128,23 @@ mod tests {
parsed.get("module-path").and_then(|v| v.as_str()),
Some("./spacetimedb")
);
assert!(parsed.get("native-aot").is_none());
}
#[test]
fn test_create_default_spacetime_config_with_native_aot() {
let temp = tempfile::TempDir::new().unwrap();
let project_path = temp.path();
std::fs::create_dir_all(project_path.join("spacetimedb")).unwrap();
let created = create_default_spacetime_config_if_missing(project_path, true)
.unwrap()
.expect("expected config to be created");
assert_eq!(created, project_path.join("spacetime.json"));
let content = std::fs::read_to_string(&created).unwrap();
let parsed: serde_json::Value = serde_json::from_str(&content).unwrap();
assert_eq!(parsed.get("native-aot").and_then(|v| v.as_bool()), Some(true));
}
#[test]
+54 -15
View File
@@ -12,8 +12,8 @@ use std::{env, fs};
use crate::common_args::ClearMode;
use crate::config::Config;
use crate::spacetime_config::{
find_and_load_with_env, CommandConfig, CommandSchema, CommandSchemaBuilder, FlatTarget, Key, LoadedConfig,
SpacetimeConfig,
find_and_load_with_env, find_and_load_with_env_from, CommandConfig, CommandSchema, CommandSchemaBuilder,
FlatTarget, Key, LoadedConfig, SpacetimeConfig,
};
use crate::util::{add_auth_header_opt, get_auth_header, strip_verbatim_prefix, AuthHeader, ResponseExt};
use crate::util::{decode_identity, y_or_n};
@@ -33,6 +33,7 @@ pub fn build_publish_schema(command: &clap::Command) -> Result<CommandSchema, an
.key(Key::new("anon_identity"))
.key(Key::new("parent"))
.key(Key::new("organization"))
.key(Key::new("native_aot").module_specific())
.exclude("clear-database")
.exclude("force")
.exclude("no_config")
@@ -76,19 +77,30 @@ pub fn get_filtered_publish_configs<'a>(
.collect();
if matched.is_empty() {
anyhow::bail!(
"No database target matches '{}'. Available databases: {}",
cli_database,
spacetime_config
.collect_all_targets_with_inheritance()
.iter()
.filter_map(|t| t.fields.get("database").and_then(|v| v.as_str()))
.collect::<Vec<_>>()
.join(", ")
);
// When there is exactly one target in the config and the CLI-provided
// database name doesn't match it, use that target's settings (e.g.
// native-aot, module-path, build-options) and let CommandConfig merge
// the CLI database name on top. This handles the common case where
// `spacetime init` generated a random database suffix that differs
// from the name the user passes on the CLI, while still picking up
// module-specific config.
let all_targets = spacetime_config.collect_all_targets_with_inheritance();
if all_targets.len() == 1 {
all_targets
} else {
anyhow::bail!(
"No database target matches '{}'. Available databases: {}",
cli_database,
all_targets
.iter()
.filter_map(|t| t.fields.get("database").and_then(|v| v.as_str()))
.collect::<Vec<_>>()
.join(", ")
);
}
} else {
matched
}
matched
} else {
all_targets
};
@@ -223,6 +235,12 @@ i.e. only lowercase ASCII letters and numbers, separated by dashes."),
.action(Set)
.help("Environment name for config file layering (e.g., dev, staging)")
)
.arg(
Arg::new("native_aot")
.long("native-aot")
.action(SetTrue)
.help("Use NativeAOT-LLVM compilation for C# modules (experimental, Windows only)")
)
.after_help("Run `spacetime help publish` for more detailed information.")
}
@@ -293,13 +311,23 @@ pub async fn exec_with_options(
let env = args.get_one::<String>("env").map(|s| s.as_str());
// Get publish configs (from spacetime.json or empty)
let owned_loaded;
let mut owned_loaded;
let loaded_config_ref = if no_config {
None
} else if let Some(pre) = pre_loaded_config {
Some(pre)
} else {
// First, try to load config from current directory
owned_loaded = find_and_load_with_env(env)?;
// If no config found and --module-path is specified, try loading from module path.
if owned_loaded.is_none()
&& args.contains_id("module_path")
&& let Some(module_path) = args.get_one::<PathBuf>("module_path")
{
owned_loaded = find_and_load_with_env_from(env, module_path.clone())?;
}
owned_loaded.as_ref().inspect(|loaded| {
if !quiet_config {
for path in &loaded.loaded_files {
@@ -420,6 +448,7 @@ async fn execute_publish_configs<'a>(
let parent = parent_opt.as_deref();
let org_opt = command_config.get_one::<String>("organization")?;
let org = org_opt.as_deref();
let native_aot = command_config.get_one::<bool>("native_aot")?.unwrap_or(false);
// If the user didn't specify an identity and we didn't specify an anonymous identity, then
// we want to use the default identity
@@ -447,6 +476,16 @@ async fn execute_publish_configs<'a>(
println!("(JS) Skipping build. Instead we are publishing {}", path.display());
(path.clone(), "Js")
} else {
// Set EXPERIMENTAL_WASM_AOT environment variable if native_aot is enabled
// This is read by the C# build system (MSBuild) and by csharp.rs to determine output paths
if native_aot {
println!("Using NativeAOT-LLVM compilation (experimental)");
// SAFETY: We are single-threaded at this point and no other code is reading
// this environment variable concurrently.
unsafe {
env::set_var("EXPERIMENTAL_WASM_AOT", "1");
}
}
build::exec_with_argstring(
path_to_project
.as_ref()
@@ -223,9 +223,7 @@ fn cli_publish_with_config_but_no_match_uses_cli_args() {
// Create a config with a different database name
let config_content = r#"{
"publish": {
"database": "config-db-name"
}
"database": "config-db-name"
}"#;
std::fs::write(module_dir.join("spacetime.json"), config_content).expect("failed to write config");
@@ -0,0 +1,63 @@
#![allow(clippy::disallowed_macros)]
use spacetimedb_guard::ensure_binaries_built;
use spacetimedb_smoketests::{have_emscripten, require_dotnet, workspace_root};
use std::process::Command;
/// Test NativeAOT-LLVM build path for C# modules.
/// Requires emscripten to be installed.
/// Only runs on Windows since runtime.linux-x64.Microsoft.DotNet.ILCompiler.LLVM
/// is not available on the dotnet-experimental NuGet feed.
#[test]
fn test_build_csharp_module_aot() {
require_dotnet!();
// NativeAOT-LLVM is only available on Windows
if std::env::consts::OS != "windows" {
eprintln!("Skipping AOT test - NativeAOT-LLVM for .NET 8 only available on Windows");
return;
}
// Check for emscripten - fail with helpful message if not available
// Uses have_emscripten() which checks for both `emcc` and `emcc.bat` on Windows
if !have_emscripten() {
panic!(
"NativeAOT-LLVM test requires emscripten but it was not found.\n\
Install from: https://emscripten.org/docs/getting_started/downloads.html\n\
Or ensure `emcc` is in your PATH."
);
}
let workspace = workspace_root();
let _cli_path = ensure_binaries_built();
// Create isolated NuGet packages folder to avoid file lock conflicts
// NativeAOT-LLVM packages contain DLLs that stay locked and interfere with other tests
let nuget_packages_dir = tempfile::tempdir().expect("Failed to create temp directory for NuGet packages");
// Set EXPERIMENTAL_WASM_AOT=1 for this specific build
// Build sdk-test-cs with NativeAOT-LLVM
let mut cmd = Command::new("dotnet");
cmd.arg("publish")
.arg("-c")
.arg("Release")
.current_dir(workspace.join("modules/sdk-test-cs"))
.env("EXPERIMENTAL_WASM_AOT", "1")
.env("NUGET_PACKAGES", nuget_packages_dir.path());
let output = cmd.output().expect("Failed to run dotnet publish");
assert!(
output.status.success(),
"NativeAOT-LLVM publish failed:\nstdout: {}\nstderr: {}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
// Clean up temp dir explicitly to verify no file locks remain
// This ensures subsequent tests can clear NuGet locals without conflicts
drop(nuget_packages_dir);
// Verify StdbModule.wasm was produced
let wasm_path = workspace.join("modules/sdk-test-cs/bin/Release/net8.0/wasi-wasm/publish/StdbModule.wasm");
assert!(wasm_path.exists(), "StdbModule.wasm not found at {:?}", wasm_path);
}
@@ -9,6 +9,7 @@ mod client_connection_errors;
mod confirmed_reads;
mod connect_disconnect_from_cli;
mod create_project;
mod csharp_aot_module;
mod csharp_module;
mod database_lock;
mod default_module_clippy;
@@ -115,6 +115,7 @@ Run `spacetime help publish` for more detailed information.
* `-y`, `--yes` — Run non-interactively wherever possible. This will answer "yes" to almost all prompts, but will sometimes answer "no" to preserve non-interactivity (e.g. when prompting whether to log in with spacetimedb.com).
* `--no-config` — Ignore spacetime.json configuration
* `--env <ENV>` — Environment name for config file layering (e.g., dev, staging)
* `--native-aot` — Use NativeAOT-LLVM compilation for C# modules (experimental, Windows only)
@@ -419,6 +420,7 @@ Initializes a new spacetime project.
* `-t`, `--template <TEMPLATE>` — Template ID or GitHub repository (owner/repo or URL)
* `--local` — Use local deployment instead of Maincloud
* `--non-interactive` — Run in non-interactive mode
* `--native-aot` — Configure C# project for NativeAOT-LLVM compilation (experimental, Windows only)
+5 -1
View File
@@ -9,5 +9,9 @@
<ProjectReference Include="../../crates/bindings-csharp/Codegen/Codegen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<ProjectReference Include="../../crates/bindings-csharp/Runtime/Runtime.csproj" />
</ItemGroup>
<ItemGroup Condition="'$(EXPERIMENTAL_WASM_AOT)' == '1'">
<PackageReference Include="Microsoft.NET.ILLink.Tasks" Version="8.0.0-*" Condition="'$(ILLinkTargetsPath)' == ''" />
<PackageReference Include="Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" GeneratePathProperty="true" />
<PackageReference Include="runtime.$(NETCoreSdkPortableRuntimeIdentifier).Microsoft.DotNet.ILCompiler.LLVM" Version="8.0.0-*" />
</ItemGroup>
</Project>
@@ -82,6 +82,12 @@ void OnConnected(DbConnection conn, Identity identity, string authToken)
.AddQuery(qb => qb.From.IenumerablePlayersFromIter())
.AddQuery(qb => qb.From.IenumerableAdminsFromFilter())
.AddQuery(qb => qb.From.IenumerablePlayersWithLevels())
.AddQuery(qb => qb.From.EqualityPerson())
.AddQuery(qb => qb.From.EqualityProduct())
.AddQuery(qb => qb.From.EqualityOrder())
.AddQuery(qb => qb.From.PlayerAction())
.AddQuery(qb => qb.From.ActionBatch())
.AddQuery(qb => qb.From.LogEntry())
.Subscribe();
// If testing against Rust, the indexed parameter will need to be changed to: ulong indexed
@@ -296,6 +302,96 @@ void OnConnected(DbConnection conn, Identity identity, string authToken)
waiting--;
ValidateCommittedReducer("InsertViewPkMembershipSecondary", ctx);
};
// Equality test reducer callbacks
conn.Reducers.OnRunAllEqualityTests += (ReducerEventContext ctx) =>
{
Log.Info("Got RunAllEqualityTests callback");
// Note: waiting-- happens after validation in this callback
Debug.Assert(
ctx.Event.Status is Status.Committed,
$"RunAllEqualityTests should commit, got {ctx.Event.Status}"
);
// Validate results while subscription is still active
// Must happen here before UnsubscribeThen removes the data
if (ctx.Event.Status is Status.Committed)
{
Log.Info("Validating equality test results...");
ValidateEqualityResults(ctx);
Log.Info("Equality tests completed and validated");
}
// Decrement waiting here after validation is complete
waiting--;
};
conn.Reducers.OnAddEqualityPerson += (ReducerEventContext ctx, uint id, string name) =>
{
Log.Info($"Got AddEqualityPerson callback: id={id}, name={name}");
// Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
};
conn.Reducers.OnAddEqualityProduct += (ReducerEventContext ctx, uint id, string name, int price, int quantity) =>
{
Log.Info($"Got AddEqualityProduct callback: id={id}, name={name}");
// Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
};
conn.Reducers.OnRunEqualityTests += (ReducerEventContext ctx) =>
{
Log.Info("Got RunEqualityTests callback");
// Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
};
conn.Reducers.OnRunComplexEqualityTests += (ReducerEventContext ctx) =>
{
Log.Info("Got RunComplexEqualityTests callback");
// Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
};
conn.Reducers.OnRunEnumEqualityTests += (ReducerEventContext ctx) =>
{
Log.Info("Got RunEnumEqualityTests callback");
// Note: waiting-- happens in OnRunAllEqualityTests after all tests complete
};
// Equality test table insert callbacks
conn.Db.EqualityPerson.OnInsert += (EventContext ctx, EqualityPerson row) =>
{
Log.Info($"EqualityPerson.OnInsert: Id={row.Id}, Name={row.Name}");
};
conn.Db.EqualityProduct.OnInsert += (EventContext ctx, EqualityProduct row) =>
{
Log.Info($"EqualityProduct.OnInsert: Id={row.Id}, Name={row.Name}, Price={row.Price}");
};
conn.Db.EqualityOrder.OnInsert += (EventContext ctx, EqualityOrder row) =>
{
Log.Info($"EqualityOrder.OnInsert: Id={row.Id}, Customer={row.CustomerName}");
};
conn.Db.PlayerAction.OnInsert += (EventContext ctx, PlayerAction row) =>
{
var actionDesc = row.Action switch
{
GameAction.Move(var m) => $"Move({m})",
GameAction.Attack(var a) => $"Attack({a})",
GameAction.Defend(var d) => $"Defend({d})",
_ => "Unknown"
};
Log.Info($"PlayerAction.OnInsert: Id={row.Id}, Action={actionDesc}");
};
conn.Db.ActionBatch.OnInsert += (EventContext ctx, ActionBatch row) =>
{
Log.Info($"ActionBatch.OnInsert: Id={row.Id}, Actions.Count={row.Actions?.Count ?? 0}");
};
conn.Db.LogEntry.OnInsert += (EventContext ctx, LogEntry row) =>
{
Log.Info($"LogEntry.OnInsert: {row.Message} at {row.Timestamp}");
};
}
const uint MAX_ID = 10;
@@ -570,6 +666,78 @@ void ValidateIEnumerableViews(IRemoteDbContext conn)
Log.Debug("IEnumerable views validation completed successfully");
}
void ValidateEqualitySubscriptions(IRemoteDbContext conn)
{
Log.Debug("Checking equality test subscriptions...");
// Verify all equality test tables are accessible
Debug.Assert(conn.Db.EqualityPerson != null, "EqualityPerson subscription should not be null");
Debug.Assert(conn.Db.EqualityProduct != null, "EqualityProduct subscription should not be null");
Debug.Assert(conn.Db.EqualityOrder != null, "EqualityOrder subscription should not be null");
Debug.Assert(conn.Db.PlayerAction != null, "PlayerAction subscription should not be null");
Debug.Assert(conn.Db.ActionBatch != null, "ActionBatch subscription should not be null");
Debug.Assert(conn.Db.LogEntry != null, "LogEntry subscription should not be null");
Log.Debug("Equality test subscriptions validated successfully");
}
void RunEqualityTests(SubscriptionEventContext ctx)
{
Log.Debug("Running equality tests...");
// Single waiting increment for the entire equality test phase
// This ensures we wait for all callbacks AND validation before proceeding
waiting++;
// Test 1: Add a person
Log.Debug("Equality Test: Adding EqualityPerson...");
ctx.Reducers.AddEqualityPerson(1, "Alice");
// Test 2: Add a product
Log.Debug("Equality Test: Adding EqualityProduct...");
ctx.Reducers.AddEqualityProduct(1, "Widget", 999, 100);
// Test 3: Run the comprehensive equality test suite
Log.Debug("Equality Test: Running RunAllEqualityTests...");
ctx.Reducers.RunAllEqualityTests();
Log.Debug("Equality tests initiated - waiting for completion");
}
void ValidateEqualityResults(IRemoteDbContext conn)
{
Log.Debug("Validating equality test results...");
// Validate EqualityPerson data
var alice = conn.Db.EqualityPerson.Id.Find(1);
Debug.Assert(alice != null, "Alice should be in EqualityPerson table");
Debug.Assert(alice.Name == "Alice", $"Expected Alice, got {alice.Name}");
// Validate EqualityProduct data
var product = conn.Db.EqualityProduct.Id.Find(1);
Debug.Assert(product != null, "Widget should be in EqualityProduct table");
Debug.Assert(product.Name == "Widget", $"Expected Widget, got {product.Name}");
Debug.Assert(product.Price == 999, $"Expected Price=999, got {product.Price}");
// Validate PlayerAction data (inserted by TestSumTypeEquality)
var actions = conn.Db.PlayerAction.Iter().ToList();
Debug.Assert(actions.Count >= 2, $"Expected at least 2 PlayerActions, got {actions.Count}");
// Validate ActionBatch data (inserted by TestListOfNullableSumTypes)
var batches = conn.Db.ActionBatch.Iter().ToList();
Debug.Assert(batches.Count >= 1, $"Expected at least 1 ActionBatch, got {batches.Count}");
var batch = batches.FirstOrDefault(b => b.Id == 1);
Debug.Assert(batch != null, "Expected ActionBatch with Id=1");
Debug.Assert(batch.Actions != null, "ActionBatch should have non-null Actions list");
Debug.Assert(batch.Actions.Count == 4, $"Expected 4 actions (2 null), got {batch.Actions.Count}");
// Validate LogEntry data (inserted by TestTableWithoutPrimaryKey)
var logEntries = conn.Db.LogEntry.Iter().ToList();
Debug.Assert(logEntries.Count >= 2, $"Expected at least 2 LogEntries, got {logEntries.Count}");
Log.Debug("Equality test results validated successfully");
}
void ValidateSemijoinSubscriptions(IRemoteDbContext conn, Identity identity)
{
Log.Debug("Checking typed semijoin subscriptions...");
@@ -882,6 +1050,10 @@ void OnSubscriptionApplied(SubscriptionEventContext context)
ValidateIEnumerableViews(context);
ValidateSemijoinSubscriptions(context, context.Identity!.Value);
// Run equality tests
ValidateEqualitySubscriptions(context);
RunEqualityTests(context);
// Do some operations that alter row state;
// we will check that everything is in sync in the callbacks for these reducer calls.
Log.Debug("Calling Add");
@@ -0,0 +1,78 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void AddEqualityOrderHandler(ReducerEventContext ctx, uint id, string? customerName, System.Collections.Generic.List<SpacetimeDB.Types.ProductItem>? items);
public event AddEqualityOrderHandler? OnAddEqualityOrder;
public void AddEqualityOrder(uint id, string? customerName, System.Collections.Generic.List<SpacetimeDB.Types.ProductItem>? items)
{
conn.InternalCallReducer(new Reducer.AddEqualityOrder(id, customerName, items));
}
public bool InvokeAddEqualityOrder(ReducerEventContext ctx, Reducer.AddEqualityOrder args)
{
if (OnAddEqualityOrder == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnAddEqualityOrder(
ctx,
args.Id,
args.CustomerName,
args.Items
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class AddEqualityOrder : Reducer, IReducerArgs
{
[DataMember(Name = "id")]
public uint Id;
[DataMember(Name = "customer_name")]
public string? CustomerName;
[DataMember(Name = "items")]
public System.Collections.Generic.List<ProductItem>? Items;
public AddEqualityOrder(
uint Id,
string? CustomerName,
System.Collections.Generic.List<ProductItem>? Items
)
{
this.Id = Id;
this.CustomerName = CustomerName;
this.Items = Items;
}
public AddEqualityOrder()
{
}
string IReducerArgs.ReducerName => "add_equality_order";
}
}
}
@@ -0,0 +1,74 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void AddEqualityPersonHandler(ReducerEventContext ctx, uint id, string name);
public event AddEqualityPersonHandler? OnAddEqualityPerson;
public void AddEqualityPerson(uint id, string name)
{
conn.InternalCallReducer(new Reducer.AddEqualityPerson(id, name));
}
public bool InvokeAddEqualityPerson(ReducerEventContext ctx, Reducer.AddEqualityPerson args)
{
if (OnAddEqualityPerson == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnAddEqualityPerson(
ctx,
args.Id,
args.Name
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class AddEqualityPerson : Reducer, IReducerArgs
{
[DataMember(Name = "id")]
public uint Id;
[DataMember(Name = "name")]
public string Name;
public AddEqualityPerson(
uint Id,
string Name
)
{
this.Id = Id;
this.Name = Name;
}
public AddEqualityPerson()
{
this.Name = "";
}
string IReducerArgs.ReducerName => "add_equality_person";
}
}
}
@@ -0,0 +1,84 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void AddEqualityProductHandler(ReducerEventContext ctx, uint id, string name, int price, int quantity);
public event AddEqualityProductHandler? OnAddEqualityProduct;
public void AddEqualityProduct(uint id, string name, int price, int quantity)
{
conn.InternalCallReducer(new Reducer.AddEqualityProduct(id, name, price, quantity));
}
public bool InvokeAddEqualityProduct(ReducerEventContext ctx, Reducer.AddEqualityProduct args)
{
if (OnAddEqualityProduct == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnAddEqualityProduct(
ctx,
args.Id,
args.Name,
args.Price,
args.Quantity
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class AddEqualityProduct : Reducer, IReducerArgs
{
[DataMember(Name = "id")]
public uint Id;
[DataMember(Name = "name")]
public string Name;
[DataMember(Name = "price")]
public int Price;
[DataMember(Name = "quantity")]
public int Quantity;
public AddEqualityProduct(
uint Id,
string Name,
int Price,
int Quantity
)
{
this.Id = Id;
this.Name = Name;
this.Price = Price;
this.Quantity = Quantity;
}
public AddEqualityProduct()
{
this.Name = "";
}
string IReducerArgs.ReducerName => "add_equality_product";
}
}
}
@@ -0,0 +1,53 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void RunAllEqualityTestsHandler(ReducerEventContext ctx);
public event RunAllEqualityTestsHandler? OnRunAllEqualityTests;
public void RunAllEqualityTests()
{
conn.InternalCallReducer(new Reducer.RunAllEqualityTests());
}
public bool InvokeRunAllEqualityTests(ReducerEventContext ctx, Reducer.RunAllEqualityTests args)
{
if (OnRunAllEqualityTests == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnRunAllEqualityTests(
ctx
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class RunAllEqualityTests : Reducer, IReducerArgs
{
string IReducerArgs.ReducerName => "run_all_equality_tests";
}
}
}
@@ -0,0 +1,53 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void RunComplexEqualityTestsHandler(ReducerEventContext ctx);
public event RunComplexEqualityTestsHandler? OnRunComplexEqualityTests;
public void RunComplexEqualityTests()
{
conn.InternalCallReducer(new Reducer.RunComplexEqualityTests());
}
public bool InvokeRunComplexEqualityTests(ReducerEventContext ctx, Reducer.RunComplexEqualityTests args)
{
if (OnRunComplexEqualityTests == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnRunComplexEqualityTests(
ctx
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class RunComplexEqualityTests : Reducer, IReducerArgs
{
string IReducerArgs.ReducerName => "run_complex_equality_tests";
}
}
}
@@ -0,0 +1,53 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void RunEnumEqualityTestsHandler(ReducerEventContext ctx);
public event RunEnumEqualityTestsHandler? OnRunEnumEqualityTests;
public void RunEnumEqualityTests()
{
conn.InternalCallReducer(new Reducer.RunEnumEqualityTests());
}
public bool InvokeRunEnumEqualityTests(ReducerEventContext ctx, Reducer.RunEnumEqualityTests args)
{
if (OnRunEnumEqualityTests == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnRunEnumEqualityTests(
ctx
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class RunEnumEqualityTests : Reducer, IReducerArgs
{
string IReducerArgs.ReducerName => "run_enum_equality_tests";
}
}
}
@@ -0,0 +1,53 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void RunEqualityTestsHandler(ReducerEventContext ctx);
public event RunEqualityTestsHandler? OnRunEqualityTests;
public void RunEqualityTests()
{
conn.InternalCallReducer(new Reducer.RunEqualityTests());
}
public bool InvokeRunEqualityTests(ReducerEventContext ctx, Reducer.RunEqualityTests args)
{
if (OnRunEqualityTests == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnRunEqualityTests(
ctx
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class RunEqualityTests : Reducer, IReducerArgs
{
string IReducerArgs.ReducerName => "run_equality_tests";
}
}
}
@@ -0,0 +1,66 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void ScheduleTaskHandler(ReducerEventContext ctx, ulong delayMicros);
public event ScheduleTaskHandler? OnScheduleTask;
public void ScheduleTask(ulong delayMicros)
{
conn.InternalCallReducer(new Reducer.ScheduleTask(delayMicros));
}
public bool InvokeScheduleTask(ReducerEventContext ctx, Reducer.ScheduleTask args)
{
if (OnScheduleTask == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnScheduleTask(
ctx,
args.DelayMicros
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class ScheduleTask : Reducer, IReducerArgs
{
[DataMember(Name = "delay_micros")]
public ulong DelayMicros;
public ScheduleTask(ulong DelayMicros)
{
this.DelayMicros = DelayMicros;
}
public ScheduleTask()
{
}
string IReducerArgs.ReducerName => "schedule_task";
}
}
}
@@ -0,0 +1,53 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void TestListOfNullableSumTypesHandler(ReducerEventContext ctx);
public event TestListOfNullableSumTypesHandler? OnTestListOfNullableSumTypes;
public void TestListOfNullableSumTypes()
{
conn.InternalCallReducer(new Reducer.TestListOfNullableSumTypes());
}
public bool InvokeTestListOfNullableSumTypes(ReducerEventContext ctx, Reducer.TestListOfNullableSumTypes args)
{
if (OnTestListOfNullableSumTypes == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnTestListOfNullableSumTypes(
ctx
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class TestListOfNullableSumTypes : Reducer, IReducerArgs
{
string IReducerArgs.ReducerName => "test_list_of_nullable_sum_types";
}
}
}
@@ -0,0 +1,67 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void TestReducerWithSumTypeParamHandler(ReducerEventContext ctx, SpacetimeDB.Types.GameAction action);
public event TestReducerWithSumTypeParamHandler? OnTestReducerWithSumTypeParam;
public void TestReducerWithSumTypeParam(SpacetimeDB.Types.GameAction action)
{
conn.InternalCallReducer(new Reducer.TestReducerWithSumTypeParam(action));
}
public bool InvokeTestReducerWithSumTypeParam(ReducerEventContext ctx, Reducer.TestReducerWithSumTypeParam args)
{
if (OnTestReducerWithSumTypeParam == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnTestReducerWithSumTypeParam(
ctx,
args.Action
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class TestReducerWithSumTypeParam : Reducer, IReducerArgs
{
[DataMember(Name = "action")]
public GameAction Action;
public TestReducerWithSumTypeParam(GameAction Action)
{
this.Action = Action;
}
public TestReducerWithSumTypeParam()
{
this.Action = null!;
}
string IReducerArgs.ReducerName => "test_reducer_with_sum_type_param";
}
}
}
@@ -0,0 +1,53 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void TestSumTypeEqualityHandler(ReducerEventContext ctx);
public event TestSumTypeEqualityHandler? OnTestSumTypeEquality;
public void TestSumTypeEquality()
{
conn.InternalCallReducer(new Reducer.TestSumTypeEquality());
}
public bool InvokeTestSumTypeEquality(ReducerEventContext ctx, Reducer.TestSumTypeEquality args)
{
if (OnTestSumTypeEquality == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnTestSumTypeEquality(
ctx
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class TestSumTypeEquality : Reducer, IReducerArgs
{
string IReducerArgs.ReducerName => "test_sum_type_equality";
}
}
}
@@ -0,0 +1,53 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteReducers : RemoteBase
{
public delegate void TestTableWithoutPrimaryKeyHandler(ReducerEventContext ctx);
public event TestTableWithoutPrimaryKeyHandler? OnTestTableWithoutPrimaryKey;
public void TestTableWithoutPrimaryKey()
{
conn.InternalCallReducer(new Reducer.TestTableWithoutPrimaryKey());
}
public bool InvokeTestTableWithoutPrimaryKey(ReducerEventContext ctx, Reducer.TestTableWithoutPrimaryKey args)
{
if (OnTestTableWithoutPrimaryKey == null)
{
if (InternalOnUnhandledReducerError != null)
{
switch (ctx.Event.Status)
{
case Status.Failed(var reason): InternalOnUnhandledReducerError(ctx, new Exception(reason)); break;
case Status.OutOfEnergy(var _): InternalOnUnhandledReducerError(ctx, new Exception("out of energy")); break;
}
}
return false;
}
OnTestTableWithoutPrimaryKey(
ctx
);
return true;
}
}
public abstract partial class Reducer
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class TestTableWithoutPrimaryKey : Reducer, IReducerArgs
{
string IReducerArgs.ReducerName => "test_table_without_primary_key";
}
}
}
@@ -1,7 +1,7 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
// This was generated using spacetimedb cli version 2.0.5 (commit c095e61ada08c525487dce5279fdd0be472a2131).
// This was generated using spacetimedb cli version 2.1.0 (commit 266eff98ab9e92e3c9308f101460151eea778d8e).
#nullable enable
@@ -29,12 +29,17 @@ namespace SpacetimeDB.Types
{
AddTable(Admins = new(conn));
AddTable(Account = new(conn));
AddTable(ActionBatch = new(conn));
AddTable(AllViewPkPlayers = new(conn));
AddTable(EqualityOrder = new(conn));
AddTable(EqualityPerson = new(conn));
AddTable(EqualityProduct = new(conn));
AddTable(ExampleData = new(conn));
AddTable(FindWhereTest = new(conn));
AddTable(IenumerableAdminsFromFilter = new(conn));
AddTable(IenumerablePlayersFromIter = new(conn));
AddTable(IenumerablePlayersWithLevels = new(conn));
AddTable(LogEntry = new(conn));
AddTable(MyAccount = new(conn));
AddTable(MyAccountMissing = new(conn));
AddTable(MyLog = new(conn));
@@ -45,6 +50,7 @@ namespace SpacetimeDB.Types
AddTable(NullableVec = new(conn));
AddTable(NullableVecView = new(conn));
AddTable(Player = new(conn));
AddTable(PlayerAction = new(conn));
AddTable(PlayerLevel = new(conn));
AddTable(PlayersAtLevelOne = new(conn));
AddTable(RetryLog = new(conn));
@@ -564,12 +570,17 @@ namespace SpacetimeDB.Types
{
new QueryBuilder().From.Admins().ToSql(),
new QueryBuilder().From.Account().ToSql(),
new QueryBuilder().From.ActionBatch().ToSql(),
new QueryBuilder().From.AllViewPkPlayers().ToSql(),
new QueryBuilder().From.EqualityOrder().ToSql(),
new QueryBuilder().From.EqualityPerson().ToSql(),
new QueryBuilder().From.EqualityProduct().ToSql(),
new QueryBuilder().From.ExampleData().ToSql(),
new QueryBuilder().From.FindWhereTest().ToSql(),
new QueryBuilder().From.IenumerableAdminsFromFilter().ToSql(),
new QueryBuilder().From.IenumerablePlayersFromIter().ToSql(),
new QueryBuilder().From.IenumerablePlayersWithLevels().ToSql(),
new QueryBuilder().From.LogEntry().ToSql(),
new QueryBuilder().From.MyAccount().ToSql(),
new QueryBuilder().From.MyAccountMissing().ToSql(),
new QueryBuilder().From.MyLog().ToSql(),
@@ -580,6 +591,7 @@ namespace SpacetimeDB.Types
new QueryBuilder().From.NullableVec().ToSql(),
new QueryBuilder().From.NullableVecView().ToSql(),
new QueryBuilder().From.Player().ToSql(),
new QueryBuilder().From.PlayerAction().ToSql(),
new QueryBuilder().From.PlayerLevel().ToSql(),
new QueryBuilder().From.PlayersAtLevelOne().ToSql(),
new QueryBuilder().From.RetryLog().ToSql(),
@@ -609,12 +621,17 @@ namespace SpacetimeDB.Types
{
public global::SpacetimeDB.Table<User, AdminsCols, AdminsIxCols> Admins() => new("admins", new AdminsCols("admins"), new AdminsIxCols("admins"));
public global::SpacetimeDB.Table<Account, AccountCols, AccountIxCols> Account() => new("account", new AccountCols("account"), new AccountIxCols("account"));
public global::SpacetimeDB.Table<ActionBatch, ActionBatchCols, ActionBatchIxCols> ActionBatch() => new("action_batch", new ActionBatchCols("action_batch"), new ActionBatchIxCols("action_batch"));
public global::SpacetimeDB.Table<ViewPkPlayer, AllViewPkPlayersCols, AllViewPkPlayersIxCols> AllViewPkPlayers() => new("all_view_pk_players", new AllViewPkPlayersCols("all_view_pk_players"), new AllViewPkPlayersIxCols("all_view_pk_players"));
public global::SpacetimeDB.Table<EqualityOrder, EqualityOrderCols, EqualityOrderIxCols> EqualityOrder() => new("equality_order", new EqualityOrderCols("equality_order"), new EqualityOrderIxCols("equality_order"));
public global::SpacetimeDB.Table<EqualityPerson, EqualityPersonCols, EqualityPersonIxCols> EqualityPerson() => new("equality_person", new EqualityPersonCols("equality_person"), new EqualityPersonIxCols("equality_person"));
public global::SpacetimeDB.Table<EqualityProduct, EqualityProductCols, EqualityProductIxCols> EqualityProduct() => new("equality_product", new EqualityProductCols("equality_product"), new EqualityProductIxCols("equality_product"));
public global::SpacetimeDB.Table<ExampleData, ExampleDataCols, ExampleDataIxCols> ExampleData() => new("example_data", new ExampleDataCols("example_data"), new ExampleDataIxCols("example_data"));
public global::SpacetimeDB.Table<WhereTest, FindWhereTestCols, FindWhereTestIxCols> FindWhereTest() => new("find_where_test", new FindWhereTestCols("find_where_test"), new FindWhereTestIxCols("find_where_test"));
public global::SpacetimeDB.Table<User, IenumerableAdminsFromFilterCols, IenumerableAdminsFromFilterIxCols> IenumerableAdminsFromFilter() => new("ienumerable_admins_from_filter", new IenumerableAdminsFromFilterCols("ienumerable_admins_from_filter"), new IenumerableAdminsFromFilterIxCols("ienumerable_admins_from_filter"));
public global::SpacetimeDB.Table<Player, IenumerablePlayersFromIterCols, IenumerablePlayersFromIterIxCols> IenumerablePlayersFromIter() => new("ienumerable_players_from_iter", new IenumerablePlayersFromIterCols("ienumerable_players_from_iter"), new IenumerablePlayersFromIterIxCols("ienumerable_players_from_iter"));
public global::SpacetimeDB.Table<PlayerAndLevel, IenumerablePlayersWithLevelsCols, IenumerablePlayersWithLevelsIxCols> IenumerablePlayersWithLevels() => new("ienumerable_players_with_levels", new IenumerablePlayersWithLevelsCols("ienumerable_players_with_levels"), new IenumerablePlayersWithLevelsIxCols("ienumerable_players_with_levels"));
public global::SpacetimeDB.Table<LogEntry, LogEntryCols, LogEntryIxCols> LogEntry() => new("log_entry", new LogEntryCols("log_entry"), new LogEntryIxCols("log_entry"));
public global::SpacetimeDB.Table<Account, MyAccountCols, MyAccountIxCols> MyAccount() => new("my_account", new MyAccountCols("my_account"), new MyAccountIxCols("my_account"));
public global::SpacetimeDB.Table<Account, MyAccountMissingCols, MyAccountMissingIxCols> MyAccountMissing() => new("my_account_missing", new MyAccountMissingCols("my_account_missing"), new MyAccountMissingIxCols("my_account_missing"));
public global::SpacetimeDB.Table<MyLog, MyLogCols, MyLogIxCols> MyLog() => new("my_log", new MyLogCols("my_log"), new MyLogIxCols("my_log"));
@@ -625,6 +642,7 @@ namespace SpacetimeDB.Types
public global::SpacetimeDB.Table<NullableVec, NullableVecCols, NullableVecIxCols> NullableVec() => new("nullable_vec", new NullableVecCols("nullable_vec"), new NullableVecIxCols("nullable_vec"));
public global::SpacetimeDB.Table<NullableVec, NullableVecViewCols, NullableVecViewIxCols> NullableVecView() => new("nullable_vec_view", new NullableVecViewCols("nullable_vec_view"), new NullableVecViewIxCols("nullable_vec_view"));
public global::SpacetimeDB.Table<Player, PlayerCols, PlayerIxCols> Player() => new("player", new PlayerCols("player"), new PlayerIxCols("player"));
public global::SpacetimeDB.Table<PlayerAction, PlayerActionCols, PlayerActionIxCols> PlayerAction() => new("player_action", new PlayerActionCols("player_action"), new PlayerActionIxCols("player_action"));
public global::SpacetimeDB.Table<PlayerLevel, PlayerLevelCols, PlayerLevelIxCols> PlayerLevel() => new("player_level", new PlayerLevelCols("player_level"), new PlayerLevelIxCols("player_level"));
public global::SpacetimeDB.Table<PlayerAndLevel, PlayersAtLevelOneCols, PlayersAtLevelOneIxCols> PlayersAtLevelOne() => new("players_at_level_one", new PlayersAtLevelOneCols("players_at_level_one"), new PlayersAtLevelOneIxCols("players_at_level_one"));
public global::SpacetimeDB.Table<RetryLog, RetryLogCols, RetryLogIxCols> RetryLog() => new("retry_log", new RetryLogCols("retry_log"), new RetryLogIxCols("retry_log"));
@@ -728,6 +746,9 @@ namespace SpacetimeDB.Types
return reducer switch
{
Reducer.Add args => Reducers.InvokeAdd(eventContext, args),
Reducer.AddEqualityOrder args => Reducers.InvokeAddEqualityOrder(eventContext, args),
Reducer.AddEqualityPerson args => Reducers.InvokeAddEqualityPerson(eventContext, args),
Reducer.AddEqualityProduct args => Reducers.InvokeAddEqualityProduct(eventContext, args),
Reducer.Delete args => Reducers.InvokeDelete(eventContext, args),
Reducer.EmitTestEvent args => Reducers.InvokeEmitTestEvent(eventContext, args),
Reducer.InsertEmptyStringIntoNonNullable args => Reducers.InvokeInsertEmptyStringIntoNonNullable(eventContext, args),
@@ -739,7 +760,16 @@ namespace SpacetimeDB.Types
Reducer.InsertViewPkPlayer args => Reducers.InvokeInsertViewPkPlayer(eventContext, args),
Reducer.InsertWhereTest args => Reducers.InvokeInsertWhereTest(eventContext, args),
Reducer.Noop args => Reducers.InvokeNoop(eventContext, args),
Reducer.RunAllEqualityTests args => Reducers.InvokeRunAllEqualityTests(eventContext, args),
Reducer.RunComplexEqualityTests args => Reducers.InvokeRunComplexEqualityTests(eventContext, args),
Reducer.RunEnumEqualityTests args => Reducers.InvokeRunEnumEqualityTests(eventContext, args),
Reducer.RunEqualityTests args => Reducers.InvokeRunEqualityTests(eventContext, args),
Reducer.ScheduleTask args => Reducers.InvokeScheduleTask(eventContext, args),
Reducer.SetNullableVec args => Reducers.InvokeSetNullableVec(eventContext, args),
Reducer.TestListOfNullableSumTypes args => Reducers.InvokeTestListOfNullableSumTypes(eventContext, args),
Reducer.TestReducerWithSumTypeParam args => Reducers.InvokeTestReducerWithSumTypeParam(eventContext, args),
Reducer.TestSumTypeEquality args => Reducers.InvokeTestSumTypeEquality(eventContext, args),
Reducer.TestTableWithoutPrimaryKey args => Reducers.InvokeTestTableWithoutPrimaryKey(eventContext, args),
Reducer.ThrowError args => Reducers.InvokeThrowError(eventContext, args),
Reducer.UpdateViewPkPlayer args => Reducers.InvokeUpdateViewPkPlayer(eventContext, args),
Reducer.UpdateWhereTest args => Reducers.InvokeUpdateWhereTest(eventContext, args),
@@ -0,0 +1,61 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.BSATN;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteTables
{
public sealed class ActionBatchHandle : RemoteTableHandle<EventContext, ActionBatch>
{
protected override string RemoteTableName => "action_batch";
public sealed class IdUniqueIndex : UniqueIndexBase<uint>
{
protected override uint GetKey(ActionBatch row) => row.Id;
public IdUniqueIndex(ActionBatchHandle table) : base(table) { }
}
public readonly IdUniqueIndex Id;
internal ActionBatchHandle(DbConnection conn) : base(conn)
{
Id = new(this);
}
protected override object GetPrimaryKey(ActionBatch row) => row.Id;
}
public readonly ActionBatchHandle ActionBatch;
}
public sealed class ActionBatchCols
{
public global::SpacetimeDB.Col<ActionBatch, uint> Id { get; }
public global::SpacetimeDB.Col<ActionBatch, System.Collections.Generic.List<GameAction?>> Actions { get; }
public ActionBatchCols(string tableName)
{
Id = new global::SpacetimeDB.Col<ActionBatch, uint>(tableName, "id");
Actions = new global::SpacetimeDB.Col<ActionBatch, System.Collections.Generic.List<GameAction?>>(tableName, "actions");
}
}
public sealed class ActionBatchIxCols
{
public global::SpacetimeDB.IxCol<ActionBatch, uint> Id { get; }
public ActionBatchIxCols(string tableName)
{
Id = new global::SpacetimeDB.IxCol<ActionBatch, uint>(tableName, "id");
}
}
}
@@ -0,0 +1,63 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.BSATN;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteTables
{
public sealed class EqualityOrderHandle : RemoteTableHandle<EventContext, EqualityOrder>
{
protected override string RemoteTableName => "equality_order";
public sealed class IdUniqueIndex : UniqueIndexBase<uint>
{
protected override uint GetKey(EqualityOrder row) => row.Id;
public IdUniqueIndex(EqualityOrderHandle table) : base(table) { }
}
public readonly IdUniqueIndex Id;
internal EqualityOrderHandle(DbConnection conn) : base(conn)
{
Id = new(this);
}
protected override object GetPrimaryKey(EqualityOrder row) => row.Id;
}
public readonly EqualityOrderHandle EqualityOrder;
}
public sealed class EqualityOrderCols
{
public global::SpacetimeDB.Col<EqualityOrder, uint> Id { get; }
public global::SpacetimeDB.Col<EqualityOrder, string> CustomerName { get; }
public global::SpacetimeDB.Col<EqualityOrder, System.Collections.Generic.List<ProductItem>> Items { get; }
public EqualityOrderCols(string tableName)
{
Id = new global::SpacetimeDB.Col<EqualityOrder, uint>(tableName, "id");
CustomerName = new global::SpacetimeDB.Col<EqualityOrder, string>(tableName, "customer_name");
Items = new global::SpacetimeDB.Col<EqualityOrder, System.Collections.Generic.List<ProductItem>>(tableName, "items");
}
}
public sealed class EqualityOrderIxCols
{
public global::SpacetimeDB.IxCol<EqualityOrder, uint> Id { get; }
public EqualityOrderIxCols(string tableName)
{
Id = new global::SpacetimeDB.IxCol<EqualityOrder, uint>(tableName, "id");
}
}
}
@@ -0,0 +1,61 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.BSATN;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteTables
{
public sealed class EqualityPersonHandle : RemoteTableHandle<EventContext, EqualityPerson>
{
protected override string RemoteTableName => "equality_person";
public sealed class IdUniqueIndex : UniqueIndexBase<uint>
{
protected override uint GetKey(EqualityPerson row) => row.Id;
public IdUniqueIndex(EqualityPersonHandle table) : base(table) { }
}
public readonly IdUniqueIndex Id;
internal EqualityPersonHandle(DbConnection conn) : base(conn)
{
Id = new(this);
}
protected override object GetPrimaryKey(EqualityPerson row) => row.Id;
}
public readonly EqualityPersonHandle EqualityPerson;
}
public sealed class EqualityPersonCols
{
public global::SpacetimeDB.Col<EqualityPerson, uint> Id { get; }
public global::SpacetimeDB.Col<EqualityPerson, string> Name { get; }
public EqualityPersonCols(string tableName)
{
Id = new global::SpacetimeDB.Col<EqualityPerson, uint>(tableName, "id");
Name = new global::SpacetimeDB.Col<EqualityPerson, string>(tableName, "name");
}
}
public sealed class EqualityPersonIxCols
{
public global::SpacetimeDB.IxCol<EqualityPerson, uint> Id { get; }
public EqualityPersonIxCols(string tableName)
{
Id = new global::SpacetimeDB.IxCol<EqualityPerson, uint>(tableName, "id");
}
}
}
@@ -0,0 +1,65 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.BSATN;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteTables
{
public sealed class EqualityProductHandle : RemoteTableHandle<EventContext, EqualityProduct>
{
protected override string RemoteTableName => "equality_product";
public sealed class IdUniqueIndex : UniqueIndexBase<uint>
{
protected override uint GetKey(EqualityProduct row) => row.Id;
public IdUniqueIndex(EqualityProductHandle table) : base(table) { }
}
public readonly IdUniqueIndex Id;
internal EqualityProductHandle(DbConnection conn) : base(conn)
{
Id = new(this);
}
protected override object GetPrimaryKey(EqualityProduct row) => row.Id;
}
public readonly EqualityProductHandle EqualityProduct;
}
public sealed class EqualityProductCols
{
public global::SpacetimeDB.Col<EqualityProduct, uint> Id { get; }
public global::SpacetimeDB.Col<EqualityProduct, string> Name { get; }
public global::SpacetimeDB.Col<EqualityProduct, int> Price { get; }
public global::SpacetimeDB.Col<EqualityProduct, int> Quantity { get; }
public EqualityProductCols(string tableName)
{
Id = new global::SpacetimeDB.Col<EqualityProduct, uint>(tableName, "id");
Name = new global::SpacetimeDB.Col<EqualityProduct, string>(tableName, "name");
Price = new global::SpacetimeDB.Col<EqualityProduct, int>(tableName, "price");
Quantity = new global::SpacetimeDB.Col<EqualityProduct, int>(tableName, "quantity");
}
}
public sealed class EqualityProductIxCols
{
public global::SpacetimeDB.IxCol<EqualityProduct, uint> Id { get; }
public EqualityProductIxCols(string tableName)
{
Id = new global::SpacetimeDB.IxCol<EqualityProduct, uint>(tableName, "id");
}
}
}
@@ -0,0 +1,47 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.BSATN;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteTables
{
public sealed class LogEntryHandle : RemoteTableHandle<EventContext, LogEntry>
{
protected override string RemoteTableName => "log_entry";
internal LogEntryHandle(DbConnection conn) : base(conn)
{
}
}
public readonly LogEntryHandle LogEntry;
}
public sealed class LogEntryCols
{
public global::SpacetimeDB.Col<LogEntry, string> Message { get; }
public global::SpacetimeDB.Col<LogEntry, ulong> Timestamp { get; }
public LogEntryCols(string tableName)
{
Message = new global::SpacetimeDB.Col<LogEntry, string>(tableName, "message");
Timestamp = new global::SpacetimeDB.Col<LogEntry, ulong>(tableName, "timestamp");
}
}
public sealed class LogEntryIxCols
{
public LogEntryIxCols(string tableName)
{
}
}
}
@@ -0,0 +1,61 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using SpacetimeDB.BSATN;
using SpacetimeDB.ClientApi;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
public sealed partial class RemoteTables
{
public sealed class PlayerActionHandle : RemoteTableHandle<EventContext, PlayerAction>
{
protected override string RemoteTableName => "player_action";
public sealed class IdUniqueIndex : UniqueIndexBase<uint>
{
protected override uint GetKey(PlayerAction row) => row.Id;
public IdUniqueIndex(PlayerActionHandle table) : base(table) { }
}
public readonly IdUniqueIndex Id;
internal PlayerActionHandle(DbConnection conn) : base(conn)
{
Id = new(this);
}
protected override object GetPrimaryKey(PlayerAction row) => row.Id;
}
public readonly PlayerActionHandle PlayerAction;
}
public sealed class PlayerActionCols
{
public global::SpacetimeDB.Col<PlayerAction, uint> Id { get; }
public global::SpacetimeDB.Col<PlayerAction, GameAction> Action { get; }
public PlayerActionCols(string tableName)
{
Id = new global::SpacetimeDB.Col<PlayerAction, uint>(tableName, "id");
Action = new global::SpacetimeDB.Col<PlayerAction, GameAction>(tableName, "action");
}
}
public sealed class PlayerActionIxCols
{
public global::SpacetimeDB.IxCol<PlayerAction, uint> Id { get; }
public PlayerActionIxCols(string tableName)
{
Id = new global::SpacetimeDB.IxCol<PlayerAction, uint>(tableName, "id");
}
}
}
@@ -0,0 +1,35 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class ActionBatch
{
[DataMember(Name = "id")]
public uint Id;
[DataMember(Name = "actions")]
public System.Collections.Generic.List<GameAction?> Actions;
public ActionBatch(
uint Id,
System.Collections.Generic.List<GameAction?> Actions
)
{
this.Id = Id;
this.Actions = Actions;
}
public ActionBatch()
{
this.Actions = new();
}
}
}
@@ -0,0 +1,38 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class EqualityOrder
{
[DataMember(Name = "id")]
public uint Id;
[DataMember(Name = "customer_name")]
public string? CustomerName;
[DataMember(Name = "items")]
public System.Collections.Generic.List<ProductItem>? Items;
public EqualityOrder(
uint Id,
string? CustomerName,
System.Collections.Generic.List<ProductItem>? Items
)
{
this.Id = Id;
this.CustomerName = CustomerName;
this.Items = Items;
}
public EqualityOrder()
{
}
}
}
@@ -0,0 +1,35 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class EqualityPerson
{
[DataMember(Name = "id")]
public uint Id;
[DataMember(Name = "name")]
public string Name;
public EqualityPerson(
uint Id,
string Name
)
{
this.Id = Id;
this.Name = Name;
}
public EqualityPerson()
{
this.Name = "";
}
}
}
@@ -0,0 +1,43 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class EqualityProduct
{
[DataMember(Name = "id")]
public uint Id;
[DataMember(Name = "name")]
public string Name;
[DataMember(Name = "price")]
public int Price;
[DataMember(Name = "quantity")]
public int Quantity;
public EqualityProduct(
uint Id,
string Name,
int Price,
int Quantity
)
{
this.Id = Id;
this.Name = Name;
this.Price = Price;
this.Quantity = Quantity;
}
public EqualityProduct()
{
this.Name = "";
}
}
}
@@ -0,0 +1,16 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
namespace SpacetimeDB.Types
{
[SpacetimeDB.Type]
public partial record GameAction : SpacetimeDB.TaggedEnum<(
string Move,
string Attack,
int Defend
)>;
}
@@ -0,0 +1,35 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class LogEntry
{
[DataMember(Name = "message")]
public string Message;
[DataMember(Name = "timestamp")]
public ulong Timestamp;
public LogEntry(
string Message,
ulong Timestamp
)
{
this.Message = Message;
this.Timestamp = Timestamp;
}
public LogEntry()
{
this.Message = "";
}
}
}
@@ -0,0 +1,35 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class PlayerAction
{
[DataMember(Name = "id")]
public uint Id;
[DataMember(Name = "action")]
public GameAction Action;
public PlayerAction(
uint Id,
GameAction Action
)
{
this.Id = Id;
this.Action = Action;
}
public PlayerAction()
{
this.Action = null!;
}
}
}
@@ -0,0 +1,34 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class ProductItem
{
[DataMember(Name = "product_id")]
public uint ProductId;
[DataMember(Name = "quantity")]
public int Quantity;
public ProductItem(
uint ProductId,
int Quantity
)
{
this.ProductId = ProductId;
this.Quantity = Quantity;
}
public ProductItem()
{
}
}
}
@@ -0,0 +1,40 @@
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
#nullable enable
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace SpacetimeDB.Types
{
[SpacetimeDB.Type]
[DataContract]
public sealed partial class ScheduledTask
{
[DataMember(Name = "id")]
public ulong Id;
[DataMember(Name = "task_name")]
public string TaskName;
[DataMember(Name = "scheduled_at")]
public SpacetimeDB.ScheduleAt ScheduledAt;
public ScheduledTask(
ulong Id,
string TaskName,
SpacetimeDB.ScheduleAt ScheduledAt
)
{
this.Id = Id;
this.TaskName = TaskName;
this.ScheduledAt = ScheduledAt;
}
public ScheduledTask()
{
this.TaskName = "";
this.ScheduledAt = null!;
}
}
}
@@ -201,6 +201,93 @@ public static partial class Module
public ulong PlayerId;
}
// === Equality Test Tables and Types ===
// Struct for testing struct equality (used in Product and Order)
[SpacetimeDB.Type]
public partial struct ProductItem
{
public uint ProductId;
public int Quantity;
}
// Simple struct - basic value type equality testing
[SpacetimeDB.Table(Accessor = "equality_person", Public = true)]
public partial struct EqualityPerson
{
[SpacetimeDB.PrimaryKey]
public uint Id;
public string Name;
}
// Complex struct with multiple fields for equality testing
[SpacetimeDB.Table(Accessor = "equality_product", Public = true)]
public partial struct EqualityProduct
{
[SpacetimeDB.PrimaryKey]
public uint Id;
public string Name;
public int Price; // Price in cents
public int Quantity;
}
// Record with reference type fields for equality testing
[SpacetimeDB.Table(Accessor = "equality_order", Public = true)]
public partial record EqualityOrder
{
[SpacetimeDB.PrimaryKey]
public uint Id;
public string? CustomerName;
public List<ProductItem>? Items;
}
// Enum for equality testing
public enum TestStatus
{
Pending,
Active,
Completed
}
// Custom sum type for testing TaggedEnum equality
[SpacetimeDB.Type]
public partial record GameAction : TaggedEnum<(string Move, string Attack, int Defend)> { }
[SpacetimeDB.Table(Accessor = "player_action", Public = true)]
public partial struct PlayerAction
{
[SpacetimeDB.PrimaryKey]
public uint Id;
public GameAction Action;
}
// List of nullable sum types - tests nullable suffix handling
[SpacetimeDB.Table(Accessor = "action_batch", Public = true)]
public partial struct ActionBatch
{
[SpacetimeDB.PrimaryKey]
public uint Id;
public List<GameAction?> Actions;
}
// Table WITHOUT primary key - triggers RawIndexDefV10.Equals/GetHashCode path
[SpacetimeDB.Table(Accessor = "log_entry", Public = true)]
public partial struct LogEntry
{
public string Message;
public ulong Timestamp;
}
// Scheduled table - tests ScheduleAt sum type (manually-written, uses ReferenceUse)
[SpacetimeDB.Table(Accessor = "scheduled_task", Scheduled = "ExecuteScheduledTask", ScheduledAt = "ScheduledAt")]
public partial struct ScheduledTask
{
[SpacetimeDB.PrimaryKey]
public ulong Id;
public string TaskName;
public ScheduleAt ScheduledAt;
}
// At-most-one row: return T?
[SpacetimeDB.View(Accessor = "my_player", Public = true)]
public static Player? MyPlayer(ViewContext ctx)
@@ -882,6 +969,255 @@ public static partial class Module
[SpacetimeDB.Reducer]
public static void Noop(ReducerContext ctx) { }
// === Equality Test Reducers ===
[SpacetimeDB.Reducer]
public static void AddEqualityPerson(ReducerContext ctx, uint id, string name)
{
ctx.Db.equality_person.Insert(new EqualityPerson { Id = id, Name = name });
}
[SpacetimeDB.Reducer]
public static void AddEqualityProduct(ReducerContext ctx, uint id, string name, int price, int quantity)
{
ctx.Db.equality_product.Insert(new EqualityProduct { Id = id, Name = name, Price = price, Quantity = quantity });
}
[SpacetimeDB.Reducer]
public static void AddEqualityOrder(ReducerContext ctx, uint id, string? customerName, List<ProductItem>? items)
{
ctx.Db.equality_order.Insert(new EqualityOrder { Id = id, CustomerName = customerName, Items = items });
}
[SpacetimeDB.Reducer]
public static void RunEqualityTests(ReducerContext ctx)
{
Log.Info("=== Testing Equality ===");
// Test 1: Direct string comparison (no allocation)
bool stringEqual = "Alice" == "Alice";
Log.Info($"Test 1 - String equality: {stringEqual}");
// Test 2: Enum equality (no allocation)
var s1 = TestStatus.Pending;
var s2 = TestStatus.Pending;
bool enumEqual = s1 == s2;
Log.Info($"Test 2 - Enum equality: {enumEqual}");
// Test 3: Integer equality (no allocation)
int i1 = 42;
int i2 = 42;
bool intEqual = i1 == i2;
Log.Info($"Test 3 - Int equality: {intEqual}");
Log.Info("=== Equality Tests Complete ===");
}
[SpacetimeDB.Reducer]
public static void RunComplexEqualityTests(ReducerContext ctx)
{
Log.Info("=== Testing Complex Equality ===");
// Test struct .Equals() - this used to cause boxing/allocation
var item1 = new ProductItem { ProductId = 1, Quantity = 10 };
var item2 = new ProductItem { ProductId = 1, Quantity = 10 };
var item3 = new ProductItem { ProductId = 2, Quantity = 5 };
bool structEqual = item1.Equals(item2);
bool structNotEqual = item1.Equals(item3);
Log.Info($"Struct equality (same): {structEqual}");
Log.Info($"Struct equality (different): {structNotEqual}");
// Test Person struct equality (table type)
var person1 = new EqualityPerson { Id = 100, Name = "Test" };
var person2 = new EqualityPerson { Id = 100, Name = "Test" };
var person3 = new EqualityPerson { Id = 200, Name = "Other" };
bool personEqual = person1.Equals(person2);
bool personNotEqual = person1.Equals(person3);
Log.Info($"Person struct equality (same): {personEqual}");
Log.Info($"Person struct equality (different): {personNotEqual}");
Log.Info("=== Complex Equality Tests Complete ===");
}
[SpacetimeDB.Reducer]
public static void RunEnumEqualityTests(ReducerContext ctx)
{
var s1 = TestStatus.Pending;
var s2 = TestStatus.Pending;
var s3 = TestStatus.Active;
bool equal1 = s1 == s2; // Should be true
bool equal2 = s1 == s3; // Should be false
Log.Info($"Enum equality (same): {equal1}");
Log.Info($"Enum equality (different): {equal2}");
}
[SpacetimeDB.Reducer]
public static void TestTableWithoutPrimaryKey(ReducerContext ctx)
{
Log.Info("=== Testing Table WITHOUT Primary Key (RawIndexDefV10 path) ===");
// This triggers RawIndexDefV10.Equals/GetHashCode which previously failed
ctx.Db.log_entry.Insert(new LogEntry { Message = "Test 1", Timestamp = 1000 });
ctx.Db.log_entry.Insert(new LogEntry { Message = "Test 2", Timestamp = 2000 });
int count = 0;
foreach (var entry in ctx.Db.log_entry.Iter())
{
count++;
Log.Info($"LogEntry: {entry.Message} at {entry.Timestamp}");
}
Log.Info($"Total log entries: {count}");
Log.Info("=== Table Without Primary Key Test Complete ===");
}
[SpacetimeDB.Reducer]
public static void TestSumTypeEquality(ReducerContext ctx)
{
Log.Info("=== Testing Sum Type (TaggedEnum) Equality ===");
// Test sum type equality via generated Equals
var action1 = new GameAction.Move("North");
var action2 = new GameAction.Move("North");
var action3 = new GameAction.Attack("Sword");
bool equal = action1.Equals(action2);
bool notEqual = action1.Equals(action3);
Log.Info($"Sum type equality (same variant): {equal}");
Log.Info($"Sum type equality (different variant): {!notEqual}");
// Insert and retrieve sum type from table
ctx.Db.player_action.Insert(new PlayerAction { Id = 1, Action = action1 });
ctx.Db.player_action.Insert(new PlayerAction { Id = 2, Action = action3 });
foreach (var pa in ctx.Db.player_action.Iter())
{
var desc = pa.Action switch
{
GameAction.Move(var m) => $"Move: {m}",
GameAction.Attack(var a) => $"Attack: {a}",
GameAction.Defend(var d) => $"Defend: {d}",
_ => "Unknown"
};
Log.Info($"PlayerAction {pa.Id}: {desc}");
}
Log.Info("=== Sum Type Equality Test Complete ===");
}
[SpacetimeDB.Reducer]
public static void TestListOfNullableSumTypes(ReducerContext ctx)
{
Log.Info("=== Testing List of Nullable Sum Types ===");
// This exercises SumTypeUse with nullable suffix (GameAction?)
var actions = new List<GameAction?>
{
new GameAction.Move("North"),
null,
new GameAction.Attack("Bow"),
null
};
ctx.Db.action_batch.Insert(new ActionBatch { Id = 1, Actions = actions });
foreach (var batch in ctx.Db.action_batch.Iter())
{
Log.Info($"Batch {batch.Id} has {batch.Actions?.Count ?? 0} actions");
if (batch.Actions != null)
{
for (int i = 0; i < batch.Actions.Count; i++)
{
var desc = batch.Actions[i] switch
{
null => "null",
GameAction.Move(var m) => $"Move({m})",
GameAction.Attack(var a) => $"Attack({a})",
GameAction.Defend(var d) => $"Defend({d})",
_ => "Unknown"
};
Log.Info($" Action {i}: {desc}");
}
}
}
Log.Info("=== List of Nullable Sum Types Test Complete ===");
}
[SpacetimeDB.Reducer]
public static void ExecuteScheduledTask(ReducerContext ctx, ScheduledTask task)
{
Log.Info($"Executing scheduled task: {task.TaskName}");
}
[SpacetimeDB.Reducer]
public static void ScheduleTask(ReducerContext ctx, ulong delayMicros)
{
Log.Info("=== Testing Scheduled Table (ScheduleAt sum type) ===");
// ScheduleAt is a manually-written sum type - tests ReferenceUse path
var scheduledAt = ScheduleAt.TimeSpanFromMicroseconds((long)delayMicros);
ctx.Db.scheduled_task.Insert(new ScheduledTask
{
Id = 1,
TaskName = "Test Task",
ScheduledAt = scheduledAt
});
Log.Info($"Scheduled task for {delayMicros} microseconds from now");
Log.Info("=== Scheduled Table Test Complete ===");
}
[SpacetimeDB.Reducer]
public static void TestReducerWithSumTypeParam(ReducerContext ctx, GameAction action)
{
Log.Info("=== Testing Reducer with Sum Type Parameter ===");
// This exercises SumTypeUse.EqualsStatement for parameter comparison
var match = action switch
{
GameAction.Move(var m) => $"Moving: {m}",
GameAction.Attack(var a) => $"Attacking with: {a}",
GameAction.Defend(var d) => $"Defending with power: {d}",
_ => "Unknown action"
};
Log.Info(match);
Log.Info("=== Reducer with Sum Type Parameter Test Complete ===");
}
[SpacetimeDB.Reducer]
public static void RunAllEqualityTests(ReducerContext ctx)
{
Log.Info("========== Starting Equality Tests ==========");
// Insert test data
ctx.Db.equality_person.Insert(new EqualityPerson { Id = 1, Name = "Alice" });
ctx.Db.equality_product.Insert(new EqualityProduct { Id = 1, Name = "Widget", Price = 999, Quantity = 100 });
// Run tests
RunEqualityTests(ctx);
RunComplexEqualityTests(ctx);
RunEnumEqualityTests(ctx);
// New tests for sum type fixes
TestTableWithoutPrimaryKey(ctx);
TestSumTypeEquality(ctx);
TestListOfNullableSumTypes(ctx);
ScheduleTask(ctx, 1000000); // 1 second delay
// Test reducer with sum type parameter
TestReducerWithSumTypeParam(ctx, new GameAction.Attack("Magic Sword"));
Log.Info("========== All Equality Tests Complete ==========");
}
[SpacetimeDB.Procedure]
public static void InsertWithTxPanic(ProcedureContext ctx)
{
+14
View File
@@ -15,6 +15,8 @@ cat >NuGet.Config <<EOF
<configuration>
<packageSources>
<clear />
<!-- Experimental NuGet feed for Microsoft.DotNet.ILCompiler.LLVM packages -->
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
<!-- Local NuGet repositories -->
<add key="Local SpacetimeDB.BSATN.Runtime" value="${SPACETIMEDB_REPO_PATH}/crates/bindings-csharp/BSATN.Runtime/bin/Release" />
<!-- We need to override the module runtime as well because the examples use it -->
@@ -30,6 +32,11 @@ cat >NuGet.Config <<EOF
<packageSource key="Local SpacetimeDB.Runtime">
<package pattern="SpacetimeDB.Runtime" />
</packageSource>
<!-- Experimental packages for NativeAOT-LLVM compilation -->
<packageSource key="dotnet-experimental">
<package pattern="Microsoft.DotNet.ILCompiler.LLVM" />
<package pattern="runtime.*" />
</packageSource>
<!-- Fallback for other packages (e.g. test deps). -->
<packageSource key="nuget.org">
<package pattern="*" />
@@ -43,6 +50,8 @@ cat >"${SPACETIMEDB_REPO_PATH}/NuGet.Config" <<EOF
<configuration>
<packageSources>
<clear />
<!-- Experimental NuGet feed for Microsoft.DotNet.ILCompiler.LLVM packages -->
<add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
<!-- Local NuGet repositories -->
<add key="Local SpacetimeDB.BSATN.Runtime" value="crates/bindings-csharp/BSATN.Runtime/bin/Release" />
<!-- We need to override the module runtime as well because the examples use it -->
@@ -58,6 +67,11 @@ cat >"${SPACETIMEDB_REPO_PATH}/NuGet.Config" <<EOF
<packageSource key="Local SpacetimeDB.Runtime">
<package pattern="SpacetimeDB.Runtime" />
</packageSource>
<!-- Experimental packages for NativeAOT-LLVM compilation -->
<packageSource key="dotnet-experimental">
<package pattern="Microsoft.DotNet.ILCompiler.LLVM" />
<package pattern="runtime.*" />
</packageSource>
<!-- Fallback for other packages (e.g. test deps). -->
<packageSource key="nuget.org">
<package pattern="*" />