Last active
October 8, 2015 15:17
-
-
Save neilpa/cc64d42d64fc502d1457 to your computer and use it in GitHub Desktop.
Building collections from signals[-of-signals] of collection mutations
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// A half-baked approach (not thread-safe, poor disposable management) for creating | |
// aggregate collections from multiple signals of collection mutations. In this | |
// case we are concating multiple mutation streams into a single container. So for | |
// each inner signal we need to offset subsequent splices by the count of preceding | |
// items (which can be recovered by scanning previous splices). | |
// | |
// Example: | |
// | |
// let (first, sink1) = SignalProducer<Splice<Int>, NoError>.buffer() | |
// let (second, sink2) = SignalProducer<Splice<Int>, NoError>.buffer() | |
// sendNext(sink1, Splice(index: 0, remove: [], insert: [1, 2, 3])) | |
// sendNext(sink2, Splice(index: 0, remove: [], insert: [4, 5, 6])) | |
// sendNext(sink2, Splice(index: 2, remove: [6], insert: [])) | |
// | |
// let combined = SignalProducer<SignalProducer<Splice<Int>, NoError>, NoError>(values: [first, second]) | |
// combined |> concatSplices |> start(next: println) | |
// | |
// Output: | |
// | |
// @0 -[] +[1, 2, 3] | |
// @3 -[] +[4, 5, 6] | |
// @5 -[6] +[] | |
// | |
// Inspiration | |
// https://github.com/ReactiveCocoa/ReactiveCocoa/pull/1032 | |
// https://github.com/ReactiveCocoa/ReactiveCocoa/issues/1903 | |
// https://github.com/jonsterling/Taliban-MVVM/issues/1 | |
// https://github.com/ReactiveCocoa/ReactiveCocoa/pull/1228#discussion_r11776533 | |
// Define collection mutations in terms of splices | |
public struct Splice<T>: Printable { | |
public let index: Int | |
// In most cases this could be simplified to just a count of removed | |
// items. However, tracking the actual items allows us to freely invert | |
// without additional context. That in turn can enable things like | |
// deferral of updates on "live" collections in cases where our UI isn't | |
// ready to accept the update yet (e.g. in the middle of an animation). | |
public let remove: [T] | |
public let insert: [T] | |
public func translate(amount: Int) -> Splice { | |
return Splice(index: index + amount, remove: remove, insert: insert) | |
} | |
public var delta: Int { | |
return insert.count - remove.count | |
} | |
public var description: String { | |
return "@\(index) -\(remove) +\(insert)" | |
} | |
} | |
// For lack of a better name at the moment | |
// TODO Could just use a Range | |
public struct Jump { | |
public let offset: Int | |
public let count: Int | |
public func successor() -> Jump { | |
return Jump(offset: offset + count, count: 0) | |
} | |
public func resize(delta: Int) -> Jump { | |
return Jump(offset: offset, count: count + delta) | |
} | |
public func translate(amount: Int) -> Jump { | |
return Jump(offset: offset + amount, count: count) | |
} | |
} | |
public func concatSplices<T>(producer: SignalProducer<SignalProducer<Splice<T>, NoError>, NoError>) -> SignalProducer<Splice<T>, NoError> { | |
return SignalProducer { observer, disposable in | |
var tail = Node(value: Jump(offset: 0, count: 0), previous: nil) | |
let tryComplete: () -> () = { _ in | |
if tail.previous == nil { | |
sendCompleted(observer) | |
} | |
} | |
producer | |
|> on(terminated: tryComplete) | |
|> start(next: { innerProducer in | |
tail = Node(value: tail.value.successor(), previous: tail) | |
let node = tail | |
innerProducer | |
|> on(terminated: { _ in | |
node.drop() | |
tryComplete() | |
}) | |
|> start(next: { splice in | |
sendNext(observer, splice.translate(node.value.offset)) | |
// Propagate index offset updates | |
if splice.delta != 0 { | |
node.value = node.value.resize(splice.delta) | |
var previous = node | |
while let next = previous.next { | |
next.value = next.value.translate(splice.delta) | |
previous = next | |
} | |
} | |
}) | |
}, completed: { | |
tryComplete() | |
}) | |
} | |
} | |
// Dead-simple linked list | |
public final class Node<T> { | |
var value: T | |
var previous: Node? | |
var next: Node? | |
public init(value: T, previous: Node?) { | |
self.value = value | |
self.previous = previous | |
next = previous?.next | |
previous?.next = self | |
} | |
public func drop() { | |
next?.previous = previous | |
previous?.next = next | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment