Skip to content

Commit d62c733

Browse files
authored
Extract PriorityQueue to its own module. (#1932)
Motivation: I'd like to move EmbeddedChannel and friends out of the main NIO repository and into their own module. Unfortunately, EmbeddedChannel shares the PriorityQueue implementation we wrote with the various POSIX channels. To avoid duplicating the code, we should pull it out to its own module. However, we've never wanted to commit to the API of this data structure, and the same is true now. To that end, I'm pulling it into an underscored module that is not a product of the package. We could have used the `@_spi` annotation here but honestly I'm a bit nervous about doing that at the low-level of NIO itself, as if the Swift team does change the spelling of it at any point in the future we'll be in real trouble. This way works almost as well, and makes our intent a lot clearer. Modifications: - Extracted Heap and PriorityQueue to a new module. - Made everything @inlinable to do our best to make performance acceptable. Result: We can re-use PriorityQueue.
1 parent c1e2f09 commit d62c733

File tree

10 files changed

+90
-53
lines changed

10 files changed

+90
-53
lines changed

Package.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
//
44
// This source file is part of the SwiftNIO open source project
55
//
6-
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
6+
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
77
// Licensed under Apache License v2.0
88
//
99
// See LICENSE.txt for license information
@@ -18,12 +18,14 @@ import PackageDescription
1818
var targets: [PackageDescription.Target] = [
1919
.target(name: "NIOCore",
2020
dependencies: ["NIOConcurrencyHelpers", "CNIOLinux"]),
21+
.target(name: "_NIODataStructures"),
2122
.target(name: "NIO",
2223
dependencies: ["CNIOLinux",
2324
"CNIODarwin",
2425
"CNIOWindows",
2526
"NIOConcurrencyHelpers",
26-
"NIOCore"]),
27+
"NIOCore",
28+
"_NIODataStructures"]),
2729
.target(name: "_NIOConcurrency",
2830
dependencies: ["NIO"]),
2931
.target(name: "NIOFoundationCompat", dependencies: ["NIO"]),
@@ -74,6 +76,8 @@ var targets: [PackageDescription.Target] = [
7476
dependencies: ["NIO", "NIOFoundationCompat", "NIOTestUtils", "NIOConcurrencyHelpers"]),
7577
.testTarget(name: "NIOConcurrencyHelpersTests",
7678
dependencies: ["NIOConcurrencyHelpers", "NIO"]),
79+
.testTarget(name: "NIODataStructuresTests",
80+
dependencies: ["_NIODataStructures"]),
7781
.testTarget(name: "NIOHTTP1Tests",
7882
dependencies: ["NIOHTTP1", "NIOFoundationCompat", "NIOTestUtils"]),
7983
.testTarget(name: "NIOTLSTests",

Sources/NIO/Embedded.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import Dispatch
16+
import _NIODataStructures
1617

1718
private final class EmbeddedScheduledTask {
1819
let task: () -> Void

Sources/NIO/SelectableEventLoop.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import Dispatch
1616
import NIOConcurrencyHelpers
17+
import _NIODataStructures
1718

1819
/// Execute the given closure and ensure we release all auto pools if needed.
1920
@inlinable

Sources/NIO/Heap.swift renamed to Sources/_NIODataStructures/Heap.swift

Lines changed: 51 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the SwiftNIO open source project
44
//
5-
// Copyright (c) 2017-2020 Apple Inc. and the SwiftNIO project authors
5+
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -11,37 +11,49 @@
1111
// SPDX-License-Identifier: Apache-2.0
1212
//
1313
//===----------------------------------------------------------------------===//
14+
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
15+
import Darwin.C
16+
#elseif os(Linux) || os(FreeBSD) || os(Android)
17+
import Glibc
18+
#endif
1419

1520
@usableFromInline
1621
internal struct Heap<Element: Comparable> {
1722
@usableFromInline
18-
internal private(set) var storage: ContiguousArray<Element> = []
23+
internal private(set) var storage: ContiguousArray<Element>
1924

20-
@usableFromInline
21-
internal init() {}
25+
@inlinable
26+
internal init() {
27+
self.storage = []
28+
}
2229

30+
@inlinable
2331
internal func comparator(_ lhs: Element, _ rhs: Element) -> Bool {
2432
// This heap is always a min-heap.
2533
return lhs < rhs
2634
}
2735

2836
// named `PARENT` in CLRS
29-
private func parentIndex(_ i: Int) -> Int {
37+
@inlinable
38+
internal func parentIndex(_ i: Int) -> Int {
3039
return (i-1) / 2
3140
}
3241

3342
// named `LEFT` in CLRS
43+
@inlinable
3444
internal func leftIndex(_ i: Int) -> Int {
3545
return 2*i + 1
3646
}
3747

3848
// named `RIGHT` in CLRS
49+
@inlinable
3950
internal func rightIndex(_ i: Int) -> Int {
4051
return 2*i + 2
4152
}
4253

4354
// named `MAX-HEAPIFY` in CLRS
44-
private mutating func heapify(_ index: Int) {
55+
/* private but */ @inlinable
56+
mutating func _heapify(_ index: Int) {
4557
let left = self.leftIndex(index)
4658
let right = self.rightIndex(index)
4759

@@ -58,12 +70,13 @@ internal struct Heap<Element: Comparable> {
5870

5971
if root != index {
6072
self.storage.swapAt(index, root)
61-
self.heapify(root)
73+
self._heapify(root)
6274
}
6375
}
6476

6577
// named `HEAP-INCREASE-KEY` in CRLS
66-
private mutating func heapRootify(index: Int, key: Element) {
78+
/* private but */ @inlinable
79+
mutating func _heapRootify(index: Int, key: Element) {
6780
var index = index
6881
if self.comparator(storage[index], key) {
6982
fatalError("New key must be closer to the root than current key")
@@ -76,7 +89,7 @@ internal struct Heap<Element: Comparable> {
7689
}
7790
}
7891

79-
@usableFromInline
92+
@inlinable
8093
internal mutating func append(_ value: Element) {
8194
var i = self.storage.count
8295
self.storage.append(value)
@@ -87,44 +100,45 @@ internal struct Heap<Element: Comparable> {
87100
}
88101

89102
@discardableResult
90-
@usableFromInline
103+
@inlinable
91104
internal mutating func removeRoot() -> Element? {
92-
return self.remove(index: 0)
105+
return self._remove(index: 0)
93106
}
94107

95108
@discardableResult
96-
@usableFromInline
109+
@inlinable
97110
internal mutating func remove(value: Element) -> Bool {
98111
if let idx = self.storage.firstIndex(of: value) {
99-
self.remove(index: idx)
112+
self._remove(index: idx)
100113
return true
101114
} else {
102115
return false
103116
}
104117
}
105118

106119
@discardableResult
107-
private mutating func remove(index: Int) -> Element? {
120+
/* private but */ @inlinable
121+
mutating func _remove(index: Int) -> Element? {
108122
guard self.storage.count > 0 else {
109123
return nil
110124
}
111125
let element = self.storage[index]
112126
if self.storage.count == 1 || self.storage[index] == self.storage[self.storage.count - 1] {
113127
self.storage.removeLast()
114128
} else if !self.comparator(self.storage[index], self.storage[self.storage.count - 1]) {
115-
self.heapRootify(index: index, key: self.storage[self.storage.count - 1])
129+
self._heapRootify(index: index, key: self.storage[self.storage.count - 1])
116130
self.storage.removeLast()
117131
} else {
118132
self.storage[index] = self.storage[self.storage.count - 1]
119133
self.storage.removeLast()
120-
self.heapify(index)
134+
self._heapify(index)
121135
}
122136
return element
123137
}
124138
}
125139

126140
extension Heap: CustomDebugStringConvertible {
127-
@usableFromInline
141+
@inlinable
128142
var debugDescription: String {
129143
guard self.storage.count > 0 else {
130144
return "<empty heap>"
@@ -184,40 +198,52 @@ extension Heap: CustomDebugStringConvertible {
184198

185199
@usableFromInline
186200
struct HeapIterator<Element: Comparable>: IteratorProtocol {
187-
private var heap: Heap<Element>
201+
/* private but */ @usableFromInline
202+
var _heap: Heap<Element>
188203

204+
@inlinable
189205
init(heap: Heap<Element>) {
190-
self.heap = heap
206+
self._heap = heap
191207
}
192208

193-
@usableFromInline
209+
@inlinable
194210
mutating func next() -> Element? {
195-
return self.heap.removeRoot()
211+
return self._heap.removeRoot()
196212
}
197213
}
198214

199215
extension Heap: Sequence {
200-
var startIndex: Int { return self.storage.startIndex }
201-
var endIndex: Int { return self.storage.endIndex }
216+
@inlinable
217+
var startIndex: Int {
218+
return self.storage.startIndex
219+
}
202220

203-
@usableFromInline
221+
@inlinable
222+
var endIndex: Int {
223+
return self.storage.endIndex
224+
}
225+
226+
@inlinable
204227
var underestimatedCount: Int {
205228
return self.storage.count
206229
}
207230

208-
@usableFromInline
231+
@inlinable
209232
func makeIterator() -> HeapIterator<Element> {
210233
return HeapIterator(heap: self)
211234
}
212235

236+
@inlinable
213237
subscript(position: Int) -> Element {
214238
return self.storage[position]
215239
}
216240

241+
@inlinable
217242
func index(after i: Int) -> Int {
218243
return i + 1
219244
}
220245

246+
@inlinable
221247
var count: Int {
222248
return self.storage.count
223249
}

Sources/NIO/PriorityQueue.swift renamed to Sources/_NIODataStructures/PriorityQueue.swift

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
//
33
// This source file is part of the SwiftNIO open source project
44
//
5-
// Copyright (c) 2017-2020 Apple Inc. and the SwiftNIO project authors
5+
// Copyright (c) 2017-2021 Apple Inc. and the SwiftNIO project authors
66
// Licensed under Apache License v2.0
77
//
88
// See LICENSE.txt for license information
@@ -12,83 +12,87 @@
1212
//
1313
//===----------------------------------------------------------------------===//
1414

15-
@usableFromInline
16-
internal struct PriorityQueue<Element: Comparable> {
15+
public struct PriorityQueue<Element: Comparable> {
1716
@usableFromInline
1817
internal var _heap: Heap<Element>
1918

20-
internal init() {
19+
@inlinable
20+
public init() {
2121
self._heap = Heap()
2222
}
2323

2424
@inlinable
25-
internal mutating func remove(_ key: Element) {
25+
public mutating func remove(_ key: Element) {
2626
self._heap.remove(value: key)
2727
}
2828

2929
@inlinable
30-
internal mutating func push(_ key: Element) {
30+
public mutating func push(_ key: Element) {
3131
self._heap.append(key)
3232
}
3333

3434
@inlinable
35-
internal func peek() -> Element? {
35+
public func peek() -> Element? {
3636
return self._heap.storage.first
3737
}
3838

3939
@inlinable
40-
internal var isEmpty: Bool {
40+
public var isEmpty: Bool {
4141
return self._heap.storage.isEmpty
4242
}
4343

4444
@inlinable
4545
@discardableResult
46-
internal mutating func pop() -> Element? {
46+
public mutating func pop() -> Element? {
4747
return self._heap.removeRoot()
4848
}
4949

5050
@inlinable
51-
internal mutating func clear() {
51+
public mutating func clear() {
5252
self._heap = Heap()
5353
}
5454
}
5555

5656
extension PriorityQueue: Equatable {
57-
@usableFromInline
58-
internal static func ==(lhs: PriorityQueue, rhs: PriorityQueue) -> Bool {
57+
@inlinable
58+
public static func ==(lhs: PriorityQueue, rhs: PriorityQueue) -> Bool {
5959
return lhs.count == rhs.count && lhs.elementsEqual(rhs)
6060
}
6161
}
6262

6363
extension PriorityQueue: Sequence {
64-
@usableFromInline
65-
struct Iterator: IteratorProtocol {
64+
public struct Iterator: IteratorProtocol {
65+
66+
/* private but */ @usableFromInline
67+
var _queue: PriorityQueue<Element>
6668

67-
private var queue: PriorityQueue<Element>
68-
fileprivate init(queue: PriorityQueue<Element>) {
69-
self.queue = queue
69+
/* fileprivate but */ @inlinable
70+
public init(queue: PriorityQueue<Element>) {
71+
self._queue = queue
7072
}
7173

74+
@inlinable
7275
public mutating func next() -> Element? {
73-
return self.queue.pop()
76+
return self._queue.pop()
7477
}
7578
}
7679

77-
@usableFromInline
78-
func makeIterator() -> Iterator {
80+
@inlinable
81+
public func makeIterator() -> Iterator {
7982
return Iterator(queue: self)
8083
}
8184
}
8285

8386
extension PriorityQueue {
84-
var count: Int {
87+
@inlinable
88+
public var count: Int {
8589
return self._heap.count
8690
}
8791
}
8892

8993
extension PriorityQueue: CustomStringConvertible {
90-
@usableFromInline
91-
var description: String {
94+
@inlinable
95+
public var description: String {
9296
return "PriorityQueue(count: \(self.count)): \(Array(self))"
9397
}
9498
}

Tests/LinuxMain.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import XCTest
2424

2525
#if os(Linux) || os(FreeBSD) || os(Android)
2626
@testable import NIOConcurrencyHelpersTests
27+
@testable import NIODataStructuresTests
2728
@testable import NIOFoundationCompatTests
2829
@testable import NIOHTTP1Tests
2930
@testable import NIOTLSTests
File renamed without changes.

Tests/NIOTests/HeapTests.swift renamed to Tests/NIODataStructuresTests/HeapTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
//===----------------------------------------------------------------------===//
1414

1515
import XCTest
16-
@testable import NIO
16+
@testable import _NIODataStructures
1717

1818
public func getRandomNumbers(count: Int) -> [UInt8] {
1919
return (0..<count).map { _ in
File renamed without changes.

0 commit comments

Comments
 (0)