Skip to content

Commit

Permalink
Streamlined Bag.
Browse files Browse the repository at this point in the history
  • Loading branch information
andersio committed May 1, 2017
1 parent e2d8bf8 commit ad106a1
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 64 deletions.
104 changes: 47 additions & 57 deletions Sources/Bag.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
// Copyright (c) 2014 GitHub. All rights reserved.
//

/// A uniquely identifying token for removing a value that was inserted into a
/// Bag.
public final class RemovalToken {}

/// An unordered, non-unique collection of values of type `Element`.
public struct Bag<Element> {
fileprivate var elements: ContiguousArray<BagElement<Element>> = []
/// A uniquely identifying token for removing a value that was inserted into a
/// Bag.
public typealias Token = UInt64

fileprivate var elements: ContiguousArray<Element> = []
fileprivate var tokens: ContiguousArray<Token> = []

private var identifier: UInt64 = 0

public init() {}

Expand All @@ -22,11 +25,16 @@ public struct Bag<Element> {
/// - parameters:
/// - value: A value that will be inserted.
@discardableResult
public mutating func insert(_ value: Element) -> RemovalToken {
let token = RemovalToken()
let element = BagElement(value: value, token: token)
public mutating func insert(_ value: Element) -> Token {
let token = Token(identifier)

// Practically speaking, this would overflow only if we have 101% uptime and we
// manage to call `insert(_:)` every 1 ns for 500+ years non-stop.
identifier += 1

elements.append(value)
tokens.append(token)

elements.append(element)
return token
}

Expand All @@ -36,73 +44,55 @@ public struct Bag<Element> {
///
/// - parameters:
/// - token: A token returned from a call to `insert()`.
public mutating func remove(using token: RemovalToken) {
let tokenIdentifier = ObjectIdentifier(token)
// Removal is more likely for recent objects than old ones.
for i in elements.indices.reversed() {
if ObjectIdentifier(elements[i].token) == tokenIdentifier {
public mutating func remove(using token: Token) {
for i in (elements.startIndex ..< elements.endIndex).reversed() {
if tokens[i] == token {
tokens.remove(at: i)
elements.remove(at: i)
break
return
}
}
}
}

extension Bag: Collection {
public typealias Index = Array<Element>.Index

public var startIndex: Index {
extension Bag: RandomAccessCollection {
public var startIndex: Int {
return elements.startIndex
}

public var endIndex: Index {
return elements.endIndex
}

public subscript(index: Index) -> Element {
return elements[index].value
public var endIndex: Int {
return elements.endIndex
}

public func index(after i: Index) -> Index {
return i + 1
public subscript(index: Int) -> Element {
return elements[index]
}

public func makeIterator() -> BagIterator<Element> {
return BagIterator(elements)
public func makeIterator() -> Iterator {
return Iterator(elements)
}
}

private struct BagElement<Value> {
let value: Value
let token: RemovalToken
}

extension BagElement: CustomStringConvertible {
var description: String {
return "BagElement(\(value))"
}
}
/// An iterator of `Bag`.
public struct Iterator: IteratorProtocol {
private let base: ContiguousArray<Element>
private var nextIndex: Int
private let endIndex: Int

/// An iterator of `Bag`.
public struct BagIterator<Element>: IteratorProtocol {
private let base: ContiguousArray<BagElement<Element>>
private var nextIndex: Int
private let endIndex: Int
fileprivate init(_ base: ContiguousArray<Element>) {
self.base = base
nextIndex = base.startIndex
endIndex = base.endIndex
}

fileprivate init(_ base: ContiguousArray<BagElement<Element>>) {
self.base = base
nextIndex = base.startIndex
endIndex = base.endIndex
}
public mutating func next() -> Element? {
let currentIndex = nextIndex

public mutating func next() -> Element? {
let currentIndex = nextIndex
if currentIndex < endIndex {
nextIndex = currentIndex + 1
return base[currentIndex]
}

if currentIndex < endIndex {
nextIndex = currentIndex + 1
return base[currentIndex].value
return nil
}

return nil
}
}
5 changes: 4 additions & 1 deletion Sources/Deprecations+Removals.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ extension Action where Input == Void {
public convenience init<P: PropertyProtocol, T>(input: P, _ execute: @escaping (T) -> SignalProducer<Output, Error>) where P.Value == T { fatalError() }
}

@available(*, unavailable, renamed:"Bag.Token")
public typealias RemovalToken = Bag<Any>.Token

@available(*, unavailable, message: "This protocol has been removed. Constrain `Action` directly instead.")
public protocol ActionProtocol {}

Expand Down Expand Up @@ -231,7 +234,7 @@ extension LoggingEvent.SignalProducer {

extension Bag {
@available(*, unavailable, renamed:"remove(using:)")
public func removeValueForToken(_ token: RemovalToken) { fatalError() }
public func removeValueForToken(_ token: Token) { fatalError() }
}

extension CompositeDisposable {
Expand Down
4 changes: 2 additions & 2 deletions Sources/Disposable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ public final class CompositeDisposable: Disposable {
/// `DisposableHandle`.
public final class DisposableHandle {
private var state: UnsafeAtomicState<DisposableState>
private var bagToken: RemovalToken?
private var bagToken: Bag<Disposable>.Token?
private weak var disposable: CompositeDisposable?

fileprivate static let empty = DisposableHandle()
Expand All @@ -131,7 +131,7 @@ public final class CompositeDisposable: Disposable {
state.deinitialize()
}

fileprivate init(bagToken: RemovalToken, disposable: CompositeDisposable) {
fileprivate init(bagToken: Bag<Disposable>.Token, disposable: CompositeDisposable) {
self.state = UnsafeAtomicState(.active)
self.bagToken = bagToken
self.disposable = disposable
Expand Down
2 changes: 1 addition & 1 deletion Sources/Signal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ public final class Signal<Value, Error: Swift.Error> {
/// or `nil` if the signal has already terminated.
@discardableResult
public func observe(_ observer: Observer) -> Disposable? {
var token: RemovalToken?
var token: Bag<Observer>.Token?
updateLock.lock()
if case let .alive(snapshot) = state {
var observers = snapshot.observers
Expand Down
6 changes: 3 additions & 3 deletions Sources/SignalProducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1969,7 +1969,7 @@ extension SignalProducer {
disposable += { _ = lifetimeToken }

while true {
var result: Result<RemovalToken?, ReplayError<Value>>!
var result: Result<Bag<Signal<Value, Error>.Observer>.Token?, ReplayError<Value>>!
state.modify {
result = $0.observe(observer)
}
Expand Down Expand Up @@ -2098,7 +2098,7 @@ private struct ReplayState<Value, Error: Swift.Error> {
/// with the corresponding removal token would be returned.
/// Otherwise, a `Result.failure` with a `ReplayError` would be
/// returned.
mutating func observe(_ observer: Signal<Value, Error>.Observer) -> Result<RemovalToken?, ReplayError<Value>> {
mutating func observe(_ observer: Signal<Value, Error>.Observer) -> Result<Bag<Signal<Value, Error>.Observer>.Token?, ReplayError<Value>> {
// Since the only use case is `replayLazily`, which always creates a unique
// `Observer` for every produced signal, we can use the ObjectIdentifier of
// the `Observer` to track them directly.
Expand Down Expand Up @@ -2175,7 +2175,7 @@ private struct ReplayState<Value, Error: Swift.Error> {
///
/// - parameters:
/// - token: The token of the observer to be removed.
mutating func removeObserver(using token: RemovalToken) {
mutating func removeObserver(using token: Bag<Signal<Value, Error>.Observer>.Token) {
observers?.remove(using: token)
}
}
Expand Down

0 comments on commit ad106a1

Please sign in to comment.