Skip to content

Commit 368d33c

Browse files
authored
Add GesturePhaseQueue and RingBuffer implementation (#14)
* Add GesturePhaseQueue and RingBuffer implementation * Add RingBuffer and GesturePhaseQueue tests
1 parent 7ed2c4a commit 368d33c

6 files changed

Lines changed: 437 additions & 42 deletions

File tree

Sources/OpenGestures/ConflictResolution/GesturePhaseQueue.swift

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//
2+
// GesturePhaseQueue.swift
3+
// OpenGestures
4+
//
5+
// Audited for 9126.1.5
6+
// Status: Complete
7+
8+
// MARK: - GesturePhaseQueue
9+
10+
/// A queue of gesture phase transitions.
11+
package struct GesturePhaseQueue<Value: Sendable> {
12+
package var timeSource: (any TimeSource)?
13+
package var currentPhase: GesturePhase<Value>
14+
package var pendingPhases: RingBuffer<GesturePhase<Value>>
15+
16+
package init(
17+
timeSource: (any TimeSource)?,
18+
currentPhase: GesturePhase<Value>,
19+
pendingPhases: RingBuffer<GesturePhase<Value>>
20+
) {
21+
self.timeSource = timeSource
22+
self.currentPhase = currentPhase
23+
self.pendingPhases = pendingPhases
24+
}
25+
}
26+
27+
// MARK: - GesturePhaseQueue.InvalidTransition
28+
29+
extension GesturePhaseQueue {
30+
package struct InvalidTransition: Error {
31+
package var phase: GesturePhase<Value>
32+
package var targetPhase: GesturePhase<Value>
33+
34+
package init(phase: GesturePhase<Value>, targetPhase: GesturePhase<Value>) {
35+
self.phase = phase
36+
self.targetPhase = targetPhase
37+
}
38+
}
39+
}
40+
41+
extension GesturePhaseQueue.InvalidTransition: NestedCustomStringConvertible {}
42+

Sources/OpenGestures/GestureNode/GestureNode.swift

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,11 @@ public final class GestureNode<Value: Sendable>: AnyGestureNode, @unchecked Send
1111
private var _didUpdatePhase: ((GesturePhase<Value>, GesturePhase<Value>) -> Void)?
1212
private var _shouldActivate: (() -> Bool)?
1313

14-
public private(set) var phase: GesturePhase<Value> = .idle
15-
public private(set) var latestPhase: GesturePhase<Value> = .idle
14+
package var phaseQueue: GesturePhaseQueue<Value> = GesturePhaseQueue(
15+
timeSource: nil,
16+
currentPhase: .idle,
17+
pendingPhases: RingBuffer(capacity: 5, emptyValue: .idle)
18+
)
1619

1720
// MARK: - Init
1821

@@ -39,14 +42,10 @@ public final class GestureNode<Value: Sendable>: AnyGestureNode, @unchecked Send
3942
// MARK: - Update
4043

4144
public func update(value: Value, isFinalUpdate: Bool) throws {
42-
let oldPhase = phase
43-
if isFinalUpdate {
44-
phase = .ended(value: value)
45-
} else {
46-
phase = .active(value: value)
47-
}
48-
latestPhase = phase
49-
_didUpdatePhase?(phase, oldPhase)
45+
let oldPhase = phaseQueue.currentPhase
46+
let newPhase: GesturePhase<Value> = isFinalUpdate ? .ended(value: value) : .active(value: value)
47+
phaseQueue.currentPhase = newPhase
48+
_didUpdatePhase?(newPhase, oldPhase)
5049
}
5150

5251
public override func update(someValue: Any, isFinalUpdate: Bool) throws {
@@ -59,17 +58,17 @@ public final class GestureNode<Value: Sendable>: AnyGestureNode, @unchecked Send
5958
// MARK: - Abort / Fail
6059

6160
public override func abort() throws {
62-
let oldPhase = phase
63-
phase = .failed(reason:.aborted)
64-
latestPhase = phase
65-
_didUpdatePhase?(phase, oldPhase)
61+
let oldPhase = phaseQueue.currentPhase
62+
let newPhase: GesturePhase<Value> = .failed(reason: .aborted)
63+
phaseQueue.currentPhase = newPhase
64+
_didUpdatePhase?(newPhase, oldPhase)
6665
}
6766

6867
public override func fail(with error: Error) throws {
69-
let oldPhase = phase
68+
let oldPhase = phaseQueue.currentPhase
7069
// TODO: .error(Error) case once non-Sendable handling resolved
71-
phase = .failed(reason:.aborted)
72-
latestPhase = phase
73-
_didUpdatePhase?(phase, oldPhase)
70+
let newPhase: GesturePhase<Value> = .failed(reason: .aborted)
71+
phaseQueue.currentPhase = newPhase
72+
_didUpdatePhase?(newPhase, oldPhase)
7473
}
7574
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
//
2+
// RingBuffer.swift
3+
// OpenGestures
4+
//
5+
// Audited for 9126.1.5
6+
// Status: Complete
7+
8+
// MARK: - RingBuffer
9+
10+
package struct RingBuffer<Element> {
11+
package let capacity: Int
12+
package var count: Int
13+
package var storage: [Element]
14+
package var emptyValue: Element
15+
package var start: Int
16+
package var end: Int
17+
18+
package init(capacity: Int, emptyValue: Element) {
19+
self.capacity = capacity
20+
self.count = 0
21+
self.storage = Array(repeating: emptyValue, count: capacity)
22+
self.emptyValue = emptyValue
23+
self.start = 0
24+
self.end = 0
25+
}
26+
27+
package var isEmpty: Bool { count == 0 }
28+
29+
package var isFull: Bool { count == capacity }
30+
31+
package mutating func append(_ element: Element) {
32+
storage[end] = element
33+
end = (end + 1) % capacity
34+
if isFull {
35+
start = (start + 1) % capacity
36+
} else {
37+
count += 1
38+
}
39+
}
40+
41+
@discardableResult
42+
package mutating func removeFirst() -> Element {
43+
let value = storage[start]
44+
storage[start] = emptyValue
45+
start = (start + 1) % capacity
46+
count -= 1
47+
return value
48+
}
49+
}
50+
51+
// MARK: - RingBuffer + Sequence
52+
53+
extension RingBuffer: Sequence {
54+
package func makeIterator() -> RingBufferIterator<Element> {
55+
RingBufferIterator(
56+
ringBuffer: self,
57+
currentIndex: start,
58+
elementsRemaining: count
59+
)
60+
}
61+
}
62+
63+
// MARK: - RingBuffer + Collection
64+
65+
extension RingBuffer: Collection {
66+
package var startIndex: Int { 0 }
67+
68+
package var endIndex: Int { count }
69+
70+
package func index(after i: Int) -> Int { i + 1 }
71+
72+
package subscript(position: Int) -> Element {
73+
storage[(start + position) % capacity]
74+
}
75+
}
76+
77+
// MARK: - RingBuffer + BidirectionalCollection
78+
79+
extension RingBuffer: BidirectionalCollection {
80+
package func index(before i: Int) -> Int { i - 1 }
81+
}
82+
83+
// MARK: - RingBuffer + CustomStringConvertible
84+
85+
extension RingBuffer: CustomStringConvertible {
86+
package var description: String {
87+
"[" + map { "\($0)" }.joined(separator: ", ") + "]"
88+
}
89+
}
90+
91+
// MARK: - RingBufferIterator
92+
93+
package struct RingBufferIterator<Element>: IteratorProtocol {
94+
package let ringBuffer: RingBuffer<Element>
95+
package var currentIndex: Int
96+
package var elementsRemaining: Int
97+
98+
package init(
99+
ringBuffer: RingBuffer<Element>,
100+
currentIndex: Int,
101+
elementsRemaining: Int
102+
) {
103+
self.ringBuffer = ringBuffer
104+
self.currentIndex = currentIndex
105+
self.elementsRemaining = elementsRemaining
106+
}
107+
108+
package mutating func next() -> Element? {
109+
guard elementsRemaining > 0 else { return nil }
110+
let value = ringBuffer.storage[currentIndex]
111+
currentIndex = (currentIndex + 1) % ringBuffer.capacity
112+
elementsRemaining -= 1
113+
return value
114+
}
115+
}
116+
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//
2+
// GesturePhaseQueueTests.swift
3+
// OpenGesturesTests
4+
5+
@_spi(Private) import OpenGestures
6+
import Testing
7+
8+
// MARK: - GesturePhaseQueueTests
9+
10+
@Suite
11+
struct GesturePhaseQueueTests {
12+
13+
// MARK: - Init
14+
15+
@Test
16+
func testInit() {
17+
let queue = GesturePhaseQueue<Int>(
18+
timeSource: nil,
19+
currentPhase: .idle,
20+
pendingPhases: RingBuffer(capacity: 5, emptyValue: .idle)
21+
)
22+
#expect(queue.currentPhase.isIdle == true)
23+
#expect(queue.pendingPhases.isEmpty == true)
24+
#expect(queue.timeSource == nil)
25+
}
26+
27+
// MARK: - Properties
28+
29+
@Test
30+
func testCurrentPhaseUpdate() {
31+
var queue = GesturePhaseQueue<Int>(
32+
timeSource: nil,
33+
currentPhase: .idle,
34+
pendingPhases: RingBuffer(capacity: 5, emptyValue: .idle)
35+
)
36+
queue.currentPhase = .active(value: 42)
37+
#expect(queue.currentPhase.isActive == true)
38+
}
39+
40+
@Test
41+
func testPendingPhasesAppend() {
42+
var queue = GesturePhaseQueue<Int>(
43+
timeSource: nil,
44+
currentPhase: .idle,
45+
pendingPhases: RingBuffer(capacity: 5, emptyValue: .idle)
46+
)
47+
queue.pendingPhases.append(.active(value: 1))
48+
#expect(queue.pendingPhases.count == 1)
49+
}
50+
51+
// MARK: - InvalidTransition
52+
53+
@Test
54+
func testInvalidTransitionInit() {
55+
let transition = GesturePhaseQueue<Int>.InvalidTransition(
56+
phase: .idle,
57+
targetPhase: .active(value: 1)
58+
)
59+
#expect(transition.phase.isIdle == true)
60+
#expect(transition.targetPhase.isActive == true)
61+
}
62+
63+
@Test
64+
func testInvalidTransitionIsError() {
65+
let _: any Error = GesturePhaseQueue<Int>.InvalidTransition(
66+
phase: .idle,
67+
targetPhase: .active(value: 1)
68+
)
69+
}
70+
71+
@Test
72+
func testInvalidTransitionDescription() {
73+
let transition = GesturePhaseQueue<Int>.InvalidTransition(
74+
phase: .idle,
75+
targetPhase: .active(value: 1)
76+
)
77+
#expect(transition.description == #"""
78+
InvalidTransition { \#("")
79+
phase: idle
80+
targetPhase: active
81+
}
82+
"""#)
83+
}
84+
}
85+

0 commit comments

Comments
 (0)