From ea22959829831f4912c8f27484930f44752504f7 Mon Sep 17 00:00:00 2001 From: Ingvar Stepanyan Date: Thu, 9 May 2024 11:26:48 -0400 Subject: [PATCH] NFC: inline insert/delete update handling --- .../client/module_bindings/Message.cs | 7 - .../quickstart/client/module_bindings/User.cs | 7 - src/SpacetimeDBClient.cs | 291 +++++------------- 3 files changed, 84 insertions(+), 221 deletions(-) diff --git a/examples/quickstart/client/module_bindings/Message.cs b/examples/quickstart/client/module_bindings/Message.cs index 25800e8..438777e 100644 --- a/examples/quickstart/client/module_bindings/Message.cs +++ b/examples/quickstart/client/module_bindings/Message.cs @@ -109,11 +109,9 @@ public static bool ComparePrimaryKey(SpacetimeDB.SATS.AlgebraicType t, Spacetime public delegate void InsertEventHandler(Message insertedValue, SpacetimeDB.Types.ReducerEvent dbEvent); public delegate void DeleteEventHandler(Message deletedValue, SpacetimeDB.Types.ReducerEvent dbEvent); - public delegate void RowUpdateEventHandler(SpacetimeDBClient.TableOp op, Message oldValue, Message newValue, SpacetimeDB.Types.ReducerEvent dbEvent); public static event InsertEventHandler OnInsert; public static event DeleteEventHandler OnBeforeDelete; public static event DeleteEventHandler OnDelete; - public static event RowUpdateEventHandler OnRowUpdate; public static void OnInsertEvent(object newValue, ClientApi.Event dbEvent) { @@ -129,10 +127,5 @@ public static void OnDeleteEvent(object oldValue, ClientApi.Event dbEvent) { OnDelete?.Invoke((Message)oldValue,(ReducerEvent)dbEvent?.FunctionCall.CallInfo); } - - public static void OnRowUpdateEvent(SpacetimeDBClient.TableOp op, object oldValue, object newValue, ClientApi.Event dbEvent) - { - OnRowUpdate?.Invoke(op, (Message)oldValue,(Message)newValue,(ReducerEvent)dbEvent?.FunctionCall.CallInfo); - } } } diff --git a/examples/quickstart/client/module_bindings/User.cs b/examples/quickstart/client/module_bindings/User.cs index 85c7ca1..04230fc 100644 --- a/examples/quickstart/client/module_bindings/User.cs +++ b/examples/quickstart/client/module_bindings/User.cs @@ -126,12 +126,10 @@ public static SpacetimeDB.SATS.AlgebraicType GetPrimaryKeyType(SpacetimeDB.SATS. public delegate void InsertEventHandler(User insertedValue, SpacetimeDB.Types.ReducerEvent dbEvent); public delegate void UpdateEventHandler(User oldValue, User newValue, SpacetimeDB.Types.ReducerEvent dbEvent); public delegate void DeleteEventHandler(User deletedValue, SpacetimeDB.Types.ReducerEvent dbEvent); - public delegate void RowUpdateEventHandler(SpacetimeDBClient.TableOp op, User oldValue, User newValue, SpacetimeDB.Types.ReducerEvent dbEvent); public static event InsertEventHandler OnInsert; public static event UpdateEventHandler OnUpdate; public static event DeleteEventHandler OnBeforeDelete; public static event DeleteEventHandler OnDelete; - public static event RowUpdateEventHandler OnRowUpdate; public static void OnInsertEvent(object newValue, ClientApi.Event dbEvent) { @@ -152,10 +150,5 @@ public static void OnDeleteEvent(object oldValue, ClientApi.Event dbEvent) { OnDelete?.Invoke((User)oldValue,(ReducerEvent)dbEvent?.FunctionCall.CallInfo); } - - public static void OnRowUpdateEvent(SpacetimeDBClient.TableOp op, object oldValue, object newValue, ClientApi.Event dbEvent) - { - OnRowUpdate?.Invoke(op, (User)oldValue,(User)newValue,(ReducerEvent)dbEvent?.FunctionCall.CallInfo); - } } } diff --git a/src/SpacetimeDBClient.cs b/src/SpacetimeDBClient.cs index 9c1263d..df642cd 100644 --- a/src/SpacetimeDBClient.cs +++ b/src/SpacetimeDBClient.cs @@ -20,14 +20,6 @@ namespace SpacetimeDB { public class SpacetimeDBClient { - public enum TableOp - { - Insert, - Delete, - Update, - NoChange, - } - public class ReducerCallRequest { public string fn; @@ -39,14 +31,23 @@ public class SubscriptionRequest public string subscriptionQuery; } - public struct DbOp + struct DbValue + { + public object value; + public byte[] bytes; + + public DbValue(object value, byte[] bytes) + { + this.value = value; + this.bytes = bytes; + } + } + + struct DbOp { public ClientCache.TableCache table; - public TableOp op; - public object newValue; - public object oldValue; - public byte[] deletedBytes; - public byte[] insertedBytes; + public DbValue? delete; + public DbValue? insert; public AlgebraicValue rowValue; } @@ -326,11 +327,7 @@ HashSet GetInsertHashSet(string tableName, int tableSize) var op = new DbOp { table = table, - deletedBytes = null, - insertedBytes = rowBytes, - op = TableOp.Insert, - newValue = obj, - oldValue = null, + insert = new(obj, rowBytes), rowValue = deserializedRow, }; @@ -380,24 +377,29 @@ HashSet GetInsertHashSet(string tableName, int tableSize) var op = new DbOp { table = table, - deletedBytes = - row.Op == TableRowOperation.Types.OperationType.Delete ? rowBytes : null, - insertedBytes = - row.Op == TableRowOperation.Types.OperationType.Delete ? null : rowBytes, - op = row.Op == TableRowOperation.Types.OperationType.Delete - ? TableOp.Delete - : TableOp.Insert, - newValue = row.Op == TableRowOperation.Types.OperationType.Delete ? null : obj, - oldValue = row.Op == TableRowOperation.Types.OperationType.Delete ? obj : null, rowValue = deserializedRow, }; + var dbValue = new DbValue(obj, rowBytes); + + switch (row.Op) + { + case TableRowOperation.Types.OperationType.Insert: + op.insert = dbValue; + break; + case TableRowOperation.Types.OperationType.Delete: + op.delete = dbValue; + break; + default: + throw new ArgumentOutOfRangeException(); + } + if (primaryKeyType != null) { var primaryKeyLookup = GetPrimaryKeyLookup(tableName, primaryKeyType); - if (primaryKeyLookup.TryGetValue(primaryKeyValue, out var value)) + if (primaryKeyLookup.TryGetValue(primaryKeyValue, out var oldOp)) { - if (value.op == op.op || value.op == TableOp.Update) + if ((op.insert is not null && oldOp.insert is not null) || (op.delete is not null && oldOp.delete is not null)) { Logger.LogWarning($"Update with the same primary key was " + $"applied multiple times! tableName={tableName}"); @@ -405,23 +407,13 @@ HashSet GetInsertHashSet(string tableName, int tableSize) // SpacetimeDB side. continue; } - - var insertOp = op; - var deleteOp = value; - if (op.op == TableOp.Delete) - { - insertOp = value; - deleteOp = op; - } + var (insertOp, deleteOp) = op.insert is not null ? (op, oldOp) : (oldOp, op); primaryKeyLookup[primaryKeyValue] = new DbOp { table = insertOp.table, - op = TableOp.Update, - newValue = insertOp.newValue, - oldValue = deleteOp.oldValue, - deletedBytes = deleteOp.deletedBytes, - insertedBytes = insertOp.insertedBytes, + delete = deleteOp.delete, + insert = insertOp.insert, rowValue = insertOp.rowValue, }; } @@ -531,11 +523,7 @@ void ExecuteStateDiff() dbOps.Add(new DbOp { table = table, - op = TableOp.Delete, - newValue = null, - oldValue = table.entries[rowBytes].Item2, - deletedBytes = rowBytes, - insertedBytes = null + delete = new(table.entries[rowBytes].Item2, rowBytes), }); } } @@ -595,15 +583,16 @@ public void Connect(string token, string uri, string addressOrName) private void OnMessageProcessCompleteUpdate(Message message, List dbOps) { + var transactionEvent = message.TransactionUpdate?.Event!; + // First trigger OnBeforeDelete foreach (var update in dbOps) { - if (update.op == TableOp.Delete) + if (update.delete is { value: var oldValue }) { try { - update.table.BeforeDeleteCallback?.Invoke(update.oldValue, - message.TransactionUpdate?.Event); + update.table.BeforeDeleteCallback?.Invoke(oldValue, transactionEvent); } catch (Exception e) { @@ -612,177 +601,65 @@ private void OnMessageProcessCompleteUpdate(Message message, List dbOps) } } - void InternalDeleteCallback(DbOp op) - { - if (op.oldValue != null) - { - op.table.InternalValueDeletedCallback(op.oldValue); - } - else - { - Logger.LogError("Delete issued, but no value was present!"); - } - } - - void InternalInsertCallback(DbOp op) - { - if (op.newValue != null) - { - op.table.InternalValueInsertedCallback(op.newValue); - } - else - { - Logger.LogError("Insert issued, but no value was present!"); - } - } - // Apply all of the state for (var i = 0; i < dbOps.Count; i++) { // TODO: Reimplement updates when we add support for primary keys var update = dbOps[i]; - switch (update.op) + + if (update.delete is {} delete) { - case TableOp.Delete: - if (dbOps[i].table.DeleteEntry(update.deletedBytes)) - { - InternalDeleteCallback(update); - } - else - { - var op = dbOps[i]; - op.op = TableOp.NoChange; - dbOps[i] = op; - } - break; - case TableOp.Insert: - if (dbOps[i].table.InsertEntry(update.insertedBytes, update.rowValue)) - { - InternalInsertCallback(update); - } - else - { - var op = dbOps[i]; - op.op = TableOp.NoChange; - dbOps[i] = op; - } - break; - case TableOp.Update: - if (dbOps[i].table.DeleteEntry(update.deletedBytes)) - { - InternalDeleteCallback(update); - } - else - { - var op = dbOps[i]; - op.op = TableOp.NoChange; - dbOps[i] = op; - } + if (update.table.DeleteEntry(delete.bytes)) + { + update.table.InternalValueDeletedCallback(delete.value); + } + else + { + update.delete = null; + dbOps[i] = update; + } + } - if (dbOps[i].table.InsertEntry(update.insertedBytes, update.rowValue)) - { - InternalInsertCallback(update); - } - else - { - var op = dbOps[i]; - op.op = TableOp.NoChange; - dbOps[i] = op; - } - break; - default: - throw new ArgumentOutOfRangeException(); + if (update.insert is {} insert) + { + if (update.table.InsertEntry(insert.bytes, update.rowValue)) + { + update.table.InternalValueInsertedCallback(insert.value); + } + else + { + update.insert = null; + dbOps[i] = update; + } } } // Send out events - var updateCount = dbOps.Count; - for (var i = 0; i < updateCount; i++) + foreach (var dbOp in dbOps) { - var tableName = dbOps[i].table.ClientTableType.Name; - var tableOp = dbOps[i].op; - var oldValue = dbOps[i].oldValue; - var newValue = dbOps[i].newValue; - - switch (tableOp) - { - case TableOp.Insert: - if (oldValue == null && newValue != null) - { - try - { - if (dbOps[i].table.InsertCallback != null) - { - dbOps[i].table.InsertCallback.Invoke(newValue, - message.TransactionUpdate?.Event); - } - } - catch (Exception e) - { - Logger.LogException(e); - } - } - else - { - Logger.LogError("Failed to send callback: invalid insert!"); - } - - break; - case TableOp.Delete: - { - if (oldValue != null && newValue == null) - { - if (dbOps[i].table.DeleteCallback != null) - { - try - { - dbOps[i].table.DeleteCallback.Invoke(oldValue, - message.TransactionUpdate?.Event); - } - catch (Exception e) - { - Logger.LogException(e); - } - } - } - else - { - Logger.LogError("Failed to send callback: invalid delete"); - } + try + { + switch (dbOp) + { + case { insert: { value: var newValue }, delete: { value: var oldValue } }: + dbOp.table.UpdateCallback?.Invoke(oldValue, newValue, transactionEvent); + break; - break; - } - case TableOp.Update: - { - if (oldValue != null && newValue != null) - { - try - { - if (dbOps[i].table.UpdateCallback != null) - { - dbOps[i].table.UpdateCallback.Invoke(oldValue, newValue, - message.TransactionUpdate?.Event); - } - } - catch (Exception e) - { - Logger.LogException(e); - } - } - else - { - Logger.LogError("Failed to send callback: invalid update"); - } + case { insert: { value: var newValue } }: + dbOp.table.InsertCallback?.Invoke(newValue, transactionEvent); + break; - break; - } - case TableOp.NoChange: - // noop - break; - default: - throw new ArgumentOutOfRangeException(); - } + case { delete: { value: var oldValue } }: + dbOp.table.DeleteCallback?.Invoke(oldValue, transactionEvent); + break; } + } + catch (Exception e) + { + Logger.LogException(e); + } + } + } private void OnMessageProcessComplete(Message message, List dbOps) {