Skip to content

Commit 95845d7

Browse files
Implement find-or-create for createMap / createCounter
Based on [1] at cb11ba8. Interface by me, implementation by Cursor. TODO unit test [1] ably/specification#353
1 parent d920775 commit 95845d7

1 file changed

Lines changed: 89 additions & 0 deletions

File tree

Sources/AblyLiveObjects/Internal/ObjectsPool.swift

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,95 @@ internal struct ObjectsPool {
306306
logger.log("applySyncObjectsPool completed. Pool now contains \(entries.count) objects", level: .debug)
307307
}
308308

309+
/// Gets or creates a counter object in the pool, implementing the "find or create zero-value" behavior of RTO12h1.
310+
///
311+
/// - Parameters:
312+
/// - creationOperation: The CounterCreationOperation containing the object ID and operation to merge
313+
/// - logger: The logger to use for any created LiveObject
314+
/// - userCallbackQueue: The callback queue to use for any created LiveObject
315+
/// - clock: The clock to use for any created LiveObject
316+
/// - Returns: The existing or newly created counter object
317+
internal mutating func getOrCreateCounter(
318+
creationOperation: ObjectCreationHelpers.CounterCreationOperation,
319+
logger: AblyPlugin.Logger,
320+
userCallbackQueue: DispatchQueue,
321+
clock: SimpleClock,
322+
) -> InternalDefaultLiveCounter {
323+
// RTO12h2: If an object with the ObjectMessage.operation.objectId exists in the internal ObjectsPool, return it
324+
if let existingEntry = entries[creationOperation.objectID] {
325+
switch existingEntry {
326+
case let .counter(counter):
327+
return counter
328+
case .map:
329+
// TODO: Add the ability to statically reason about the type of pool entries in https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/36
330+
preconditionFailure("Expected counter object with ID \(creationOperation.objectID) but found map object")
331+
}
332+
}
333+
334+
// RTO12h3: Otherwise, if the object does not exist in the internal ObjectsPool:
335+
// RTO12h3a: Create a zero-value LiveCounter, set its objectId to ObjectMessage.operation.objectId, and merge the initial value
336+
let counter = InternalDefaultLiveCounter.createZeroValued(
337+
objectID: creationOperation.objectID,
338+
logger: logger,
339+
userCallbackQueue: userCallbackQueue,
340+
clock: clock,
341+
)
342+
343+
// Merge the initial value from the creation operation
344+
_ = counter.mergeInitialValue(from: creationOperation.operation)
345+
346+
// RTO12h3b: Add the created LiveCounter instance to the internal ObjectsPool
347+
entries[creationOperation.objectID] = .counter(counter)
348+
349+
// RTO12h3c: Return the created LiveCounter instance
350+
return counter
351+
}
352+
353+
/// Gets or creates a map object in the pool, implementing the "find or create zero-value" behavior of RTO11h1.
354+
///
355+
/// - Parameters:
356+
/// - creationOperation: The MapCreationOperation containing the object ID and operation to merge
357+
/// - logger: The logger to use for any created LiveObject
358+
/// - userCallbackQueue: The callback queue to use for any created LiveObject
359+
/// - clock: The clock to use for any created LiveObject
360+
/// - Returns: The existing or newly created map object
361+
internal mutating func getOrCreateMap(
362+
creationOperation: ObjectCreationHelpers.MapCreationOperation,
363+
logger: AblyPlugin.Logger,
364+
userCallbackQueue: DispatchQueue,
365+
clock: SimpleClock,
366+
) -> InternalDefaultLiveMap {
367+
// RTO11h2: If an object with the ObjectMessage.operation.objectId exists in the internal ObjectsPool, return it
368+
if let existingEntry = entries[creationOperation.objectID] {
369+
switch existingEntry {
370+
case let .map(map):
371+
return map
372+
case .counter:
373+
// TODO: Add the ability to statically reason about the type of pool entries in https://github.com/ably/ably-cocoa-liveobjects-plugin/issues/36
374+
preconditionFailure("Expected map object with ID \(creationOperation.objectID) but found counter object")
375+
}
376+
}
377+
378+
// RTO11h3: Otherwise, if the object does not exist in the internal ObjectsPool:
379+
// RTO11h3a: Create a zero-value LiveMap, set its objectId to ObjectMessage.operation.objectId, set its semantics to ObjectMessage.operation.map.semantics, and merge the initial value
380+
let map = InternalDefaultLiveMap.createZeroValued(
381+
objectID: creationOperation.objectID,
382+
semantics: .known(creationOperation.semantics),
383+
logger: logger,
384+
userCallbackQueue: userCallbackQueue,
385+
clock: clock,
386+
)
387+
388+
// Merge the initial value from the creation operation
389+
_ = map.mergeInitialValue(from: creationOperation.operation, objectsPool: &self)
390+
391+
// RTO11h3b: Add the created LiveMap instance to the internal ObjectsPool
392+
entries[creationOperation.objectID] = .map(map)
393+
394+
// RTO11h3c: Return the created LiveMap instance
395+
return map
396+
}
397+
309398
/// Removes all entries except the root, and clears the root's data. This is to be used when an `ATTACHED` ProtocolMessage indicates that the only object in a channel is an empty root map, per RTO4b.
310399
internal mutating func reset() {
311400
let root = root

0 commit comments

Comments
 (0)