From 16f3353181b3fd8660f791ab770f5225a4d63ddd Mon Sep 17 00:00:00 2001 From: Jason Larabie Date: Mon, 6 Jul 2026 16:10:20 -0700 Subject: [PATCH] Add View PK Find() for C# and Unreal SDKs --- crates/codegen/src/csharp.rs | 90 ++++++- crates/codegen/src/unrealcpp.rs | 227 ++++++++++-------- .../regression-tests/client/Program.cs | 6 + .../Tables/AllViewPkPlayers.g.cs | 10 + .../Tables/ProceduralViewPkPlayers.g.cs | 10 + .../Tables/SenderViewPkPlayersA.g.cs | 10 + .../Tables/SenderViewPkPlayersB.g.cs | 10 + .../Tables/WhereTestQuery.g.cs | 10 + .../Tables/AllViewPkPlayersTable.g.cpp | 5 + .../Tables/SenderViewPkPlayersATable.g.cpp | 5 + .../Tables/SenderViewPkPlayersBTable.g.cpp | 5 + .../Private/Tests/TestHandler.cpp | 11 +- .../Tables/AllViewPkPlayersTable.g.h | 40 +++ .../Tables/SenderViewPkPlayersATable.g.h | 40 +++ .../Tables/SenderViewPkPlayersBTable.g.h | 40 +++ 15 files changed, 413 insertions(+), 106 deletions(-) diff --git a/crates/codegen/src/csharp.rs b/crates/codegen/src/csharp.rs index 76907815898..376d1f3e118 100644 --- a/crates/codegen/src/csharp.rs +++ b/crates/codegen/src/csharp.rs @@ -8,7 +8,7 @@ use std::ops::Deref; use super::code_indenter::CodeIndenter; use super::Lang; use crate::util::{ - collect_case, is_reducer_invokable, iter_indexes, iter_reducers, iter_table_names_and_types, + collect_case, is_reducer_invokable, iter_indexes, iter_reducers, iter_table_names_and_types, iter_unique_cols, print_auto_generated_file_comment, print_auto_generated_version_comment, type_ref_name, }; use crate::{indent_scope, CodegenOptions, OutputFile}; @@ -545,6 +545,41 @@ impl Lang for Csharp<'_> { let mut index_names = Vec::new(); + let emit_unique_index = |output: &mut CodeIndenter, + index_names: &mut Vec, + csharp_index_name: String, + field_name: &Identifier, + field_type: &AlgebraicTypeUse| { + if index_names.contains(&csharp_index_name) { + return; + } + + let csharp_index_class_name = csharp_index_name.clone() + "UniqueIndex"; + let csharp_field_name_pascal = field_name.deref().to_case(Case::Pascal); + let csharp_field_type = ty_fmt(module, field_type).to_string(); + + writeln!( + output, + "public sealed class {csharp_index_class_name} : UniqueIndexBase<{csharp_field_type}>" + ); + indented_block(output, |output| { + writeln!( + output, + "protected override {csharp_field_type} GetKey({table_type} row) => row.{csharp_field_name_pascal};" + ); + writeln!(output); + writeln!( + output, + "public {csharp_index_class_name}({csharp_table_class_name} table) : base(table) {{ }}" + ); + }); + writeln!(output); + writeln!(output, "public readonly {csharp_index_class_name} {csharp_index_name};"); + writeln!(output); + + index_names.push(csharp_index_name); + }; + for idx in iter_indexes(table) { let Some(accessor_name) = idx.accessor_name.as_ref() else { // If there is no accessor name, we shouldn't generate a client-side index accessor. @@ -582,18 +617,42 @@ impl Lang for Csharp<'_> { let csharp_index_name = accessor_name.deref().to_case(Case::Pascal); - let mut csharp_index_class_name = csharp_index_name.clone(); - let csharp_index_base_class_name = if schema.is_unique(&columns) { - csharp_index_class_name += "UniqueIndex"; - "UniqueIndexBase" - } else { - csharp_index_class_name += "Index"; - "BTreeIndexBase" - }; + if schema.is_unique(&columns) { + if let Some(col_pos) = columns.as_singleton() { + let (field_name, field_type) = &product_type.elements[col_pos.idx()]; + emit_unique_index(output, &mut index_names, csharp_index_name, field_name, field_type); + } else { + let csharp_index_class_name = csharp_index_name.clone() + "UniqueIndex"; + + writeln!( + output, + "public sealed class {csharp_index_class_name} : UniqueIndexBase<{key_type}>" + ); + indented_block(output, |output| { + writeln!( + output, + "protected override {key_type} GetKey({table_type} row) => {row_to_key};" + ); + writeln!(output); + writeln!( + output, + "public {csharp_index_class_name}({csharp_table_class_name} table) : base(table) {{ }}" + ); + }); + writeln!(output); + writeln!(output, "public readonly {csharp_index_class_name} {csharp_index_name};"); + writeln!(output); + + index_names.push(csharp_index_name); + } + continue; + } + + let csharp_index_class_name = csharp_index_name.clone() + "Index"; writeln!( output, - "public sealed class {csharp_index_class_name} : {csharp_index_base_class_name}<{key_type}>" + "public sealed class {csharp_index_class_name} : BTreeIndexBase<{key_type}>" ); indented_block(output, |output| { writeln!( @@ -613,6 +672,17 @@ impl Lang for Csharp<'_> { index_names.push(csharp_index_name); } + for (field_name, field_type) in iter_unique_cols(module.typespace_for_generate(), &schema, product_type) + { + emit_unique_index( + output, + &mut index_names, + field_name.deref().to_case(Case::Pascal), + field_name, + field_type, + ); + } + writeln!( output, "internal {csharp_table_class_name}(DbConnection conn) : base(conn)" diff --git a/crates/codegen/src/unrealcpp.rs b/crates/codegen/src/unrealcpp.rs index d55ffa5e04e..a521e739180 100644 --- a/crates/codegen/src/unrealcpp.rs +++ b/crates/codegen/src/unrealcpp.rs @@ -4,7 +4,7 @@ use crate::util::{ fmt_fn, iter_table_names_and_types, iter_tables, iter_views, print_auto_generated_file_comment, print_auto_generated_version_comment, CodegenVisibility, }; -use crate::util::{iter_indexes, iter_procedures, iter_reducers}; +use crate::util::{iter_indexes, iter_procedures, iter_reducers, iter_unique_cols}; use crate::Lang; use crate::{CodegenOptions, OutputFile}; use convert_case::{Case, Casing}; @@ -68,110 +68,124 @@ impl Lang for UnrealCpp<'_> { let mut unique_indexes = Vec::new(); let mut multi_key_indexes = Vec::new(); + let mut emitted_unique_index_names = HashSet::new(); + let mut explicit_index_property_names = HashSet::new(); + + let mut emit_unique_index = + |output: &mut UnrealCppAutogen, index_name: String, f_name: &Identifier, f_ty: &AlgebraicTypeUse| { + if !emitted_unique_index_names.insert(index_name.clone()) { + return; + } + + let field_name = f_name.deref().to_case(Case::Pascal); + let field_type = cpp_ty_fmt_with_module(self.module_prefix, module, f_ty, self.module_name).to_string(); + let index_class_name = format!("U{table_pascal}{index_name}UniqueIndex"); + let key_type = field_type.clone(); + let field_name_lowercase = field_name.to_lowercase(); + + writeln!(output, "UCLASS(Blueprintable)"); + writeln!( + output, + "class {} {index_class_name} : public UObject", + self.get_api_macro() + ); + writeln!(output, "{{"); + writeln!(output, " GENERATED_BODY()"); + writeln!(output); + writeln!(output, "private:"); + writeln!(output, " // Declare an instance of your templated helper."); + writeln!( + output, + " // It's private because the UObject wrapper will expose its functionality." + ); + writeln!( + output, + " FUniqueIndexHelper<{row_struct}, {key_type}, FTableCache<{row_struct}>> {index_name}IndexHelper;" + ); + writeln!(output); + writeln!(output, "public:"); + writeln!(output, " {index_class_name}()"); + writeln!( + output, + " // Initialize the helper with the specific unique index name" + ); + writeln!(output, " : {index_name}IndexHelper(\"{}\") {{", f_name.deref()); + writeln!(output, " }}"); + writeln!(output); + writeln!(output, " /**"); + writeln!( + output, + " * Finds a {table_pascal} by their unique {field_name_lowercase}." + ); + writeln!(output, " * @param Key The {field_name_lowercase} to search for."); + writeln!( + output, + " * @return The found {row_struct}, or a default-constructed {row_struct} if not found." + ); + writeln!(output, " */"); + + // Only mark as BlueprintCallable if the key type is Blueprint-compatible + if is_blueprintable(module, f_ty) { + writeln!( + output, + " UFUNCTION(BlueprintCallable, Category = \"SpacetimeDB|{table_pascal}Index\")" + ); + } else { + writeln!( + output, + " // NOTE: Not exposed to Blueprint because {key_type} types are not Blueprint-compatible" + ); + } + + writeln!(output, " {row_struct} Find({key_type} Key)"); + writeln!(output, " {{"); + writeln!(output, " // Simply delegate the call to the internal helper"); + writeln!(output, " return {index_name}IndexHelper.FindUniqueIndex(Key);"); + writeln!(output, " }}"); + writeln!(output); + writeln!( + output, + " // A public setter to provide the cache to the helper after construction" + ); + writeln!( + output, + " // This is a common pattern when the cache might be created or provided by another system." + ); + writeln!( + output, + " void SetCache(TSharedPtr> In{table_pascal}Cache)" + ); + writeln!(output, " {{"); + writeln!(output, " {index_name}IndexHelper.Cache = In{table_pascal}Cache;"); + writeln!(output, " }}"); + writeln!(output, "}};"); + writeln!(output, "/***/"); + writeln!(output); + + unique_indexes.push((index_name, index_class_name, field_type, f_name.deref().to_string())); + }; for idx in iter_indexes(table) { let Some(accessor_name) = idx.accessor_name.as_ref() else { continue; }; + let index_property_name = accessor_name.deref().to_case(Case::Pascal); + explicit_index_property_names.insert(index_property_name.clone()); + // Whatever the index algorithm on the host, // the client can still use btrees. let columns = idx.algorithm.columns(); if schema.is_unique(&columns) { if let Some(col) = columns.as_singleton() { let (f_name, f_ty) = &product_type.unwrap().elements[col.idx()]; - let field_name = f_name.deref().to_case(Case::Pascal); - let field_type = - cpp_ty_fmt_with_module(self.module_prefix, module, f_ty, self.module_name).to_string(); - let index_name = accessor_name.deref().to_case(Case::Pascal); - let index_class_name = format!("U{table_pascal}{index_name}UniqueIndex"); - let key_type = field_type.clone(); - let field_name_lowercase = field_name.to_lowercase(); - - writeln!(output, "UCLASS(Blueprintable)"); - writeln!( - output, - "class {} {index_class_name} : public UObject", - self.get_api_macro() - ); - writeln!(output, "{{"); - writeln!(output, " GENERATED_BODY()"); - writeln!(output); - writeln!(output, "private:"); - writeln!(output, " // Declare an instance of your templated helper."); - writeln!( - output, - " // It's private because the UObject wrapper will expose its functionality." - ); - writeln!( - output, - " FUniqueIndexHelper<{row_struct}, {key_type}, FTableCache<{row_struct}>> {index_name}IndexHelper;" - ); - writeln!(output); - writeln!(output, "public:"); - writeln!(output, " {index_class_name}()"); - writeln!( - output, - " // Initialize the helper with the specific unique index name" - ); - writeln!(output, " : {index_name}IndexHelper(\"{}\") {{", f_name.deref()); - writeln!(output, " }}"); - writeln!(output); - writeln!(output, " /**"); - writeln!( - output, - " * Finds a {table_pascal} by their unique {field_name_lowercase}." - ); - writeln!(output, " * @param Key The {field_name_lowercase} to search for."); - writeln!( - output, - " * @return The found {row_struct}, or a default-constructed {row_struct} if not found." - ); - writeln!(output, " */"); - - // Only mark as BlueprintCallable if the key type is Blueprint-compatible - if is_blueprintable(module, f_ty) { - writeln!( - output, - " UFUNCTION(BlueprintCallable, Category = \"SpacetimeDB|{table_pascal}Index\")" - ); - } else { - writeln!( - output, - " // NOTE: Not exposed to Blueprint because {key_type} types are not Blueprint-compatible" - ); - } - - writeln!(output, " {row_struct} Find({key_type} Key)"); - writeln!(output, " {{"); - writeln!(output, " // Simply delegate the call to the internal helper"); - writeln!(output, " return {index_name}IndexHelper.FindUniqueIndex(Key);"); - writeln!(output, " }}"); - writeln!(output); - writeln!( - output, - " // A public setter to provide the cache to the helper after construction" - ); - writeln!(output, " // This is a common pattern when the cache might be created or provided by another system."); - writeln!( - output, - " void SetCache(TSharedPtr> In{table_pascal}Cache)" - ); - writeln!(output, " {{"); - writeln!(output, " {index_name}IndexHelper.Cache = In{table_pascal}Cache;"); - writeln!(output, " }}"); - writeln!(output, "}};"); - writeln!(output, "/***/"); - writeln!(output); - - unique_indexes.push((index_name, index_class_name, field_type, f_name.deref().to_string())); + emit_unique_index(&mut output, index_property_name, f_name, f_ty); } } // Handle non-unique BTree indexes else { // Generate non-unique BTree index class - let _index_name = accessor_name.deref().to_case(Case::Pascal); - let index_class_name = format!("U{table_pascal}{_index_name}Index"); + let index_class_name = format!("U{table_pascal}{index_property_name}Index"); // Get column information let column_info: Vec<_> = columns @@ -279,8 +293,14 @@ impl Lang for UnrealCpp<'_> { writeln!(output); // Store information for PostInitialize generation - let property_name = accessor_name.deref().to_case(Case::Pascal); - multi_key_indexes.push((property_name, index_class_name)); + multi_key_indexes.push((index_property_name, index_class_name)); + } + } + + for (f_name, f_ty) in iter_unique_cols(module.typespace_for_generate(), &schema, product_type.unwrap()) { + let index_name = f_name.deref().to_case(Case::Pascal); + if !explicit_index_property_names.contains(&index_name) { + emit_unique_index(&mut output, index_name, f_name, f_ty); } } @@ -1175,25 +1195,35 @@ fn generate_table_cpp( let mut unique_indexes = Vec::new(); let mut multi_key_indexes = Vec::new(); + let mut emitted_unique_index_names = HashSet::new(); + let mut explicit_index_property_names = HashSet::new(); + + let mut add_unique_index = |index_name: String, f_name: &Identifier, f_ty: &AlgebraicTypeUse| { + if !emitted_unique_index_names.insert(index_name.clone()) { + return; + } + + let field_type = cpp_ty_fmt_with_module(module_prefix, module, f_ty, module_name).to_string(); + unique_indexes.push((index_name, field_type, f_name.deref().to_string())); + }; for idx in iter_indexes(table) { let Some(accessor_name) = idx.accessor_name.as_ref() else { continue; }; + let index_name = accessor_name.deref().to_case(Case::Pascal); + explicit_index_property_names.insert(index_name.clone()); + // Whatever the index algorithm on the host, // the client can still use btrees. let columns = idx.algorithm.columns(); if schema.is_unique(&columns) { if let Some(col) = columns.as_singleton() { let (f_name, f_ty) = &product_type.unwrap().elements[col.idx()]; - let _field_name = f_name.deref().to_case(Case::Pascal); - let field_type = cpp_ty_fmt_with_module(module_prefix, module, f_ty, module_name).to_string(); - let index_name = accessor_name.deref().to_case(Case::Pascal); - unique_indexes.push((index_name, field_type, f_name.deref().to_string())); + add_unique_index(index_name, f_name, f_ty); } } else { // Non-unique BTree index - let index_name = accessor_name.deref().to_case(Case::Pascal); let index_class_name = format!("U{table_pascal}{index_name}Index"); // Collect column information for AddMultiKeyBTreeIndex call @@ -1216,6 +1246,13 @@ fn generate_table_cpp( } } + for (f_name, f_ty) in iter_unique_cols(module.typespace_for_generate(), schema, product_type.unwrap()) { + let index_name = f_name.deref().to_case(Case::Pascal); + if !explicit_index_property_names.contains(&index_name) { + add_unique_index(index_name, f_name, f_ty); + } + } + // Generate PostInitialize implementation writeln!(output, "void U{table_pascal}Table::PostInitialize()"); writeln!(output, "{{"); diff --git a/sdks/csharp/examples~/regression-tests/client/Program.cs b/sdks/csharp/examples~/regression-tests/client/Program.cs index 7f947cbc9d6..f1b73b5f950 100644 --- a/sdks/csharp/examples~/regression-tests/client/Program.cs +++ b/sdks/csharp/examples~/regression-tests/client/Program.cs @@ -842,6 +842,12 @@ void OnAllViewPkPlayersUpdate(EventContext _, ViewPkPlayer oldRow, ViewPkPlayer oldRow, newRow ); + var found = ctx.Db.AllViewPkPlayers.Id.Find(playerId); + Debug.Assert(found != null, $"Expected AllViewPkPlayers.Id.Find({playerId}) to find a row"); + Debug.Assert( + found.Id == playerId && found.Name == after, + $"Expected AllViewPkPlayers.Id.Find({playerId}) to return updated row '{after}'" + ); waiting++; phaseHandle?.UnsubscribeThen(_ => diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/AllViewPkPlayers.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/AllViewPkPlayers.g.cs index 08fa30dc034..3f1603af6f6 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/AllViewPkPlayers.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/AllViewPkPlayers.g.cs @@ -17,8 +17,18 @@ public sealed class AllViewPkPlayersHandle : RemoteTableHandle "all_view_pk_players"; + public sealed class IdUniqueIndex : UniqueIndexBase + { + protected override ulong GetKey(ViewPkPlayer row) => row.Id; + + public IdUniqueIndex(AllViewPkPlayersHandle table) : base(table) { } + } + + public readonly IdUniqueIndex Id; + internal AllViewPkPlayersHandle(DbConnection conn) : base(conn) { + Id = new(this); } protected override object GetPrimaryKey(ViewPkPlayer row) => row.Id; diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ProceduralViewPkPlayers.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ProceduralViewPkPlayers.g.cs index 5c7cbd8aa46..47eca2402ba 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ProceduralViewPkPlayers.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/ProceduralViewPkPlayers.g.cs @@ -17,8 +17,18 @@ public sealed class ProceduralViewPkPlayersHandle : RemoteTableHandle "procedural_view_pk_players"; + public sealed class IdUniqueIndex : UniqueIndexBase + { + protected override ulong GetKey(ViewPkPlayer row) => row.Id; + + public IdUniqueIndex(ProceduralViewPkPlayersHandle table) : base(table) { } + } + + public readonly IdUniqueIndex Id; + internal ProceduralViewPkPlayersHandle(DbConnection conn) : base(conn) { + Id = new(this); } protected override object GetPrimaryKey(ViewPkPlayer row) => row.Id; diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/SenderViewPkPlayersA.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/SenderViewPkPlayersA.g.cs index 8a6eb5b0360..6e67fb6f849 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/SenderViewPkPlayersA.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/SenderViewPkPlayersA.g.cs @@ -17,8 +17,18 @@ public sealed class SenderViewPkPlayersAHandle : RemoteTableHandle "sender_view_pk_players_a"; + public sealed class IdUniqueIndex : UniqueIndexBase + { + protected override ulong GetKey(ViewPkPlayer row) => row.Id; + + public IdUniqueIndex(SenderViewPkPlayersAHandle table) : base(table) { } + } + + public readonly IdUniqueIndex Id; + internal SenderViewPkPlayersAHandle(DbConnection conn) : base(conn) { + Id = new(this); } protected override object GetPrimaryKey(ViewPkPlayer row) => row.Id; diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/SenderViewPkPlayersB.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/SenderViewPkPlayersB.g.cs index b58d3adde95..11121c60b49 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/SenderViewPkPlayersB.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/SenderViewPkPlayersB.g.cs @@ -17,8 +17,18 @@ public sealed class SenderViewPkPlayersBHandle : RemoteTableHandle "sender_view_pk_players_b"; + public sealed class IdUniqueIndex : UniqueIndexBase + { + protected override ulong GetKey(ViewPkPlayer row) => row.Id; + + public IdUniqueIndex(SenderViewPkPlayersBHandle table) : base(table) { } + } + + public readonly IdUniqueIndex Id; + internal SenderViewPkPlayersBHandle(DbConnection conn) : base(conn) { + Id = new(this); } protected override object GetPrimaryKey(ViewPkPlayer row) => row.Id; diff --git a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/WhereTestQuery.g.cs b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/WhereTestQuery.g.cs index 6ebbfe7db97..e46b332ad52 100644 --- a/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/WhereTestQuery.g.cs +++ b/sdks/csharp/examples~/regression-tests/client/module_bindings/Tables/WhereTestQuery.g.cs @@ -17,8 +17,18 @@ public sealed class WhereTestQueryHandle : RemoteTableHandle "where_test_query"; + public sealed class IdUniqueIndex : UniqueIndexBase + { + protected override uint GetKey(WhereTest row) => row.Id; + + public IdUniqueIndex(WhereTestQueryHandle table) : base(table) { } + } + + public readonly IdUniqueIndex Id; + internal WhereTestQueryHandle(DbConnection conn) : base(conn) { + Id = new(this); } protected override object GetPrimaryKey(WhereTest row) => row.Id; diff --git a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/AllViewPkPlayersTable.g.cpp b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/AllViewPkPlayersTable.g.cpp index 24c0c58e9f2..6dd9da0f6e8 100644 --- a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/AllViewPkPlayersTable.g.cpp +++ b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/AllViewPkPlayersTable.g.cpp @@ -13,6 +13,11 @@ void UAllViewPkPlayersTable::PostInitialize() Data = MakeShared>(); TSharedPtr> AllViewPkPlayersTable = Data->GetOrAdd(TableName); + AllViewPkPlayersTable->AddUniqueConstraint("id", [](const FViewPkPlayerType& Row) -> const uint64& { + return Row.Id; }); + + Id = NewObject(this); + Id->SetCache(AllViewPkPlayersTable); /***/ } diff --git a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/SenderViewPkPlayersATable.g.cpp b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/SenderViewPkPlayersATable.g.cpp index 5d772b8f853..c0bb7ba242d 100644 --- a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/SenderViewPkPlayersATable.g.cpp +++ b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/SenderViewPkPlayersATable.g.cpp @@ -13,6 +13,11 @@ void USenderViewPkPlayersATable::PostInitialize() Data = MakeShared>(); TSharedPtr> SenderViewPkPlayersATable = Data->GetOrAdd(TableName); + SenderViewPkPlayersATable->AddUniqueConstraint("id", [](const FViewPkPlayerType& Row) -> const uint64& { + return Row.Id; }); + + Id = NewObject(this); + Id->SetCache(SenderViewPkPlayersATable); /***/ } diff --git a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/SenderViewPkPlayersBTable.g.cpp b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/SenderViewPkPlayersBTable.g.cpp index a1d8d919897..0c5bac5fa31 100644 --- a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/SenderViewPkPlayersBTable.g.cpp +++ b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/ModuleBindings/Tables/SenderViewPkPlayersBTable.g.cpp @@ -13,6 +13,11 @@ void USenderViewPkPlayersBTable::PostInitialize() Data = MakeShared>(); TSharedPtr> SenderViewPkPlayersBTable = Data->GetOrAdd(TableName); + SenderViewPkPlayersBTable->AddUniqueConstraint("id", [](const FViewPkPlayerType& Row) -> const uint64& { + return Row.Id; }); + + Id = NewObject(this); + Id->SetCache(SenderViewPkPlayersBTable); /***/ } diff --git a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/Tests/TestHandler.cpp b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/Tests/TestHandler.cpp index ad1614f45db..c091965b164 100644 --- a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/Tests/TestHandler.cpp +++ b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Private/Tests/TestHandler.cpp @@ -1,5 +1,7 @@ #include "Tests/TestHandler.h" +#include "ModuleBindings/Tables/AllViewPkPlayersTable.g.h" + namespace { bool ValidateInsertedRow(UViewPkRuntimeHandler* Handler, const FString& StepName, const FViewPkPlayerType& Value) @@ -51,10 +53,17 @@ void UViewPkRuntimeHandler::OnAllViewPkPlayersInsert(const FEventContext&, const } } -void UViewPkRuntimeHandler::OnAllViewPkPlayersUpdate(const FEventContext&, const FViewPkPlayerType& OldValue, const FViewPkPlayerType& NewValue) +void UViewPkRuntimeHandler::OnAllViewPkPlayersUpdate(const FEventContext& Context, const FViewPkPlayerType& OldValue, const FViewPkPlayerType& NewValue) { if (ValidateUpdatedRows(this, TEXT("all_view_pk_players_update"), OldValue, NewValue)) { + const FViewPkPlayerType Found = Context.Db->AllViewPkPlayers->Id->Find(ExpectedId); + if (Found.Id != ExpectedId || Found.Name != UpdatedName) + { + Counter->MarkFailure(TEXT("all_view_pk_players_update"), FString::Printf(TEXT("Expected Id.Find(%llu) to return updated row %s"), static_cast(ExpectedId), *UpdatedName)); + Counter->Abort(); + return; + } Counter->MarkSuccess(TEXT("all_view_pk_players_update")); } } diff --git a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/AllViewPkPlayersTable.g.h b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/AllViewPkPlayersTable.g.h index 0a0879db4e1..8fe031c272a 100644 --- a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/AllViewPkPlayersTable.g.h +++ b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/AllViewPkPlayersTable.g.h @@ -12,12 +12,52 @@ #include "DBCache/TableCache.h" #include "AllViewPkPlayersTable.g.generated.h" +UCLASS(Blueprintable) +class TESTVIEWPKCLIENT_API UAllViewPkPlayersIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> IdIndexHelper; + +public: + UAllViewPkPlayersIdUniqueIndex() + // Initialize the helper with the specific unique index name + : IdIndexHelper("id") { + } + + /** + * Finds a AllViewPkPlayers by their unique id. + * @param Key The id to search for. + * @return The found FViewPkPlayerType, or a default-constructed FViewPkPlayerType if not found. + */ + // NOTE: Not exposed to Blueprint because uint64 types are not Blueprint-compatible + FViewPkPlayerType Find(uint64 Key) + { + // Simply delegate the call to the internal helper + return IdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InAllViewPkPlayersCache) + { + IdIndexHelper.Cache = InAllViewPkPlayersCache; + } +}; +/***/ + UCLASS(BlueprintType) class TESTVIEWPKCLIENT_API UAllViewPkPlayersTable : public URemoteTable { GENERATED_BODY() public: + UPROPERTY(BlueprintReadOnly) + UAllViewPkPlayersIdUniqueIndex* Id; + void PostInitialize(); /** Update function for all_view_pk_players table*/ diff --git a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/SenderViewPkPlayersATable.g.h b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/SenderViewPkPlayersATable.g.h index b0ee3b0a181..36ed12c1130 100644 --- a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/SenderViewPkPlayersATable.g.h +++ b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/SenderViewPkPlayersATable.g.h @@ -12,12 +12,52 @@ #include "DBCache/TableCache.h" #include "SenderViewPkPlayersATable.g.generated.h" +UCLASS(Blueprintable) +class TESTVIEWPKCLIENT_API USenderViewPkPlayersAIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> IdIndexHelper; + +public: + USenderViewPkPlayersAIdUniqueIndex() + // Initialize the helper with the specific unique index name + : IdIndexHelper("id") { + } + + /** + * Finds a SenderViewPkPlayersA by their unique id. + * @param Key The id to search for. + * @return The found FViewPkPlayerType, or a default-constructed FViewPkPlayerType if not found. + */ + // NOTE: Not exposed to Blueprint because uint64 types are not Blueprint-compatible + FViewPkPlayerType Find(uint64 Key) + { + // Simply delegate the call to the internal helper + return IdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InSenderViewPkPlayersACache) + { + IdIndexHelper.Cache = InSenderViewPkPlayersACache; + } +}; +/***/ + UCLASS(BlueprintType) class TESTVIEWPKCLIENT_API USenderViewPkPlayersATable : public URemoteTable { GENERATED_BODY() public: + UPROPERTY(BlueprintReadOnly) + USenderViewPkPlayersAIdUniqueIndex* Id; + void PostInitialize(); /** Update function for sender_view_pk_players_a table*/ diff --git a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/SenderViewPkPlayersBTable.g.h b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/SenderViewPkPlayersBTable.g.h index c47ab25f1a3..f9cf060a50d 100644 --- a/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/SenderViewPkPlayersBTable.g.h +++ b/sdks/unreal/tests/TestViewPkClient/Source/TestViewPkClient/Public/ModuleBindings/Tables/SenderViewPkPlayersBTable.g.h @@ -12,12 +12,52 @@ #include "DBCache/TableCache.h" #include "SenderViewPkPlayersBTable.g.generated.h" +UCLASS(Blueprintable) +class TESTVIEWPKCLIENT_API USenderViewPkPlayersBIdUniqueIndex : public UObject +{ + GENERATED_BODY() + +private: + // Declare an instance of your templated helper. + // It's private because the UObject wrapper will expose its functionality. + FUniqueIndexHelper> IdIndexHelper; + +public: + USenderViewPkPlayersBIdUniqueIndex() + // Initialize the helper with the specific unique index name + : IdIndexHelper("id") { + } + + /** + * Finds a SenderViewPkPlayersB by their unique id. + * @param Key The id to search for. + * @return The found FViewPkPlayerType, or a default-constructed FViewPkPlayerType if not found. + */ + // NOTE: Not exposed to Blueprint because uint64 types are not Blueprint-compatible + FViewPkPlayerType Find(uint64 Key) + { + // Simply delegate the call to the internal helper + return IdIndexHelper.FindUniqueIndex(Key); + } + + // A public setter to provide the cache to the helper after construction + // This is a common pattern when the cache might be created or provided by another system. + void SetCache(TSharedPtr> InSenderViewPkPlayersBCache) + { + IdIndexHelper.Cache = InSenderViewPkPlayersBCache; + } +}; +/***/ + UCLASS(BlueprintType) class TESTVIEWPKCLIENT_API USenderViewPkPlayersBTable : public URemoteTable { GENERATED_BODY() public: + UPROPERTY(BlueprintReadOnly) + USenderViewPkPlayersBIdUniqueIndex* Id; + void PostInitialize(); /** Update function for sender_view_pk_players_b table*/