Skip to content

Commit 1847ff5

Browse files
Move map and counter accessor methods into the mutable state
This gives us atomicity instead of breaking it up into multiple mutex acquisitions. Should have done this when I implemented these methods.
1 parent d935f96 commit 1847ff5

2 files changed

Lines changed: 119 additions & 112 deletions

File tree

Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -80,13 +80,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
8080
// MARK: - Internal methods that back LiveCounter conformance
8181

8282
internal func value(coreSDK: CoreSDK) throws(ARTErrorInfo) -> Double {
83-
// RTLC5b: If the channel is in the DETACHED or FAILED state, the library should indicate an error with code 90001
84-
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveCounter.value")
85-
86-
return mutex.withLock {
87-
// RTLC5c
88-
mutableState.data
89-
}
83+
try mutableState.value(coreSDK: coreSDK)
9084
}
9185

9286
internal func increment(amount: Double, coreSDK: CoreSDK) async throws(ARTErrorInfo) {
@@ -419,5 +413,13 @@ internal final class InternalDefaultLiveCounter: Sendable {
419413
// RTLC4
420414
data = 0
421415
}
416+
417+
internal func value(coreSDK: CoreSDK) throws(ARTErrorInfo) -> Double {
418+
// RTLC5b: If the channel is in the DETACHED or FAILED state, the library should indicate an error with code 90001
419+
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveCounter.value")
420+
421+
// RTLC5c
422+
return data
423+
}
422424
}
423425
}

Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift

Lines changed: 110 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -110,57 +110,15 @@ 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
128-
}
129-
130-
// RTLM5d2: If a ObjectsMapEntry exists at the key, convert it using the shared logic
131-
return convertEntryToLiveMapValue(entry, delegate: delegate)
113+
try mutableState.get(key: key, coreSDK: coreSDK, delegate: delegate)
132114
}
133115

134116
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-
}
143-
}
117+
try mutableState.size(coreSDK: coreSDK, delegate: delegate)
144118
}
145119

146120
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
163-
}
121+
try mutableState.entries(coreSDK: coreSDK, delegate: delegate)
164122
}
165123

166124
internal func keys(coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> [String] {
@@ -843,90 +801,137 @@ internal final class InternalDefaultLiveMap: Sendable {
843801
return !shouldRelease
844802
}
845803
}
846-
}
847804

