Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions Sources/OpenGestures/Component/CompositeGestureComponent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//
// CompositeGestureComponent.swift
// OpenGestures
//
// Audited for 9126.1.5
// Status: Complete

// MARK: - CompositeGestureComponent

/// A gesture component that wraps an upstream component, enabling chaining.
public protocol CompositeGestureComponent: GestureComponent {
associatedtype Upstream: GestureComponent
var upstream: Upstream { get set }
}

extension CompositeGestureComponent {
public mutating func reset() {
upstream.reset()
}

public func traits() -> GestureTraitCollection? {
upstream.traits()
}

public func capacity<E: Event>(for eventType: E.Type) -> Int {
upstream.capacity(for: eventType)
}
}
10 changes: 10 additions & 0 deletions Sources/OpenGestures/Component/DiscreteComponent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
//
// DiscreteComponent.swift
// OpenGestures
//
// Audited for 9126.1.5
// Status: Complete

// MARK: - DiscreteComponent

public protocol DiscreteComponent: GestureComponent {}
50 changes: 50 additions & 0 deletions Sources/OpenGestures/Component/DiscreteGate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//
// DiscreteGate.swift
// OpenGestures
//
// Audited for 9126.1.5
// Status: Complete

// MARK: - DiscreteGate

package struct DiscreteGate<Upstream: GestureComponent>: Sendable {
package var upstream: Upstream

package init(upstream: Upstream) {
self.upstream = upstream
}
}

// MARK: - DiscreteGate + GestureComponent

extension DiscreteGate: GestureComponent {
package typealias Value = Upstream.Value
}

// MARK: - DiscreteGate + CompositeGestureComponent

extension DiscreteGate: CompositeGestureComponent {}

// MARK: - DiscreteGate + DiscreteComponent

extension DiscreteGate: DiscreteComponent {}

// MARK: - DiscreteGate + ValueTransformingComponent

extension DiscreteGate: ValueTransformingComponent {
package mutating func transform(
_ value: Value,
isFinal: Bool
) throws -> GestureOutput<Value> {
if isFinal {
return .finalValue(value, metadata: nil)
} else {
return .empty(
.filtered,
metadata: GestureOutputMetadata(
traceAnnotation: UpdateTraceAnnotation(value: "not final event")
)
)
}
}
}
116 changes: 77 additions & 39 deletions Sources/OpenGestures/Component/GestureComponent.swift
Original file line number Diff line number Diff line change
@@ -1,77 +1,115 @@
//
// GestureComponent.swift
// OpenGestures
//
// Audited for 9126.1.5
// Status: Complete

// MARK: - GestureComponent

/// A protocol for gesture recognition components.
public protocol GestureComponent: Sendable {
associatedtype Value: Sendable

func update(context: GestureComponentContext) throws -> GestureOutput<Value>
mutating func update(context: GestureComponentContext) throws -> GestureOutput<Value>
mutating func reset()
func traits() -> GestureTraitCollection?
func capacity<E: Event>(for eventType: E.Type) -> Int
}

// MARK: - Default capacity
// MARK: - GestureComponent + Tracing

extension GestureComponent {
public func capacity<E: Event>(for eventType: E.Type) -> Int { 1 }
}

// MARK: - StatefulGestureComponent

/// A gesture component that maintains mutable state across updates.
public protocol StatefulGestureComponent: GestureComponent {
associatedtype State: GestureComponentState
var state: State { get set }
}

extension StatefulGestureComponent {
public mutating func reset() {
state = State()
package mutating func tracingUpdateResult(
context: GestureComponentContext
) -> Result<GestureOutput<Value>, any Error> {
Result {
try tracingUpdate(context: context)
}
}
}

// MARK: - CompositeGestureComponent

/// A gesture component that wraps an upstream component, enabling chaining.
public protocol CompositeGestureComponent: GestureComponent {
associatedtype Upstream: GestureComponent
var upstream: Upstream { get set }
}

extension CompositeGestureComponent {
/// Default: delegates to upstream.update(context:)
public func update(context: GestureComponentContext) throws -> GestureOutput<Value> {
fatalError("CompositeGestureComponent subtype must override update(context:)")
package mutating func tracingUpdate(context: GestureComponentContext) throws -> GestureOutput<Value> {
guard let updateTracer = context.updateTracer else {
return try update(context: context)
}

updateTracer.beginTrace()
let result: Result<GestureOutput<Value>, any Error> = Result {
try update(context: context)
}
let snapshot = makeTraceDataSnapshot(result: result)
updateTracer.endTrace(snapshot: snapshot)
return try result.get()
}

public mutating func reset() {
upstream.reset()
private mutating func makeTraceDataSnapshot(
result: Result<GestureOutput<Value>, any Error>
) -> TraceDataSnapshot {
let componentDescription = String(describing: self)
let resultDescription = String(describing: result)
let stateDescription: String
if let stateful = self as? any StatefulGestureComponent {
stateDescription = String(describing: stateful.state)
} else {
stateDescription = ""
}
return TraceDataSnapshot(
component: { componentDescription },
result: { resultDescription },
state: { stateDescription },
isSuccess: {
switch result {
case .success: true
case .failure: false
}
}()
)
}
}

public func traits() -> GestureTraitCollection? {
upstream.traits()
extension CompositeGestureComponent {
public mutating func update(
context: GestureComponentContext
) throws -> GestureOutput<Upstream.Value> {
try upstream.tracingUpdate(context: context)
}
}

// MARK: - GestureComponentContext

/// Context passed to gesture components during update cycles.
public struct GestureComponentContext: Sendable {
public struct GestureComponentContext: @unchecked Sendable {
public var startTime: Timestamp
public var currentTime: Timestamp
package var updateSource: GestureUpdateSource
package var updateTracer: UpdateTracer?
package var eventStore: AnyEventStore

public var durationSinceStart: Duration {
startTime.duration(to: currentTime)
}

public init(startTime: Timestamp, currentTime: Timestamp) {
package init(
startTime: Timestamp,
currentTime: Timestamp,
updateSource: GestureUpdateSource,
updateTracer: UpdateTracer? = nil,
eventStore: AnyEventStore
) {
self.startTime = startTime
self.currentTime = currentTime
self.updateSource = updateSource
self.updateTracer = updateTracer
self.eventStore = eventStore
}
}

// MARK: - GestureComponentState
// MARK: - GestureUpdateSource

/// Source that caused a gesture component update cycle.
package enum GestureUpdateSource: Equatable, Sendable {
/// Scheduler-driven update carrying the scheduled request identifiers.
case scheduler(Set<UInt32>)

public protocol GestureComponentState: Sendable {
init()
/// Event-driven update.
case event
}
Loading
Loading