@@ -110,56 +110,20 @@ internal final class InternalDefaultLiveMap: Sendable {
110110
111111 /// Returns the value associated with a given key, following RTLM5d specification.
112112 internal func get( key: String , coreSDK: CoreSDK , delegate: LiveMapObjectPoolDelegate ) throws ( ARTErrorInfo) -> InternalLiveMapValue ? {
113- // RTLM5c: If the channel is in the DETACHED or FAILED state, the library should indicate an error with code 90001
114- try coreSDK. validateChannelState ( notIn: [ . detached, . failed] , operationDescription: " LiveMap.get " )
115-
116- // RTLM5e - Return nil if self is tombstone
117- if isTombstone {
118- return nil
119- }
120-
121- let entry = mutex. withLock {
122- mutableState. data [ key]
123- }
124-
125- // RTLM5d1: If no ObjectsMapEntry exists at the key, return undefined/null
126- guard let entry else {
127- return nil
113+ try mutex. ablyLiveObjects_withLockWithTypedThrow { ( ) throws ( ARTErrorInfo) in
114+ try mutableState. get ( key: key, coreSDK: coreSDK, delegate: delegate)
128115 }
129-
130- // RTLM5d2: If a ObjectsMapEntry exists at the key, convert it using the shared logic
131- return convertEntryToLiveMapValue ( entry, delegate: delegate)
132116 }
133117
134118 internal func size( coreSDK: CoreSDK , delegate: LiveMapObjectPoolDelegate ) throws ( ARTErrorInfo) -> Int {
135- // RTLM10c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
136- try coreSDK. validateChannelState ( notIn: [ . detached, . failed] , operationDescription: " LiveMap.size " )
137-
138- return mutex. withLock {
139- // RTLM10d: Returns the number of non-tombstoned entries (per RTLM14) in the internal data map
140- mutableState. data. values. count { entry in
141- !Self. isEntryTombstoned ( entry, delegate: delegate)
142- }
119+ try mutex. ablyLiveObjects_withLockWithTypedThrow { ( ) throws ( ARTErrorInfo) in
120+ try mutableState. size ( coreSDK: coreSDK, delegate: delegate)
143121 }
144122 }
145123
146124 internal func entries( coreSDK: CoreSDK , delegate: LiveMapObjectPoolDelegate ) throws ( ARTErrorInfo) -> [ ( key: String , value: InternalLiveMapValue ) ] {
147- // RTLM11c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
148- try coreSDK. validateChannelState ( notIn: [ . detached, . failed] , operationDescription: " LiveMap.entries " )
149-
150- return mutex. withLock {
151- // RTLM11d: Returns key-value pairs from the internal data map
152- // RTLM11d1: Pairs with tombstoned entries (per RTLM14) are not returned
153- var result : [ ( key: String , value: InternalLiveMapValue ) ] = [ ]
154-
155- for (key, entry) in mutableState. data where !Self. isEntryTombstoned ( entry, delegate: delegate) {
156- // Convert entry to LiveMapValue using the same logic as get(key:)
157- if let value = convertEntryToLiveMapValue ( entry, delegate: delegate) {
158- result. append ( ( key: key, value: value) )
159- }
160- }
161-
162- return result
125+ try mutex. ablyLiveObjects_withLockWithTypedThrow { ( ) throws ( ARTErrorInfo) in
126+ try mutableState. entries ( coreSDK: coreSDK, delegate: delegate)
163127 }
164128 }
165129
@@ -655,7 +619,7 @@ internal final class InternalDefaultLiveMap: Sendable {
655619 internal mutating func applyMapSetOperation(
656620 key: String ,
657621 operationTimeserial: String ? ,
658- operationData: ObjectData ,
622+ operationData: ObjectData ? ,
659623 objectsPool: inout ObjectsPool ,
660624 logger: AblyPlugin . Logger ,
661625 userCallbackQueue: DispatchQueue ,
@@ -684,7 +648,7 @@ internal final class InternalDefaultLiveMap: Sendable {
684648 }
685649
686650 // RTLM7c: If the operation has a non-empty ObjectData.objectId attribute
687- if let objectId = operationData. objectId, !objectId. isEmpty {
651+ if let objectId = operationData? . objectId, !objectId. isEmpty {
688652 // RTLM7c1: Create a zero-value LiveObject in the internal ObjectsPool per RTO6
689653 _ = objectsPool. createZeroValueObject ( forObjectID: objectId, logger: logger, userCallbackQueue: userCallbackQueue, clock: clock)
690654 }
@@ -721,7 +685,7 @@ internal final class InternalDefaultLiveMap: Sendable {
721685 // RTLM8a2c: Set ObjectsMapEntry.tombstone to true (equivalent to next point)
722686 // RTLM8a2d: Set ObjectsMapEntry.tombstonedAt per RTLM8a2d
723687 var updatedEntry = existingEntry
724- updatedEntry. data = ObjectData ( )
688+ updatedEntry. data = nil
725689 updatedEntry. timeserial = operationTimeserial
726690 updatedEntry. tombstonedAt = tombstonedAt
727691 data [ key] = updatedEntry
@@ -730,7 +694,7 @@ internal final class InternalDefaultLiveMap: Sendable {
730694 // RTLM8b1: Create a new entry in data for the specified key, with ObjectsMapEntry.data set to undefined/null and the operation's serial
731695 // RTLM8b2: Set ObjectsMapEntry.tombstone for the new entry to true
732696 // RTLM8b3: Set ObjectsMapEntry.tombstonedAt per RTLM8f
733- data [ key] = InternalObjectsMapEntry ( tombstonedAt: tombstonedAt, timeserial: operationTimeserial, data: ObjectData ( ) )
697+ data [ key] = InternalObjectsMapEntry ( tombstonedAt: tombstonedAt, timeserial: operationTimeserial, data: nil )
734698 }
735699
736700 return . update( DefaultLiveMapUpdate ( update: [ key: . removed] ) )
@@ -843,90 +807,137 @@ internal final class InternalDefaultLiveMap: Sendable {
843807 return !shouldRelease
844808 }
845809 }
846- }
847810
848- // MARK: - Helper Methods
811+ /// Returns the value associated with a given key, following RTLM5d specification.
812+ internal func get( key: String , coreSDK: CoreSDK , delegate: LiveMapObjectPoolDelegate ) throws ( ARTErrorInfo) -> InternalLiveMapValue ? {
813+ // RTLM5c: If the channel is in the DETACHED or FAILED state, the library should indicate an error with code 90001
814+ try coreSDK. validateChannelState ( notIn: [ . detached, . failed] , operationDescription: " LiveMap.get " )
849815
850- /// Returns whether a map entry should be considered tombstoned, per the check described in RTLM14.
851- private static func isEntryTombstoned( _ entry: InternalObjectsMapEntry , delegate: LiveMapObjectPoolDelegate ) -> Bool {
852- // RTLM14a
853- if entry. tombstone {
854- return true
855- }
816+ // RTLM5e - Return nil if self is tombstone
817+ if liveObjectMutableState. isTombstone {
818+ return nil
819+ }
856820
857- // RTLM14c
858- if let objectId = entry. data. objectId {
859- if let poolEntry = delegate. getObjectFromPool ( id: objectId) , poolEntry. isTombstone {
860- return true
821+ // RTLM5d1: If no ObjectsMapEntry exists at the key, return undefined/null
822+ guard let entry = data [ key] else {
823+ return nil
861824 }
825+
826+ // RTLM5d2: If a ObjectsMapEntry exists at the key, convert it using the shared logic
827+ return convertEntryToLiveMapValue ( entry, delegate: delegate)
862828 }
863829
864- // RTLM14b
865- return false
866- }
830+ internal func size ( coreSDK : CoreSDK , delegate : LiveMapObjectPoolDelegate ) throws ( ARTErrorInfo ) -> Int {
831+ // RTLM10c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
832+ try coreSDK . validateChannelState ( notIn : [ . detached , . failed ] , operationDescription : " LiveMap.size " )
867833
868- /// Converts an InternalObjectsMapEntry to LiveMapValue using the same logic as get(key:)
869- /// This is used by entries to ensure consistent value conversion
870- private func convertEntryToLiveMapValue( _ entry: InternalObjectsMapEntry , delegate: LiveMapObjectPoolDelegate ) -> InternalLiveMapValue ? {
871- // RTLM5d2a: If ObjectsMapEntry.tombstone is true, return undefined/null
872- if entry. tombstone == true {
873- return nil
834+ // RTLM10d: Returns the number of non-tombstoned entries (per RTLM14) in the internal data map
835+ return data. values. count { entry in
836+ !Self. isEntryTombstoned ( entry, delegate: delegate)
837+ }
874838 }
875839
876- // Handle primitive values in the order specified by RTLM5d2b through RTLM5d2e
840+ internal func entries( coreSDK: CoreSDK , delegate: LiveMapObjectPoolDelegate ) throws ( ARTErrorInfo) -> [ ( key: String , value: InternalLiveMapValue ) ] {
841+ // RTLM11c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
842+ try coreSDK. validateChannelState ( notIn: [ . detached, . failed] , operationDescription: " LiveMap.entries " )
877843
878- // RTLM5d2b: If ObjectsMapEntry.data.boolean exists, return it
879- if let boolean = entry. data. boolean {
880- return . primitive( . bool( boolean) )
881- }
844+ // RTLM11d: Returns key-value pairs from the internal data map
845+ // RTLM11d1: Pairs with tombstoned entries (per RTLM14) are not returned
846+ var result : [ ( key: String , value: InternalLiveMapValue ) ] = [ ]
882847
883- // RTLM5d2c: If ObjectsMapEntry.data.bytes exists, return it
884- if let bytes = entry. data. bytes {
885- return . primitive( . data( bytes) )
886- }
848+ for (key, entry) in data where !Self. isEntryTombstoned ( entry, delegate: delegate) {
849+ // Convert entry to LiveMapValue using the same logic as get(key:)
850+ if let value = convertEntryToLiveMapValue ( entry, delegate: delegate) {
851+ result. append ( ( key: key, value: value) )
852+ }
853+ }
887854
888- // RTLM5d2d: If ObjectsMapEntry.data.number exists, return it
889- if let number = entry. data. number {
890- return . primitive( . number( number. doubleValue) )
855+ return result
891856 }
892857
893- // RTLM5d2e: If ObjectsMapEntry.data.string exists, return it
894- if let string = entry. data. string {
895- return . primitive( . string( string) )
896- }
858+ // MARK: - Helper Methods
897859
898- // TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
899- if let json = entry. data. json {
900- switch json {
901- case let . array( array) :
902- return . primitive( . jsonArray( array) )
903- case let . object( object) :
904- return . primitive( . jsonObject( object) )
860+ /// Returns whether a map entry should be considered tombstoned, per the check described in RTLM14.
861+ private static func isEntryTombstoned( _ entry: InternalObjectsMapEntry , delegate: LiveMapObjectPoolDelegate ) -> Bool {
862+ // RTLM14a
863+ if entry. tombstone {
864+ return true
905865 }
866+
867+ // RTLM14c
868+ if let objectId = entry. data? . objectId {
869+ if let poolEntry = delegate. getObjectFromPool ( id: objectId) , poolEntry. isTombstone {
870+ return true
871+ }
872+ }
873+
874+ // RTLM14b
875+ return false
906876 }
907877
908- // RTLM5d2f: If ObjectsMapEntry.data.objectId exists, get the object stored at that objectId from the internal ObjectsPool
909- if let objectId = entry. data. objectId {
910- // RTLM5d2f1: If an object with id objectId does not exist, return undefined/null
911- guard let poolEntry = delegate. getObjectFromPool ( id: objectId) else {
878+ /// Converts an InternalObjectsMapEntry to LiveMapValue using the same logic as get(key:)
879+ /// This is used by entries to ensure consistent value conversion
880+ private func convertEntryToLiveMapValue( _ entry: InternalObjectsMapEntry , delegate: LiveMapObjectPoolDelegate ) -> InternalLiveMapValue ? {
881+ // RTLM5d2a: If ObjectsMapEntry.tombstone is true, return undefined/null
882+ if entry. tombstone == true {
912883 return nil
913884 }
914885
915- // RTLM5d2f3: If referenced object is tombstoned, return nil
916- if poolEntry. isTombstone {
917- return nil
886+ // Handle primitive values in the order specified by RTLM5d2b through RTLM5d2e
887+
888+ // RTLM5d2b: If ObjectsMapEntry.data.boolean exists, return it
889+ if let boolean = entry. data? . boolean {
890+ return . primitive( . bool( boolean) )
918891 }
919892
920- // RTLM5d2f2: Return referenced object
921- switch poolEntry {
922- case let . map( map) :
923- return . liveMap( map)
924- case let . counter( counter) :
925- return . liveCounter( counter)
893+ // RTLM5d2c: If ObjectsMapEntry.data.bytes exists, return it
894+ if let bytes = entry. data? . bytes {
895+ return . primitive( . data( bytes) )
896+ }
897+
898+ // RTLM5d2d: If ObjectsMapEntry.data.number exists, return it
899+ if let number = entry. data? . number {
900+ return . primitive( . number( number. doubleValue) )
901+ }
902+
903+ // RTLM5d2e: If ObjectsMapEntry.data.string exists, return it
904+ if let string = entry. data? . string {
905+ return . primitive( . string( string) )
906+ }
907+
908+ // TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
909+ if let json = entry. data? . json {
910+ switch json {
911+ case let . array( array) :
912+ return . primitive( . jsonArray( array) )
913+ case let . object( object) :
914+ return . primitive( . jsonObject( object) )
915+ }
926916 }
927- }
928917
929- // RTLM5d2g: Otherwise, return undefined/null
930- return nil
918+ // RTLM5d2f: If ObjectsMapEntry.data.objectId exists, get the object stored at that objectId from the internal ObjectsPool
919+ if let objectId = entry. data? . objectId {
920+ // RTLM5d2f1: If an object with id objectId does not exist, return undefined/null
921+ guard let poolEntry = delegate. getObjectFromPool ( id: objectId) else {
922+ return nil
923+ }
924+
925+ // RTLM5d2f3: If referenced object is tombstoned, return nil
926+ if poolEntry. isTombstone {
927+ return nil
928+ }
929+
930+ // RTLM5d2f2: Return referenced object
931+ switch poolEntry {
932+ case let . map( map) :
933+ return . liveMap( map)
934+ case let . counter( counter) :
935+ return . liveCounter( counter)
936+ }
937+ }
938+
939+ // RTLM5d2g: Otherwise, return undefined/null
940+ return nil
941+ }
931942 }
932943}
0 commit comments