11
11
using SpacetimeDB . Internal ;
12
12
using SpacetimeDB . ClientApi ;
13
13
using Thread = System . Threading . Thread ;
14
+ using System . Diagnostics ;
14
15
15
16
16
17
namespace SpacetimeDB
@@ -211,11 +212,10 @@ struct UnprocessedMessage
211
212
212
213
struct ProcessedDatabaseUpdate
213
214
{
214
- // Map: table handles -> (primary key -> DbValue).
215
- // If a particular table has no primary key, the "primary key" is just a byte[]
216
- // storing the BSATN encoding of the row.
217
- // See Decode(...).
218
- public Dictionary < IRemoteTableHandle , MultiDictionaryDelta < object , DbValue > > Updates ;
215
+ // Map: table handles -> (primary key -> IStructuralReadWrite).
216
+ // If a particular table has no primary key, the "primary key" is just the row itself.
217
+ // This is valid because any [SpacetimeDB.Type] automatically has a correct Equals and HashSet implementation.
218
+ public Dictionary < IRemoteTableHandle , MultiDictionaryDelta < object , IStructuralReadWrite > > Updates ;
219
219
220
220
// Can't override the default constructor. Make sure you use this one!
221
221
public static ProcessedDatabaseUpdate New ( )
@@ -225,13 +225,11 @@ public static ProcessedDatabaseUpdate New()
225
225
return result ;
226
226
}
227
227
228
- public MultiDictionaryDelta < object , DbValue > DeltaForTable ( IRemoteTableHandle table )
228
+ public MultiDictionaryDelta < object , IStructuralReadWrite > DeltaForTable ( IRemoteTableHandle table )
229
229
{
230
230
if ( ! Updates . TryGetValue ( table , out var delta ) )
231
231
{
232
- // Make sure we use GenericEqualityComparer here, since it handles byte[]s and arbitrary primary key types
233
- // correctly.
234
- delta = new MultiDictionaryDelta < object , DbValue > ( GenericEqualityComparer . Instance , DbValueComparer . Instance ) ;
232
+ delta = new MultiDictionaryDelta < object , IStructuralReadWrite > ( EqualityComparer < object > . Default , EqualityComparer < object > . Default ) ;
235
233
Updates [ table ] = delta ;
236
234
}
237
235
@@ -265,18 +263,20 @@ struct ProcessedMessage
265
263
/// If not, the BSATN for the entire row is used instead.
266
264
/// </summary>
267
265
/// <param name="table"></param>
268
- /// <param name="bin "></param>
266
+ /// <param name="reader "></param>
269
267
/// <param name="primaryKey"></param>
270
268
/// <returns></returns>
271
- static DbValue Decode ( IRemoteTableHandle table , byte [ ] bin , out object primaryKey )
269
+ static IStructuralReadWrite Decode ( IRemoteTableHandle table , BinaryReader reader , out object primaryKey )
272
270
{
273
- var obj = table . DecodeValue ( bin ) ;
271
+ var obj = table . DecodeValue ( reader ) ;
272
+
274
273
// TODO(1.1): we should exhaustively check that GenericEqualityComparer works
275
274
// for all types that are allowed to be primary keys.
276
275
var primaryKey_ = table . GetPrimaryKey ( obj ) ;
277
- primaryKey_ ??= bin ;
276
+ primaryKey_ ??= obj ;
278
277
primaryKey = primaryKey_ ;
279
- return new ( obj , bin ) ;
278
+
279
+ return obj ;
280
280
}
281
281
282
282
private static readonly Status Committed = new Status . Committed ( default ) ;
@@ -353,24 +353,28 @@ private static QueryUpdate DecompressDecodeQueryUpdate(CompressableQueryUpdate u
353
353
return new QueryUpdate . BSATN ( ) . Read ( new BinaryReader ( memoryStream ) ) ;
354
354
}
355
355
356
- private static IEnumerable < byte [ ] > BsatnRowListIter ( BsatnRowList list )
357
- {
358
- var rowsData = list . RowsData ;
359
356
360
- return list . SizeHint switch
361
- {
362
- RowSizeHint . FixedSize ( var size ) => Enumerable
363
- . Range ( 0 , rowsData . Count / size )
364
- . Select ( index => rowsData . Skip ( index * size ) . Take ( size ) . ToArray ( ) ) ,
365
-
366
- RowSizeHint . RowOffsets ( var offsets ) => offsets . Zip (
367
- offsets . Skip ( 1 ) . Append ( ( ulong ) rowsData . Count ) ,
368
- ( start , end ) => rowsData . Take ( ( int ) end ) . Skip ( ( int ) start ) . ToArray ( )
369
- ) ,
370
-
371
- _ => throw new InvalidOperationException ( "Unknown RowSizeHint variant" ) ,
372
- } ;
373
- }
357
+ /// <summary>
358
+ /// Prepare to read a BsatnRowList.
359
+ ///
360
+ /// This could return an IEnumerable, but we return the reader and row count directly to avoid an allocation.
361
+ /// It is legitimate to repeatedly call <c>IStructuralReadWrite.Read<T></c> <c>rowCount</c> times on the resulting
362
+ /// BinaryReader:
363
+ /// Our decoding infrastructure guarantees that reading a value consumes the correct number of bytes
364
+ /// from the BinaryReader. (This is easy because BSATN doesn't have padding.)
365
+ /// </summary>
366
+ /// <param name="list"></param>
367
+ /// <returns>A reader for the rows of the list and a count of rows.</returns>
368
+ private static ( BinaryReader reader , int rowCount ) ParseRowList ( BsatnRowList list ) =>
369
+ (
370
+ new BinaryReader ( new ListStream ( list . RowsData ) ) ,
371
+ list . SizeHint switch
372
+ {
373
+ RowSizeHint . FixedSize ( var size ) => list . RowsData . Count / size ,
374
+ RowSizeHint . RowOffsets ( var offsets ) => offsets . Count ,
375
+ _ => throw new NotImplementedException ( )
376
+ }
377
+ ) ;
374
378
375
379
#if UNITY_WEBGL && ! UNITY_EDITOR
376
380
IEnumerator PreProcessMessages ( )
@@ -455,9 +459,10 @@ void PreProcessInsertOnlyTable(IRemoteTableHandle table, TableUpdate update, Pro
455
459
{
456
460
Log . Warn ( "Non-insert during an insert-only server message!" ) ;
457
461
}
458
- foreach ( var bin in BsatnRowListIter ( qu . Inserts ) )
462
+ var ( insertReader , insertRowCount ) = ParseRowList ( qu . Inserts ) ;
463
+ for ( var i = 0 ; i < insertRowCount ; i ++ )
459
464
{
460
- var obj = Decode ( table , bin , out var pk ) ;
465
+ var obj = Decode ( table , insertReader , out var pk ) ;
461
466
delta . Add ( pk , obj ) ;
462
467
}
463
468
}
@@ -473,9 +478,11 @@ void PreProcessDeleteOnlyTable(IRemoteTableHandle table, TableUpdate update, Pro
473
478
{
474
479
Log . Warn ( "Non-delete during a delete-only operation!" ) ;
475
480
}
476
- foreach ( var bin in BsatnRowListIter ( qu . Deletes ) )
481
+
482
+ var ( deleteReader , deleteRowCount ) = ParseRowList ( qu . Deletes ) ;
483
+ for ( var i = 0 ; i < deleteRowCount ; i ++ )
477
484
{
478
- var obj = Decode ( table , bin , out var pk ) ;
485
+ var obj = Decode ( table , deleteReader , out var pk ) ;
479
486
delta . Remove ( pk , obj ) ;
480
487
}
481
488
}
@@ -491,14 +498,17 @@ void PreProcessTable(IRemoteTableHandle table, TableUpdate update, ProcessedData
491
498
// Because we are accumulating into a MultiDictionaryDelta that will be applied all-at-once
492
499
// to the table, it doesn't matter that we call Add before Remove here.
493
500
494
- foreach ( var bin in BsatnRowListIter ( qu . Inserts ) )
501
+ var ( insertReader , insertRowCount ) = ParseRowList ( qu . Inserts ) ;
502
+ for ( var i = 0 ; i < insertRowCount ; i ++ )
495
503
{
496
- var obj = Decode ( table , bin , out var pk ) ;
504
+ var obj = Decode ( table , insertReader , out var pk ) ;
497
505
delta . Add ( pk , obj ) ;
498
506
}
499
- foreach ( var bin in BsatnRowListIter ( qu . Deletes ) )
507
+
508
+ var ( deleteReader , deleteRowCount ) = ParseRowList ( qu . Deletes ) ;
509
+ for ( var i = 0 ; i < deleteRowCount ; i ++ )
500
510
{
501
- var obj = Decode ( table , bin , out var pk ) ;
511
+ var obj = Decode ( table , deleteReader , out var pk ) ;
502
512
delta . Remove ( pk , obj ) ;
503
513
}
504
514
}
@@ -1002,9 +1012,13 @@ T[] LogAndThrow(string error)
1002
1012
return LogAndThrow ( $ "Mismatched result type, expected { typeof ( T ) } but got { resultTable . TableName } ") ;
1003
1013
}
1004
1014
1005
- return BsatnRowListIter ( resultTable . Rows )
1006
- . Select ( BSATNHelpers . Decode < T > )
1007
- . ToArray ( ) ;
1015
+ var ( resultReader , resultCount ) = ParseRowList ( resultTable . Rows ) ;
1016
+ var output = new T [ resultCount ] ;
1017
+ for ( int i = 0 ; i < resultCount ; i ++ )
1018
+ {
1019
+ output [ i ] = IStructuralReadWrite . Read < T > ( resultReader ) ;
1020
+ }
1021
+ return output ;
1008
1022
}
1009
1023
1010
1024
public bool IsActive => webSocket . IsConnected ;
@@ -1056,32 +1070,4 @@ public uint Next()
1056
1070
return lastAllocated ;
1057
1071
}
1058
1072
}
1059
- internal readonly struct DbValue
1060
- {
1061
- public readonly IStructuralReadWrite value ;
1062
- public readonly byte [ ] bytes ;
1063
-
1064
- public DbValue ( IStructuralReadWrite value , byte [ ] bytes )
1065
- {
1066
- this . value = value ;
1067
- this . bytes = bytes ;
1068
- }
1069
-
1070
- // TODO: having a nice ToString here would give better way better errors when applying table deltas,
1071
- // but it's tricky to do that generically.
1072
- }
1073
-
1074
- /// <summary>
1075
- /// DbValue comparer that uses BSATN-encoded records to compare DbValues for equality.
1076
- /// </summary>
1077
- internal readonly struct DbValueComparer : IEqualityComparer < DbValue >
1078
- {
1079
- public static DbValueComparer Instance = new ( ) ;
1080
-
1081
- public bool Equals ( DbValue x , DbValue y ) =>
1082
- ByteArrayComparer . Instance . Equals ( x . bytes , y . bytes ) ;
1083
-
1084
- public int GetHashCode ( DbValue obj ) =>
1085
- ByteArrayComparer . Instance . GetHashCode ( obj . bytes ) ;
1086
- }
1087
1073
}
0 commit comments