Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ let package = Package(
),
],
dependencies: [
// TODO: Unpin before release
.package(
url: "https://github.com/ably/ably-cocoa",
from: "1.2.55",
revision: "e9cd9e1",
),
// TODO: Unpin before release
.package(
url: "https://github.com/ably/ably-cocoa-plugin-support",
from: "1.0.0",
revision: "e015e70",
),
.package(
url: "https://github.com/apple/swift-argument-parser",
Expand Down
6 changes: 6 additions & 0 deletions Sources/AblyLiveObjects/Internal/DefaultInternalPlugin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,13 @@ import ObjectiveC.NSObject
internal final class DefaultInternalPlugin: NSObject, _AblyPluginSupportPrivate.LiveObjectsInternalPluginProtocol {
private let pluginAPI: _AblyPluginSupportPrivate.PluginAPIProtocol

internal var compatibleWithProtocolV6: Bool { true }

internal init(pluginAPI: _AblyPluginSupportPrivate.PluginAPIProtocol) {
precondition(
pluginAPI.usesLiveObjectsProtocolV6,
"This version of the LiveObjects plugin requires a version of ably-cocoa that uses LiveObjects protocol v6.",
)
self.pluginAPI = pluginAPI
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ internal final class InternalDefaultLiveCounter: Sendable {
action: .known(.counterInc),
// RTLC12e3
objectId: mutableState.liveObjectMutableState.objectID,
counterOp: .init(
counterInc: .init(
// RTLC12e4
amount: .init(value: amount),
number: .init(value: amount),
),
),
)
Expand Down Expand Up @@ -230,7 +230,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
}

/// Test-only method to apply a COUNTER_INC operation, per RTLC9.
internal func testsOnly_applyCounterIncOperation(_ operation: WireObjectsCounterOp?) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
internal func testsOnly_applyCounterIncOperation(_ operation: CounterInc?) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
mutableStateMutex.withSync { mutableState in
mutableState.applyCounterIncOperation(operation)
}
Expand Down Expand Up @@ -354,8 +354,8 @@ internal final class InternalDefaultLiveCounter: Sendable {
internal mutating func mergeInitialValue(from operation: ObjectOperation) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
let update: LiveObjectUpdate<DefaultLiveCounterUpdate>

// RTLC10a: Add ObjectOperation.counter.count to data, if it exists
if let operationCount = operation.counter?.count?.doubleValue {
// RTLC10a: Add ObjectOperation.counterCreate.count to data, if it exists
if let operationCount = operation.counterCreate?.count?.doubleValue {
data += operationCount
// RTLC10c
update = .update(DefaultLiveCounterUpdate(amount: operationCount))
Expand Down Expand Up @@ -407,7 +407,7 @@ internal final class InternalDefaultLiveCounter: Sendable {
liveObjectMutableState.emit(update, on: userCallbackQueue)
case .known(.counterInc):
// RTLC7d2
let update = applyCounterIncOperation(operation.counterOp)
let update = applyCounterIncOperation(operation.counterInc)
// RTLC7d2a
liveObjectMutableState.emit(update, on: userCallbackQueue)
case .known(.objectDelete):
Expand Down Expand Up @@ -445,14 +445,14 @@ internal final class InternalDefaultLiveCounter: Sendable {
}

/// Applies a `COUNTER_INC` operation, per RTLC9.
internal mutating func applyCounterIncOperation(_ operation: WireObjectsCounterOp?) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
internal mutating func applyCounterIncOperation(_ operation: CounterInc?) -> LiveObjectUpdate<DefaultLiveCounterUpdate> {
guard let operation else {
// RTL9e
return .noop
}

// RTLC9b, RTLC9d
let amount = operation.amount.doubleValue
let amount = operation.number.doubleValue
data += amount
return .update(DefaultLiveCounterUpdate(amount: amount))
}
Expand Down
26 changes: 13 additions & 13 deletions Sources/AblyLiveObjects/Internal/InternalDefaultLiveMap.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,11 +170,11 @@ internal final class InternalDefaultLiveMap: Sendable {
action: .known(.mapSet),
// RTLM20e3
objectId: mutableState.liveObjectMutableState.objectID,
mapOp: .init(
mapSet: .init(
// RTLM20e4
key: key,
// RTLM20e5
data: value.nosync_toObjectData,
value: value.nosync_toObjectData,
),
),
)
Expand All @@ -194,7 +194,7 @@ internal final class InternalDefaultLiveMap: Sendable {
action: .known(.mapRemove),
// RTLM21e3
objectId: mutableState.liveObjectMutableState.objectID,
mapOp: .init(
mapRemove: .init(
// RTLM21e4
key: key,
),
Expand Down Expand Up @@ -521,8 +521,8 @@ internal final class InternalDefaultLiveMap: Sendable {
userCallbackQueue: DispatchQueue,
clock: SimpleClock,
) -> LiveObjectUpdate<DefaultLiveMapUpdate> {
// RTLM17a: For each key–ObjectsMapEntry pair in ObjectOperation.map.entries
let perKeyUpdates: [LiveObjectUpdate<DefaultLiveMapUpdate>] = if let entries = operation.map?.entries {
// RTLM17a: For each key–ObjectsMapEntry pair in ObjectOperation.mapCreate.entries
let perKeyUpdates: [LiveObjectUpdate<DefaultLiveMapUpdate>] = if let entries = operation.mapCreate?.entries {
entries.map { key, entry in
if entry.tombstone == true {
// RTLM17a2: If ObjectsMapEntry.tombstone is true, apply the MAP_REMOVE operation
Expand Down Expand Up @@ -614,20 +614,20 @@ internal final class InternalDefaultLiveMap: Sendable {
// RTLM15d1a
liveObjectMutableState.emit(update, on: userCallbackQueue)
case .known(.mapSet):
guard let mapOp = operation.mapOp else {
logger.log("Could not apply MAP_SET since operation.mapOp is missing", level: .warn)
guard let mapSet = operation.mapSet else {
logger.log("Could not apply MAP_SET since operation.mapSet is missing", level: .warn)
return
}
guard let data = mapOp.data else {
logger.log("Could not apply MAP_SET since operation.data is missing", level: .warn)
guard let value = mapSet.value else {
logger.log("Could not apply MAP_SET since operation.mapSet.value is missing", level: .warn)
return
}

// RTLM15d2
let update = applyMapSetOperation(
key: mapOp.key,
key: mapSet.key,
operationTimeserial: applicableOperation.objectMessageSerial,
operationData: data,
operationData: value,
objectsPool: &objectsPool,
logger: logger,
internalQueue: internalQueue,
Expand All @@ -637,13 +637,13 @@ internal final class InternalDefaultLiveMap: Sendable {
// RTLM15d2a
liveObjectMutableState.emit(update, on: userCallbackQueue)
case .known(.mapRemove):
guard let mapOp = operation.mapOp else {
guard let mapRemove = operation.mapRemove else {
return
}

// RTLM15d3
let update = applyMapRemoveOperation(
key: mapOp.key,
key: mapRemove.key,
operationTimeserial: applicableOperation.objectMessageSerial,
operationSerialTimestamp: objectMessageSerialTimestamp,
logger: logger,
Expand Down
78 changes: 45 additions & 33 deletions Sources/AblyLiveObjects/Internal/ObjectCreationHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,10 @@ internal enum ObjectCreationHelpers {
timestamp: Date,
) -> CounterCreationOperation {
// RTO12f2: Create initial value for the new LiveCounter
let initialValue = PartialObjectOperation(
counter: WireObjectsCounter(count: NSNumber(value: count)),
)
let counterCreate = CounterCreate(count: NSNumber(value: count))

// RTO12f3: Create an initial value JSON string as described in RTO13
let initialValueJSONString = createInitialValueJSONString(from: initialValue)
let initialValueJSONString = createInitialValueJSONString(from: counterCreate)

// RTO12f4: Create a unique nonce as a random string
let nonce = generateNonce()
Expand All @@ -75,22 +73,27 @@ internal enum ObjectCreationHelpers {
)

// RTO12f7-12: Set ObjectMessage.operation fields
let operation = ObjectOperation(
let sendOperation = ObjectOperation(
action: .known(.counterCreate),
objectId: objectId,
counter: WireObjectsCounter(count: NSNumber(value: count)),
nonce: nonce,
initialValue: initialValueJSONString,
counterCreateWithObjectId: .init(nonce: nonce, initialValue: initialValueJSONString),
)

// TODO: The spec currently says that publishAndApply should apply the same operation that it sends (RTO20d2), but the operation we send (counterCreateWithObjectId) is not the one that mergeInitialValue expects (counterCreate). For now, we construct a separate operation for local application. We have not yet specified this behaviour: https://github.com/ably/specification/pull/426#discussion_r2849354510
let applyOperation = ObjectOperation(
action: .known(.counterCreate),
objectId: objectId,
counterCreate: counterCreate,
)

// Create the OutboundObjectMessage
let objectMessage = OutboundObjectMessage(
operation: operation,
operation: sendOperation,
)

return CounterCreationOperation(
objectID: objectId,
operation: operation,
operation: applyOperation,
objectMessage: objectMessage,
)
}
Expand All @@ -109,15 +112,14 @@ internal enum ObjectCreationHelpers {
ObjectsMapEntry(data: liveMapValue.nosync_toObjectData)
}

let initialValue = PartialObjectOperation(
map: ObjectsMap(
semantics: .known(.lww),
entries: mapEntries,
),
let semantics = ObjectsMapSemantics.lww
let mapCreate = MapCreate(
semantics: .known(semantics),
entries: mapEntries,
)

// RTO11f5: Create an initial value JSON string as described in RTO13
let initialValueJSONString = createInitialValueJSONString(from: initialValue)
let initialValueJSONString = createInitialValueJSONString(from: mapCreate)

// RTO11f6: Create a unique nonce as a random string
let nonce = generateNonce()
Expand All @@ -134,47 +136,57 @@ internal enum ObjectCreationHelpers {
)

// RTO11f9-13: Set ObjectMessage.operation fields
let semantics = ObjectsMapSemantics.lww
let operation = ObjectOperation(
let sendOperation = ObjectOperation(
action: .known(.mapCreate),
objectId: objectId,
map: ObjectsMap(
semantics: .known(semantics),
entries: mapEntries,
),
nonce: nonce,
initialValue: initialValueJSONString,
mapCreateWithObjectId: .init(nonce: nonce, initialValue: initialValueJSONString),
)

// TODO: The spec currently says that publishAndApply should apply the same operation that it sends (RTO20d2), but the operation we send (mapCreateWithObjectId) is not the one that mergeInitialValue expects (mapCreate). For now, we construct a separate operation for local application. We have not yet specified this behaviour: https://github.com/ably/specification/pull/426#discussion_r2849354510
let applyOperation = ObjectOperation(
action: .known(.mapCreate),
objectId: objectId,
mapCreate: mapCreate,
)

// Create the OutboundObjectMessage
let objectMessage = OutboundObjectMessage(
operation: operation,
operation: sendOperation,
)

return MapCreationOperation(
objectID: objectId,
operation: operation,
operation: applyOperation,
objectMessage: objectMessage,
semantics: semantics,
)
}

// MARK: - Private Helper Methods

/// Creates an initial value JSON string from a PartialObjectOperation, per RTO13.
private static func createInitialValueJSONString(from initialValue: PartialObjectOperation) -> String {
// RTO13b: Encode the initial value using OM4 encoding
let partialWireObjectOperation = initialValue.toWire(format: .json)
let jsonObject = partialWireObjectOperation.toWireObject.mapValues { wireValue in
/// Creates an initial value JSON string from a CounterCreate, per RTO12f13.
private static func createInitialValueJSONString(from counterCreate: CounterCreate) -> String {
let wireCounterCreate = counterCreate.toWire()
return createInitialValueJSONString(wireObject: wireCounterCreate.toWireObject)
}

/// Creates an initial value JSON string from a MapCreate, per RTO11f15.
private static func createInitialValueJSONString(from mapCreate: MapCreate) -> String {
let wireMapCreate = mapCreate.toWire(format: .json)
return createInitialValueJSONString(wireObject: wireMapCreate.toWireObject)
}

/// Encodes a wire object dictionary to a JSON string for use as an initial value.
private static func createInitialValueJSONString(wireObject: [String: WireValue]) -> String {
let jsonObject: [String: JSONValue] = wireObject.mapValues { wireValue in
do {
return try wireValue.toJSONValue
} catch {
// By using `format: .json` we've requested a type that should be JSON-encodable, so if it isn't then it's a programmer error. (We can't reason about it statically though because of our choice to use a general-purpose WireValue type; maybe could improve upon this in the future.)
// By using `format: .json` we've requested a type that should be JSON-encodable, so if it isn't then it's a programmer error.
preconditionFailure("Failed to convert WireValue \(wireValue) to JSONValue when encoding initialValue")
}
}

// RTO13c
return JSONObjectOrArray.object(jsonObject).toJSONString
}

Expand Down
Loading
Loading