Skip to content

Commit ffc512f

Browse files
Bump protocol version to 6
Implements the ObjectOperation field changes specified in spec commit 47a9d51. Integration test updates ported from JS commit fdc33cb (excluding the public API changes, since we don't yet expose any sort of ObjectOperation, nor does the spec). Note that this is the first time that we've had to bump the protocol version in a way that affects LiveObjects, and here we're following the process documented in the pinned plugin-support commit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent cbb49fd commit ffc512f

18 files changed

+1028
-663
lines changed

AblyLiveObjects.xcworkspace/xcshareddata/swiftpm/Package.resolved

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.resolved

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ let package = Package(
2020
dependencies: [
2121
// TODO: Unpin before release
2222
.package(
23-
url: "https://github.com/ably/ably-cocoa.git",
24-
revision: "dbdd4db5c0c64f4330e200ff2ca9bc9528598ff3",
23+
url: "https://github.com/ably/ably-cocoa",
24+
revision: "154b6cd",
2525
),
2626
// TODO: Unpin before release
2727
.package(
2828
url: "https://github.com/ably/ably-cocoa-plugin-support",
29-
revision: "242fac1d4a829c8a63f9b3f96a71809e1f6eeffc",
29+
revision: "b0ac0e1684b9e5a2c3fb38bcfa68b49f7767b6b4",
3030
),
3131
.package(
3232
url: "https://github.com/apple/swift-argument-parser",

Sources/AblyLiveObjects/Internal/DefaultInternalPlugin.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ import ObjectiveC.NSObject
99
internal final class DefaultInternalPlugin: NSObject, _AblyPluginSupportPrivate.LiveObjectsInternalPluginProtocol {
1010
private let pluginAPI: _AblyPluginSupportPrivate.PluginAPIProtocol
1111

12+
internal var compatibleWithProtocolV6: Bool { true }
13+
1214
internal init(pluginAPI: _AblyPluginSupportPrivate.PluginAPIProtocol) {
15+
precondition(
16+
pluginAPI.usesLiveObjectsProtocolV6,
17+
"This version of the LiveObjects plugin requires a version of ably-cocoa that uses LiveObjects protocol v6.",
18+
)
1319
self.pluginAPI = pluginAPI
1420
}
1521

Sources/AblyLiveObjects/Internal/InternalDefaultLiveCounter.swift

Lines changed: 23 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ internal final class InternalDefaultLiveCounter: Sendable {
124124
action: .known(.counterInc),
125125
// RTLC12e3
126126
objectId: mutableState.liveObjectMutableState.objectID,
127-
counterOp: .init(
128-
// RTLC12e4
129-
amount: .init(value: amount),
127+
counterInc: .init(
128+
// RTLC12e5
129+
number: .init(value: amount),
130130
),
131131
),
132132
)
@@ -223,7 +223,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
223223
}
224224
}
225225

226-
/// Merges the initial value from an ObjectOperation into this LiveCounter, per RTLC10.
226+
/// Merges the initial value from an ObjectOperation into this LiveCounter, per RTLC16.
227227
internal func nosync_mergeInitialValue(from operation: ObjectOperation) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
228228
mutableStateMutex.withoutSync { mutableState in
229229
mutableState.mergeInitialValue(from: operation)
@@ -238,7 +238,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
238238
}
239239

240240
/// Test-only method to apply a COUNTER_INC operation, per RTLC9.
241-
internal func testsOnly_applyCounterIncOperation(_ operation: WireObjectsCounterOp?) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
241+
internal func testsOnly_applyCounterIncOperation(_ operation: CounterInc?) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
242242
mutableStateMutex.withSync { mutableState in
243243
mutableState.applyCounterIncOperation(operation)
244244
}
@@ -352,7 +352,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
352352
// RTLC6c: Set data to the value of ObjectState.counter.count, or to 0 if it does not exist
353353
data = state.counter?.count?.doubleValue ?? 0
354354

355-
// RTLC6d: If ObjectState.createOp is present, merge the initial value into the LiveCounter as described in RTLC10
355+
// RTLC6d: If ObjectState.createOp is present, merge the initial value into the LiveCounter as described in RTLC16
356356
// Discard the LiveCounterUpdate object returned by the merge operation
357357
if let createOp = state.createOp {
358358
_ = mergeInitialValue(from: createOp)
@@ -362,21 +362,25 @@ internal final class InternalDefaultLiveCounter: Sendable {
362362
return ObjectDiffHelpers.calculateCounterDiff(previousData: previousData, newData: data)
363363
}
364364

365-
/// Merges the initial value from an ObjectOperation into this LiveCounter, per RTLC10.
365+
/// Merges the initial value from an ObjectOperation into this LiveCounter, per RTLC16.
366366
internal mutating func mergeInitialValue(from operation: ObjectOperation) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
367367
let update: LiveObjectUpdate<DefaultLiveCounterUpdate>
368368

369-
// RTLC10a: Add ObjectOperation.counter.count to data, if it exists
370-
if let operationCount = operation.counter?.count?.doubleValue {
369+
// RTLC16: Resolve counterCreate from either the direct property or the one
370+
// from which counterCreateWithObjectId was derived (RTO12f16)
371+
let counterCreate = operation.counterCreate ?? operation.counterCreateWithObjectId?.derivedFrom
372+
373+
// RTLC16a: Add counterCreate.count to data, if it exists
374+
if let operationCount = counterCreate?.count?.doubleValue {
371375
data += operationCount
372-
// RTLC10c
376+
// RTLC16c
373377
update = .update(DefaultLiveCounterUpdate(amount: operationCount))
374378
} else {
375-
// RTLC10d
379+
// RTLC16d
376380
update = .noop
377381
}
378382

379-
// RTLC10b: Set the private flag createOperationIsMerged to true
383+
// RTLC16b: Set the private flag createOperationIsMerged to true
380384
liveObjectMutableState.createOperationIsMerged = true
381385

382386
return update
@@ -425,11 +429,11 @@ internal final class InternalDefaultLiveCounter: Sendable {
425429
// RTLC7d1b
426430
return true
427431
case .known(.counterInc):
428-
// RTLC7d2
429-
let update = applyCounterIncOperation(operation.counterOp)
430-
// RTLC7d2a
432+
// RTLC7d5
433+
let update = applyCounterIncOperation(operation.counterInc)
434+
// RTLC7d5a
431435
liveObjectMutableState.emit(update, on: userCallbackQueue)
432-
// RTLC7d2b
436+
// RTLC7d5b
433437
return true
434438
case .known(.objectDelete):
435439
let dataBeforeApplyingOperation = data
@@ -469,14 +473,14 @@ internal final class InternalDefaultLiveCounter: Sendable {
469473
}
470474

471475
/// Applies a `COUNTER_INC` operation, per RTLC9.
472-
internal mutating func applyCounterIncOperation(_ operation: WireObjectsCounterOp?) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
476+
internal mutating func applyCounterIncOperation(_ operation: CounterInc?) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
473477
guard let operation else {
474478
// RTL9e
475479
return .noop
476480
}
477481

478-
// RTLC9b, RTLC9d
479-
let amount = operation.amount.doubleValue
482+
// RTLC9f, RTLC9g
483+
let amount = operation.number.doubleValue
480484
data += amount
481485
return .update(DefaultLiveCounterUpdate(amount: amount))
482486
}

Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,11 @@ internal final class InternalDefaultLiveMap: Sendable {
172172
action: .known(.mapSet),
173173
// RTLM20e3
174174
objectId: mutableState.liveObjectMutableState.objectID,
175-
mapOp: .init(
176-
// RTLM20e4
175+
mapSet: .init(
176+
// RTLM20e6
177177
key: key,
178-
// RTLM20e5
179-
data: value.nosync_toObjectData,
178+
// RTLM20e7
179+
value: value.nosync_toObjectData,
180180
),
181181
),
182182
)
@@ -205,8 +205,8 @@ internal final class InternalDefaultLiveMap: Sendable {
205205
action: .known(.mapRemove),
206206
// RTLM21e3
207207
objectId: mutableState.liveObjectMutableState.objectID,
208-
mapOp: .init(
209-
// RTLM21e4
208+
mapRemove: .init(
209+
// RTLM21e5
210210
key: key,
211211
),
212212
),
@@ -303,7 +303,7 @@ internal final class InternalDefaultLiveMap: Sendable {
303303
}
304304
}
305305

306-
/// Merges the initial value from an ObjectOperation into this LiveMap, per RTLM17.
306+
/// Merges the initial value from an ObjectOperation into this LiveMap, per RTLM23.
307307
internal func nosync_mergeInitialValue(from operation: ObjectOperation, objectsPool: inout ObjectsPool) -> LiveObjectUpdate<DefaultLiveMapUpdate> {
308308
mutableStateMutex.withoutSync { mutableState in
309309
mutableState.mergeInitialValue(
@@ -516,7 +516,7 @@ internal final class InternalDefaultLiveMap: Sendable {
516516
return .init(objectsMapEntry: entry, tombstonedAt: tombstonedAt)
517517
} ?? [:]
518518

519-
// RTLM6d: If ObjectState.createOp is present, merge the initial value into the LiveMap as described in RTLM17
519+
// RTLM6d: If ObjectState.createOp is present, merge the initial value into the LiveMap as described in RTLM23
520520
// Discard the LiveMapUpdate object returned by the merge operation
521521
if let createOp = state.createOp {
522522
_ = mergeInitialValue(
@@ -533,7 +533,7 @@ internal final class InternalDefaultLiveMap: Sendable {
533533
return ObjectDiffHelpers.calculateMapDiff(previousData: previousData, newData: data)
534534
}
535535

536-
/// Merges the initial value from an ObjectOperation into this LiveMap, per RTLM17.
536+
/// Merges the initial value from an ObjectOperation into this LiveMap, per RTLM23.
537537
internal mutating func mergeInitialValue(
538538
from operation: ObjectOperation,
539539
objectsPool: inout ObjectsPool,
@@ -542,11 +542,15 @@ internal final class InternalDefaultLiveMap: Sendable {
542542
userCallbackQueue: DispatchQueue,
543543
clock: SimpleClock,
544544
) -> LiveObjectUpdate<DefaultLiveMapUpdate> {
545-
// RTLM17a: For each key–ObjectsMapEntry pair in ObjectOperation.map.entries
546-
let perKeyUpdates: [LiveObjectUpdate<DefaultLiveMapUpdate>] = if let entries = operation.map?.entries {
545+
// RTLM23: Resolve mapCreate from either the direct property or the one
546+
// from which mapCreateWithObjectId was derived (RTO11f18)
547+
let mapCreate = operation.mapCreate ?? operation.mapCreateWithObjectId?.derivedFrom
548+
549+
// RTLM23a: For each key–ObjectsMapEntry pair in mapCreate.entries
550+
let perKeyUpdates: [LiveObjectUpdate<DefaultLiveMapUpdate>] = if let entries = mapCreate?.entries {
547551
entries.map { key, entry in
548552
if entry.tombstone == true {
549-
// RTLM17a2: If ObjectsMapEntry.tombstone is true, apply the MAP_REMOVE operation
553+
// RTLM23a2: If ObjectsMapEntry.tombstone is true, apply the MAP_REMOVE operation
550554
// as described in RTLM8, passing in the current key as ObjectsMapOp, ObjectsMapEntry.timeserial as the operation's serial, and ObjectsMapEntry.serialTimestamp as the operation's serial timestamp
551555
applyMapRemoveOperation(
552556
key: key,
@@ -556,7 +560,7 @@ internal final class InternalDefaultLiveMap: Sendable {
556560
clock: clock,
557561
)
558562
} else {
559-
// RTLM17a1: If ObjectsMapEntry.tombstone is false, apply the MAP_SET operation
563+
// RTLM23a1: If ObjectsMapEntry.tombstone is false, apply the MAP_SET operation
560564
// as described in RTLM7, passing in ObjectsMapEntry.data and the current key as ObjectsMapOp, and ObjectsMapEntry.timeserial as the operation's serial
561565
applyMapSetOperation(
562566
key: key,
@@ -574,10 +578,10 @@ internal final class InternalDefaultLiveMap: Sendable {
574578
[]
575579
}
576580

577-
// RTLM17b: Set the private flag createOperationIsMerged to true
581+
// RTLM23b: Set the private flag createOperationIsMerged to true
578582
liveObjectMutableState.createOperationIsMerged = true
579583

580-
// RTLM17c: Merge the updates, skipping no-ops
584+
// RTLM23c: Merge the updates, skipping no-ops
581585
// I don't love having to use uniqueKeysWithValues, when I shouldn't have to. I should be able to reason _statically_ that there are no overlapping keys. The problem that we're trying to use LiveMapUpdate throughout instead of something more communicative. But I don't know what's to come in the spec so I don't want to mess with this internal interface.
582586
let filteredPerKeyUpdates = perKeyUpdates.compactMap { update -> LiveMapUpdate? in
583587
switch update {
@@ -642,46 +646,46 @@ internal final class InternalDefaultLiveMap: Sendable {
642646
// RTLM15d1b
643647
return true
644648
case .known(.mapSet):
645-
guard let mapOp = operation.mapOp else {
646-
logger.log("Could not apply MAP_SET since operation.mapOp is missing", level: .warn)
649+
guard let mapSet = operation.mapSet else {
650+
logger.log("Could not apply MAP_SET since operation.mapSet is missing", level: .warn)
647651
return false
648652
}
649-
guard let data = mapOp.data else {
650-
logger.log("Could not apply MAP_SET since operation.data is missing", level: .warn)
653+
guard let value = mapSet.value else {
654+
logger.log("Could not apply MAP_SET since operation.mapSet.value is missing", level: .warn)
651655
return false
652656
}
653657

654-
// RTLM15d2
658+
// RTLM15d6
655659
let update = applyMapSetOperation(
656-
key: mapOp.key,
660+
key: mapSet.key,
657661
operationTimeserial: applicableOperation.objectMessageSerial,
658-
operationData: data,
662+
operationData: value,
659663
objectsPool: &objectsPool,
660664
logger: logger,
661665
internalQueue: internalQueue,
662666
userCallbackQueue: userCallbackQueue,
663667
clock: clock,
664668
)
665-
// RTLM15d2a
669+
// RTLM15d6a
666670
liveObjectMutableState.emit(update, on: userCallbackQueue)
667-
// RTLM15d2b
671+
// RTLM15d6b
668672
return true
669673
case .known(.mapRemove):
670-
guard let mapOp = operation.mapOp else {
674+
guard let mapRemove = operation.mapRemove else {
671675
return false
672676
}
673677

674-
// RTLM15d3
678+
// RTLM15d7
675679
let update = applyMapRemoveOperation(
676-
key: mapOp.key,
680+
key: mapRemove.key,
677681
operationTimeserial: applicableOperation.objectMessageSerial,
678682
operationSerialTimestamp: objectMessageSerialTimestamp,
679683
logger: logger,
680684
clock: clock,
681685
)
682-
// RTLM15d3a
686+
// RTLM15d7a
683687
liveObjectMutableState.emit(update, on: userCallbackQueue)
684-
// RTLM15d3b
688+
// RTLM15d7b
685689
return true
686690
case .known(.objectDelete):
687691
let dataBeforeApplyingOperation = data

0 commit comments

Comments
 (0)