848-
// MARK: - Helper Methods
805+
/// Returns the value associated with a given key, following RTLM5d specification.
806+
internal func get(key: String, coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> InternalLiveMapValue? {
807+
// RTLM5c: If the channel is in the DETACHED or FAILED state, the library should indicate an error with code 90001
808+
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveMap.get")
849809

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-
}
810+
// RTLM5e - Return nil if self is tombstone
811+
if liveObjectMutableState.isTombstone {
812+
return nil
813+
}
856814

857-
// RTLM14c
858-
if let objectId = entry.data?.objectId {
859-
if let poolEntry = delegate.getObjectFromPool(id: objectId), poolEntry.isTombstone {
860-
return true
815+
// RTLM5d1: If no ObjectsMapEntry exists at the key, return undefined/null
816+
guard let entry = data[key] else {
817+
return nil
861818
}
819+
820+
// RTLM5d2: If a ObjectsMapEntry exists at the key, convert it using the shared logic
821+
return convertEntryToLiveMapValue(entry, delegate: delegate)
862822
}
863823

864-
// RTLM14b
865-
return false
866-
}
824+
internal func size(coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> Int {
825+
// RTLM10c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
826+
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveMap.size")
867827

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
828+
// RTLM10d: Returns the number of non-tombstoned entries (per RTLM14) in the internal data map
829+
return data.values.count { entry in
830+
!Self.isEntryTombstoned(entry, delegate: delegate)
831+
}
874832
}
875833

876-
// Handle primitive values in the order specified by RTLM5d2b through RTLM5d2e
834+
internal func entries(coreSDK: CoreSDK, delegate: LiveMapObjectPoolDelegate) throws(ARTErrorInfo) -> [(key: String, value: InternalLiveMapValue)] {
835+
// RTLM11c: If the channel is in the DETACHED or FAILED state, the library should throw an ErrorInfo error with statusCode 400 and code 90001
836+
try coreSDK.validateChannelState(notIn: [.detached, .failed], operationDescription: "LiveMap.entries")
877837

878-
// RTLM5d2b: If ObjectsMapEntry.data.boolean exists, return it
879-
if let boolean = entry.data?.boolean {
880-
return .primitive(.bool(boolean))
881-
}
838+
// RTLM11d: Returns key-value pairs from the internal data map
839+
// RTLM11d1: Pairs with tombstoned entries (per RTLM14) are not returned
840+
var result: [(key: String, value: InternalLiveMapValue)] = []
882841

883-
// RTLM5d2c: If ObjectsMapEntry.data.bytes exists, return it
884-
if let bytes = entry.data?.bytes {
885-
return .primitive(.data(bytes))
886-
}
842+
for (key, entry) in data where !Self.isEntryTombstoned(entry, delegate: delegate) {
843+
// Convert entry to LiveMapValue using the same logic as get(key:)
844+
if let value = convertEntryToLiveMapValue(entry, delegate: delegate) {
845+
result.append((key: key, value: value))
846+
}
847+
}
887848

888-
// RTLM5d2d: If ObjectsMapEntry.data.number exists, return it
889-
if let number = entry.data?.number {
890-
return .primitive(.number(number.doubleValue))
849+
return result
891850
}
892851

893-
// RTLM5d2e: If ObjectsMapEntry.data.string exists, return it
894-
if let string = entry.data?.string {
895-
return .primitive(.string(string))
896-
}
852+
// MARK: - Helper Methods
897853

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))
854+
/// Returns whether a map entry should be considered tombstoned, per the check described in RTLM14.
855+
private static func isEntryTombstoned(_ entry: InternalObjectsMapEntry, delegate: LiveMapObjectPoolDelegate) -> Bool {
856+
// RTLM14a
857+
if entry.tombstone {
858+
return true
905859
}
860+
861+
// RTLM14c
862+
if let objectId = entry.data?.objectId {
863+
if let poolEntry = delegate.getObjectFromPool(id: objectId), poolEntry.isTombstone {
864+
return true
865+
}
866+
}
867+
868+
// RTLM14b
869+
return false
906870
}
907871

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 {
872+
/// Converts an InternalObjectsMapEntry to LiveMapValue using the same logic as get(key:)
873+
/// This is used by entries to ensure consistent value conversion
874+
private func convertEntryToLiveMapValue(_ entry: InternalObjectsMapEntry, delegate: LiveMapObjectPoolDelegate) -> InternalLiveMapValue? {
875+
// RTLM5d2a: If ObjectsMapEntry.tombstone is true, return undefined/null
876+
if entry.tombstone == true {
912877
return nil
913878
}
914879

915-
// RTLM5d2f3: If referenced object is tombstoned, return nil
916-
if poolEntry.isTombstone {
917-
return nil
880+
// Handle primitive values in the order specified by RTLM5d2b through RTLM5d2e
881+
882+
// RTLM5d2b: If ObjectsMapEntry.data.boolean exists, return it
883+
if let boolean = entry.data?.boolean {
884+
return .primitive(.bool(boolean))
918885
}
919886

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)
887+
// RTLM5d2c: If ObjectsMapEntry.data.bytes exists, return it
888+
if let bytes = entry.data?.bytes {
889+
return .primitive(.data(bytes))
890+
}
891+
892+
// RTLM5d2d: If ObjectsMapEntry.data.number exists, return it
893+
if let number = entry.data?.number {
894+
return .primitive(.number(number.doubleValue))
895+
}
896+
897+
// RTLM5d2e: If ObjectsMapEntry.data.string exists, return it
898+
if let string = entry.data?.string {
899+
return .primitive(.string(string))
900+
}
901+
902+
// TODO: Needs specification (see https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/46)
903+
if let json = entry.data?.json {
904+
switch json {
905+
case let .array(array):
906+
return .primitive(.jsonArray(array))
907+
case let .object(object):
908+
return .primitive(.jsonObject(object))
909+
}
926910
}
927-
}
928911

929-
// RTLM5d2g: Otherwise, return undefined/null
930-
return nil
912+
// RTLM5d2f: If ObjectsMapEntry.data.objectId exists, get the object stored at that objectId from the internal ObjectsPool
913+
if let objectId = entry.data?.objectId {
914+
// RTLM5d2f1: If an object with id objectId does not exist, return undefined/null
915+
guard let poolEntry = delegate.getObjectFromPool(id: objectId) else {
916+
return nil
917+
}
918+
919+
// RTLM5d2f3: If referenced object is tombstoned, return nil
920+
if poolEntry.isTombstone {
921+
return nil
922+
}
923+
924+
// RTLM5d2f2: Return referenced object
925+
switch poolEntry {
926+
case let .map(map):
927+
return .liveMap(map)
928+
case let .counter(counter):
929+
return .liveCounter(counter)
930+
}
931+
}
932+
933+
// RTLM5d2g: Otherwise, return undefined/null
934+
return nil
935+
}
931936
}
932937
}

0 commit comments

Comments
 (0)