Skip to content

Commit 29276a5

Browse files
committed
Add Objects spec for applying incoming OBJECT messages
Resolves PUB-1825, PUB-1826
1 parent 7d4c215 commit 29276a5

File tree

1 file changed

+98
-12
lines changed

1 file changed

+98
-12
lines changed

textile/objects-features.textile

Lines changed: 98 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,19 @@ h3(#realtime-objects). RealtimeObjects
3535
** @(RTO4b)@ If the @HAS_OBJECTS@ flag is 0 or there is no @flags@ field, the sync sequence must be considered complete immediately, and the client library must perform the following actions in order:
3636
*** @(RTO4b1)@ All objects except the one with id @root@ must be removed from the internal @ObjectsPool@
3737
*** @(RTO4b2)@ The data for the @LiveMap@ with id @root@ must be cleared by setting it to a zero-value per "RTLM4":#RTLM4
38-
*** @(RTO4b3)@ The @SyncObjectsPool@ must be cleared
38+
*** @(RTO4b3)@ The @SyncObjectsPool@ list must be cleared
39+
*** @(RTO4b5)@ The @BufferedObjectOperations@ list must be cleared
3940
*** @(RTO4b4)@ Perform the actions for objects sync completion as described in "RTO5c":#RTO5c
4041
* @(RTO5)@ The realtime system reserves the right to initiate an objects sync of the objects on a channel at any point once a channel is attached. A server initiated objects sync provides Ably with a means to send a complete list of objects present on the channel at any point
4142
** @(RTO5a)@ When an @OBJECT_SYNC@ @ProtocolMessage@ is received with a @channel@ attribute matching the channel name, the client library must parse the @channelSerial@ attribute:
4243
*** @(RTO5a1)@ The @channelSerial@ is used as the sync cursor and is a two-part identifier: @<sequence id>:<cursor value>@
4344
*** @(RTO5a2)@ If a new sequence id is sent from Ably, the client library must treat it as the start of a new objects sync sequence, and any previous in-flight sync must be discarded:
44-
**** @(RTO5a2a)@ The current @SyncObjectsPool@ list must be cleared
45+
**** @(RTO5a2a)@ The @SyncObjectsPool@ list must be cleared
46+
**** @(RTO5a2b)@ The @BufferedObjectOperations@ list must be cleared
4547
*** @(RTO5a3)@ If the sequence id matches the previously received sequence id, the client library should continue the sync process
4648
*** @(RTO5a4)@ The objects sync sequence for that sequence identifier is considered complete once the cursor is empty; that is when the @channelSerial@ looks like @<sequence id>:@
4749
*** @(RTO5a5)@ An @OBJECT_SYNC@ may also be sent with no @channelSerial@ attribute. In this case, the sync data is entirely contained within the @ProtocolMessage@
48-
** @(RTO5b)@ During the sync sequence, the @ObjectMessage.object@ values from incoming @OBJECT_SYNC@ @ProtocolMessage@s must be temporarily stored in the internal @SyncObjectsPool@ list
50+
** @(RTO5b)@ During the sync sequence, the @ObjectMessage.object@ values from incoming @OBJECT_SYNC@ @ProtocolMessages@ must be temporarily stored in the internal @SyncObjectsPool@ list
4951
** @(RTO5c)@ When the objects sync has completed, the client library must perform the following actions in order:
5052
*** @(RTO5c1)@ For each @ObjectState@ member in the @SyncObjectsPool@ list:
5153
**** @(RTO5c1a)@ If an object with @ObjectState.objectId@ exists in the internal @ObjectsPool@:
@@ -57,14 +59,52 @@ h3(#realtime-objects). RealtimeObjects
5759
****** @(RTO5c1b1c)@ Otherwise, log a warning that an unsupported object state message has been received, and discard the current @ObjectState@ without taking any action
5860
*** @(RTO5c2)@ Remove any objects from the internal @ObjectsPool@ for which @objectId@s were not received during the sync sequence
5961
**** @(RTO5c2a)@ The object with ID @root@ must not be removed from @ObjectsPool@, as per "RTO3b":#RTO3b
62+
*** @(RTO5c6)@ @ObjectMessages@ stored in the @BufferedObjectOperations@ list are applied as described in "RTO9":#RTO9
6063
*** @(RTO5c3)@ Clear any stored sync sequence identifiers and cursor values
6164
*** @(RTO5c4)@ The @SyncObjectsPool@ must be cleared
65+
*** @(RTO5c5)@ The @BufferedObjectOperations@ list must be cleared
6266
* @(RTO6)@ When needed, a zero-value object can be created if it does not exist in the internal @ObjectsPool@ for an @objectId@, in the following way:
6367
** @(RTO6a)@ If an object with @objectId@ exists in @ObjectsPool@, do not create a new object
6468
** @(RTO6b)@ The expected type of the object can be inferred from the provided @objectId@:
6569
*** @(RTO6b1)@ Split the @objectId@ (formatted as @type:hash&#64;timestamp@) on the separator @:@ and parse the first part as the type string
6670
*** @(RTO6b2)@ If the parsed type is @map@, create a zero-value @LiveMap@ per "RTLM4":#RTLM4 in the @ObjectsPool@
6771
*** @(RTO6b3)@ If the parsed type is @counter@, create a zero-value @LiveCounter@ per "RTLC4":#RTLC4 in the @ObjectsPool@
72+
* @(RTO7)@ The client library may receive @OBJECT@ @ProtocolMessages@ in realtime over the channel concurrently with @OBJECT_SYNC@ @ProtocolMessages@ during the object sync sequence ("RTO5":#RTO5). Some of the incoming @OBJECT@ messages may have already been applied to the objects described in the sync sequence, while others may not. Therefore, the client must buffer @OBJECT@ messages during the sync sequence so that it can determine which of them should be applied to the objects once the sync is complete. See "RTO8":#RTO8
73+
** @(RTO7a)@ An internal @BufferedObjectOperations@ should be used to store the buffered @ObjectMessages@, as described in "RTO8a":#RTO8a. @BufferedObjectOperations@ is an array of @ObjectMessage@ instances
74+
*** @(RTO7a1)@ This array is empty upon @RealtimeObjects@ initialization
75+
* @(RTO8)@ When the library receives a @ProtocolMessage@ with an action of @OBJECT@, each member of the @ProtocolMessage.state@ array (decoded into @ObjectMessage@ objects) is passed to the @RealtimeObjects@ instance per "RTL1":../features#RTL1. Each @ObjectMessage@ from @OBJECT@ @ProtocolMessage@ (also referred to as an @OBJECT@ message) describes an operation to be applied to an object on a channel and must be handled as follows:
76+
** @(RTO8a)@ If an object sync sequence is currently in progress, add the @ObjectMessages@ to the internal @BufferedObjectOperations@ array
77+
** @(RTO8b)@ Otherwise, apply the @ObjectMessages@ as described in "RTO9":#RTO9
78+
* @(RTO9)@ @OBJECT@ messages can be applied to @RealtimeObjects@ in the following way:
79+
** @(RTO9a)@ For each @ObjectMessage@ in the provided list:
80+
*** @(RTO9a1)@ If @ObjectMessage.operation@ is null or omitted, log a warning indicating that an unsupported object operation message has been received, and discard the current @ObjectMessage@ without taking any action
81+
*** @(RTO9a2)@ The @ObjectMessage.operation.action@ field (see "@ObjectOperationAction@":../features#OOP2) determines the type of operation to apply:
82+
**** @(RTO9a2a)@ If @ObjectMessage.operation.action@ is one of the following: @MAP_CREATE@, @MAP_SET@, @MAP_REMOVE@, @COUNTER_CREATE@, @COUNTER_INC@, or @OBJECT_DELETE@, then:
83+
***** @(RTO9a2a1)@ If it does not already exist, create a zero-value @LiveObject@ in the internal @ObjectsPool@ per "RTO6":#RTO6 using the @objectId@ from @ObjectMessage.operation.objectId@
84+
***** @(RTO9a2a2)@ Get the @LiveObject@ instance from the internal @ObjectsPool@ using the @objectId@ from @ObjectMessage.operation.objectId@
85+
***** @(RTO9a2a3)@ Apply the @ObjectMessage.operation@ to the @LiveObject@; see "RTLC7":#RTLC7, "RTLM15":#RTLM15
86+
**** @(RTO9a2b)@ Otherwise, log a warning that an object operation message with an unsupported action has been received, and discard the current @ObjectMessage@ without taking any action
87+
88+
h3(#livecounter). LiveObject
89+
90+
* @(RTLO1)@ The @LiveObject@ represents the common interface and includes shared functionality for concrete object types
91+
* @(RTLO2)@ The client library may choose to implement @LiveObject@ as an abstract class
92+
* @(RTLO3)@ @LiveObject@ properties:
93+
** @(RTLO3a)@ protected @objectId@ string - an Object ID for this object
94+
*** @(RTLO3a1)@ Must be provided and set in the constructor
95+
** @(RTLO3b)@ protected @siteTimeserials@ @Dict<String, String>@ - a map of "serials":../features#OM2h keyed by "siteCode":../features#OM2i, representing the last operations applied to this object
96+
*** @(RTLO3b1)@ Set to an empty map when the @LiveObject@ is initialized, so that any future operation can be applied to this object
97+
** @(RTLO3c)@ protected @createOperationIsMerged@ boolean - a flag indicating whether the corresponding @MAP_CREATE@ or @COUNTER_CREATE@ operation has been applied to this @LiveObject@ instance
98+
*** @(RTLO3c1)@ Set to @false@ when the @LiveObject@ is initialized
99+
* @(RTLO4)@ @LiveObject@ methods:
100+
** @(RTLO4a)@ A convenience method @canApplyOperation@ - used to determine whether the @ObjectMessage.operation@ should be applied to this object based on a serial value:
101+
*** @(RTLO4a1)@ Expects the following arguments:
102+
**** @(RTLO4a1a)@ @ObjectMessage@
103+
*** @(RTLO4a2)@ Returns a boolean indicating whether the operation should be applied to this object
104+
*** @(RTLO4a3)@ Both @ObjectMessage.serial@ and @ObjectMessage.siteCode@ must be non-empty strings. Otherwise, log a warning that the object operation message has invalid serial values. The client library must not apply this operation to the object
105+
*** @(RTLO4a4)@ Get the @siteSerial@ value stored for this @LiveObject@ in the @siteTimeserials@ map using the key @ObjectMessage.siteCode@
106+
*** @(RTLO4a5)@ If the @siteSerial@ for this @LiveObject@ is null or an empty string, return true
107+
*** @(RTLO4a6)@ If the @siteSerial@ for this @LiveObject@ is not an empty string, return true if @ObjectMessage.serial@ is greater than @siteSerial@ when compared lexicographically
68108

69109
h3(#livecounter). LiveCounter
70110

@@ -80,9 +120,29 @@ h3(#livecounter). LiveCounter
80120
** @(RTLC6a)@ Replace the private @siteTimeserials@ of the @LiveCounter@ with the value from @ObjectState.siteTimeserials@
81121
** @(RTLC6b)@ Set the private flag @createOperationIsMerged@ to @false@
82122
** @(RTLC6c)@ Set @data@ to the value of @ObjectState.counter.count@, or to 0 if it does not exist
83-
** @(RTLC6d)@ If @ObjectState.createOp@ is present:
84-
*** @(RTLC6d1)@ Add @ObjectState.createOp.counter.count@ to @data@, if it exists
85-
*** @(RTLC6d2)@ Set the private flag @createOperationIsMerged@ to @true@
123+
** @(RTLC6d)@ If @ObjectState.createOp@ is present, merge the initial value into the @LiveCounter@ as described in "RTLC10":#RTLC10, passing in the @ObjectState.createOp@ instance
124+
*** @(RTLC6d1)@ This clause has been replaced by "RTLC10a":#RTLC10a
125+
*** @(RTLC6d2)@ This clause has been replaced by "RTLC10b":#RTLC10b
126+
* @(RTLC7)@ An @ObjectOperation@ from @ObjectMessage.operation@ can be applied to a @LiveCounter@ in the following way:
127+
** @(RTLC7a)@ A client library may choose to implement this logic as a convenience method named @applyOperation@, which accepts an @ObjectMessage@ instance with an existing @ObjectMessage.operation@ object, with @ObjectMessage.operation.objectId@ matching the Object ID of this @LiveCounter@. This @ObjectMessage@ represents the operation to be applied to this @LiveCounter@
128+
** @(RTLC7b)@ If @ObjectMessage.operation@ cannot be applied based on the result of "@LiveObject.canApplyOperation@":#RTLO4a, log a debug or trace message indicating that the operation cannot be applied because its serial value is not newer than the object's, and discard the @ObjectMessage@ without taking any action
129+
** @(RTLC7c)@ Set the entry in the private @siteTimeserials@ map at the key @ObjectMessage.siteCode@ to equal @ObjectMessage.serial@
130+
** @(RTLC7d)@ The @ObjectMessage.operation.action@ field (see "@ObjectOperationAction@":../features#OOP2) determines the type of operation to apply:
131+
*** @(RTLC7d1)@ If @ObjectMessage.operation.action@ is set to @COUNTER_CREATE@, apply the operation as described in "RTLC8":#RTLC8, passing in @ObjectMessage.operation@
132+
*** @(RTLC7d2)@ If @ObjectMessage.operation.action@ is set to @COUNTER_INC@, apply the operation as described in "RTLC9":#RTLC9, passing in @ObjectMessage.operation.counterOp@
133+
*** @(RTLC7d3)@ Otherwise, log a warning that an object operation message with an unsupported action has been received, and discard the current @ObjectMessage@ without taking any action
134+
* @(RTLC8)@ A @COUNTER_CREATE@ operation can be applied to a @LiveCounter@ in the following way:
135+
** @(RTLC8a)@ Expects the following arguments:
136+
*** @(RTLC8a1)@ @ObjectOperation@
137+
** @(RTLC8b)@ If the private flag @createOperationIsMerged@ is @true@, log a debug or trace message indicating that the operation will not be applied because a @COUNTER_CREATE@ operation has already been applied to this @LiveCounter@, and discard the operation without taking any action
138+
** @(RTLC8c)@ Merge the initial value into the @LiveCounter@ as described in "RTLC10":#RTLC10, passing in the @ObjectOperation@ instance
139+
* @(RTLC9)@ A @COUNTER_INC@ operation can be applied to a @LiveCounter@ in the following way:
140+
** @(RTLC9a)@ Expects the following arguments:
141+
*** @(RTLC9a1)@ @ObjectsCounterOp@
142+
** @(RTLC9b)@ Add @ObjectsCounterOp.amount@ to @data@, if it exists
143+
* @(RTLC10)@ The initial value from @ObjectOperation.counter@ can be merged into this @LiveCounter@ in the following way:
144+
** @(RTLC10a)@ Add @ObjectOperation.counter.count@ to @data@, if it exists
145+
** @(RTLC10b)@ Set the private flag @createOperationIsMerged@ to @true@
86146

87147
h3(#livemap). LiveMap
88148

@@ -131,12 +191,30 @@ h3(#livemap). LiveMap
131191
** @(RTLM6a)@ Replace the private @siteTimeserials@ of the @LiveMap@ with the value from @ObjectState.siteTimeserials@
132192
** @(RTLM6b)@ Set the private flag @createOperationIsMerged@ to @false@
133193
** @(RTLM6c)@ Set @data@ to @ObjectState.map.entries@, or to an empty map if it does not exist
134-
** @(RTLM6d)@ If @ObjectState.createOp@ is present:
135-
*** @(RTLM6d1)@ For each key–@ObjectsMapEntry@ pair in @ObjectState.createOp.map.entries@:
136-
**** @(RTLM6d1a)@ If @ObjectsMapEntry.tombstone@ is @false@, apply the @MAP_SET@ operation to the specified key using @ObjectsMapEntry.timeserial@ and @ObjectsMapEntry.data@ per "RTLM7":#RTLM7
137-
**** @(RTLM6d1b)@ If @ObjectsMapEntry.tombstone@ is @true@, apply the @MAP_REMOVE@ operation to the specified key using @ObjectsMapEntry.timeserial@ per "RTLM8":#RTLM8
138-
*** @(RTLM6d2)@ Set the private flag @createOperationIsMerged@ to @true@
139-
* @(RTLM7)@ @MAP_SET@ operation for a key can be applied to a @LiveMap@ in the following way:
194+
** @(RTLM6d)@ If @ObjectState.createOp@ is present, merge the initial value into the @LiveMap@ as described in "RTLM17":#RTLM17, passing in the @ObjectState.createOp@ instance
195+
*** @(RTLM6d1)@ This clause has been replaced by "RTLM17a":#RTLM17a
196+
**** @(RTLM6d1a)@ This clause has been replaced by "RTLM17a1":#RTLM17a1
197+
**** @(RTLM6d1b)@ This clause has been replaced by "RTLM17a2":#RTLM17a2
198+
*** @(RTLM6d2)@ This clause has been replaced by "RTLM17b":#RTLM17b
199+
* @(RTLM15)@ An @ObjectOperation@ from @ObjectMessage.operation@ can be applied to a @LiveMap@ in the following way:
200+
** @(RTLM15a)@ A client library may choose to implement this logic as a convenience method named @applyOperation@, which accepts an @ObjectMessage@ instance with an existing @ObjectMessage.operation@ object, with @ObjectMessage.operation.objectId@ matching the Object ID of this @LiveMap@. This @ObjectMessage@ represents the operation to be applied to this @LiveMap@
201+
** @(RTLM15b)@ If @ObjectMessage.operation@ cannot be applied based on the result of "@LiveObject.canApplyOperation@":#RTLO4a, log a debug or trace message indicating that the operation cannot be applied because its serial value is not newer than the object's, and discard the @ObjectMessage@ without taking any action
202+
** @(RTLM15c)@ Set the entry in the private @siteTimeserials@ map at the key @ObjectMessage.siteCode@ to equal @ObjectMessage.serial@
203+
** @(RTLM15d)@ The @ObjectMessage.operation.action@ field (see "@ObjectOperationAction@":../features#OOP2) determines the type of operation to apply:
204+
*** @(RTLM15d1)@ If @ObjectMessage.operation.action@ is set to @MAP_CREATE@, apply the operation as described in "RTLM16":#RTLM16, passing in @ObjectMessage.operation@
205+
*** @(RTLM15d2)@ If @ObjectMessage.operation.action@ is set to @MAP_SET@, apply the operation as described in "RTLM7":#RTLM7, passing in @ObjectMessage.operation.mapOp@ and @ObjectMessage.serial@
206+
*** @(RTLM15d3)@ If @ObjectMessage.operation.action@ is set to @MAP_REMOVE@, apply the operation as described in "RTLM8":#RTLM8, passing in @ObjectMessage.operation.mapOp@ and @ObjectMessage.serial@
207+
*** @(RTLM15d4)@ Otherwise, log a warning that an object operation message with an unsupported action has been received, and discard the current @ObjectMessage@ without taking any action
208+
* @(RTLM16)@ A @MAP_CREATE@ operation can be applied to a @LiveMap@ in the following way:
209+
** @(RTLM16a)@ Expects the following argument:
210+
*** @(RTLM16a1)@ @ObjectOperation@
211+
** @(RTLM16b)@ If the private flag @createOperationIsMerged@ is @true@, log a debug or trace message indicating that the operation will not be applied because a @MAP_CREATE@ operation has already been applied to this @LiveMap@, and discard the operation without taking any action
212+
** @(RTLM16c)@ If the private @semantics@ field does not match @ObjectOperation.map.semantics@, log a warning that the operation cannot be applied due to mismatched semantics, and discard the operation without taking any action
213+
** @(RTLM16d)@ Merge the initial value into the @LiveMap@ as described in "RTLM17":#RTLM17, passing in the @ObjectOperation@ instance
214+
* @(RTLM7)@ A @MAP_SET@ operation for a key can be applied to a @LiveMap@ in the following way:
215+
** @(RTLM7d)@ Expects the following arguments:
216+
*** @(RTLM7d1)@ @ObjectsMapOp@
217+
*** @(RTLM7d2)@ @serial@ string - operation's serial value
140218
** @(RTLM7a)@ If an entry exists in the private @data@ for the specified key:
141219
*** @(RTLM7a1)@ If the operation cannot be applied as per "RTLM9":#RTLM9, discard the operation without taking any action
142220
*** @(RTLM7a2)@ Otherwise, apply the operation:
@@ -149,6 +227,9 @@ h3(#livemap). LiveMap
149227
** @(RTLM7c)@ If the operation has a non-empty @ObjectData.objectId@ attribute:
150228
*** @(RTLM7c1)@ Create a zero-value @LiveObject@ in the internal @ObjectsPool@ per "RTO6":#RTO6
151229
* @(RTLM8)@ @MAP_REMOVE@ operation for a key can be applied to a @LiveMap@ in the following way:
230+
** @(RTLM8c)@ Expects the following arguments:
231+
*** @(RTLM8c1)@ @ObjectsMapOp@
232+
*** @(RTLM8c2)@ @serial@ string - operation's serial value
152233
** @(RTLM8a)@ If an entry exists in the private @data@ for the specified key:
153234
*** @(RTLM8a1)@ If the operation cannot be applied as per "RTLM9":#RTLM9, discard the operation without taking any action
154235
*** @(RTLM8a2)@ Otherwise, apply the operation:
@@ -164,3 +245,8 @@ h3(#livemap). LiveMap
164245
** @(RTLM9c)@ If only the entry serial exists, the missing operation serial is considered lower than the existing entry serial, so the operation must not be applied
165246
** @(RTLM9d)@ If only the operation serial exists, it is considered greater than the missing entry serial, so the operation can be applied
166247
** @(RTLM9e)@ If both serials exist, compare them lexicographically and allow operation to be applied only if the operation's serial is greater than the entry's serial
248+
* @(RTLM17)@ The initial value from @ObjectOperation.map@ can be merged into this @LiveMap@ in the following way:
249+
** @(RTLM17a)@ For each key–@ObjectsMapEntry@ pair in @ObjectOperation.map.entries@:
250+
*** @(RTLM17a1)@ If @ObjectsMapEntry.tombstone@ is @false@, apply the @MAP_SET@ operation as described in "RTLM7":#RTLM7, passing in @ObjectsMapEntry.data@ and the current key as @ObjectsMapOp@, and @ObjectsMapEntry.timeserial@ as the operation's serial
251+
*** @(RTLM17a2)@ If @ObjectsMapEntry.tombstone@ is @true@, apply the @MAP_REMOVE@ operation as described in "RTLM8":#RTLM8, passing in the current key as @ObjectsMapOp@, and @ObjectsMapEntry.timeserial@ as the operation's serial
252+
** @(RTLM17b)@ Set the private flag @createOperationIsMerged@ to @true@

0 commit comments

Comments
 (0)