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
182 changes: 182 additions & 0 deletions Sources/OpenGestures/Component/Components/CombinerComponent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
//
// CombinerComponent.swift
// OpenGestures
//
// Audited for 9126.1.5
// Status: Complete

// MARK: - CombinerComponent

package struct CombinerComponent<each Upstream, Output>: Sendable
where repeat each Upstream: GestureComponent, Output: Sendable {
package var upstream: (repeat CombinerElement<each Upstream>)
package let outputCombiner: GestureOutputCombiner<repeat (each Upstream).Value, Output>
package let resetComponentsOnCompletion: Bool

package init(
upstream: (repeat CombinerElement<each Upstream>),
outputCombiner: GestureOutputCombiner<repeat (each Upstream).Value, Output>,
resetComponentsOnCompletion: Bool
) {
self.upstream = upstream
self.outputCombiner = outputCombiner
self.resetComponentsOnCompletion = resetComponentsOnCompletion
}
}

// MARK: - CombinerComponent + GestureComponent

extension CombinerComponent: GestureComponent {
package typealias Value = Output

package mutating func update(context: GestureComponentContext) throws -> GestureOutput<Value> {
let updated = (repeat try Self.updateElement(
each upstream,
context: context,
resetComponentsOnCompletion: resetComponentsOnCompletion
))
let outputs = (repeat (each updated).output)
upstream = (repeat (each updated).component)
return try outputCombiner.combine(repeat each outputs)
}

private static func updateElement<Component>(
_ component: CombinerElement<Component>,
context: GestureComponentContext,
resetComponentsOnCompletion: Bool
) throws -> (component: CombinerElement<Component>, output: GestureOutput<Component.Value>) {
var component = component
let output = try component.tracingUpdate(context: context)
if resetComponentsOnCompletion, output.isFinal {
component.reset()
}
return (component, output)
}

package mutating func reset() {
upstream = (repeat {
var element = each upstream
element.reset()
return element
}())
}

package mutating func traits() -> GestureTraitCollection? {
var result: GestureTraitCollection?
upstream = (repeat Self.collectTraits(from: each upstream, into: &result))
return result
}

private static func collectTraits<Component>(
from component: CombinerElement<Component>,
into result: inout GestureTraitCollection?
) -> CombinerElement<Component> {
var component = component
let traits = component.traits()
let newResult: GestureTraitCollection?
if let result, let traits {
newResult = result.merging(traits)
} else if let result {
newResult = result
} else {
newResult = traits
}
result = newResult
return component
}

package mutating func capacity<EventType: Event>(for eventType: EventType.Type) -> Int {
let updated = (repeat {
var component = each upstream
let capacity = component.capacity(for: eventType)
return (component: component, capacity: capacity)
}())
upstream = (repeat (each updated).component)

var total = 0
for capacity in repeat (each updated).capacity {
total += capacity
}
return total
}
}

// MARK: - CombinerElement

package struct CombinerElement<Upstream>: Sendable where Upstream: GestureComponent {
package struct State: GestureComponentState, NestedCustomStringConvertible, Sendable {
package var cachedOutput: GestureOutput<Upstream.Value>?
package var isDirty: Bool

package init() {
cachedOutput = nil
isDirty = false
}
}

package var upstream: Upstream
package var state: State

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

extension CombinerElement: ReplicatingValue {
package func replicated() -> Self {
var copy = self
copy.reset()
return copy
}
}

extension CombinerElement: GestureComponent {
package typealias Value = Upstream.Value

package mutating func update(context: GestureComponentContext) throws -> GestureOutput<Value> {
state.isDirty = true
guard let cachedOutput = state.cachedOutput, cachedOutput.isFinal else {
let output = try upstream.tracingUpdate(context: context)
guard !output.isEmpty else {
guard let cachedOutput = state.cachedOutput else {
return output
}
return cachedOutput.copyWithCombinedMetadata(
output.metadata ?? GestureOutputMetadata()
)
}
state.cachedOutput = output.copyClearingMetadata()
return output
}
return cachedOutput
}

package mutating func reset() {
guard state.isDirty else {
return
}
state = State()
upstream.reset()
}

package mutating func traits() -> GestureTraitCollection? {
state.isDirty = true
return upstream.traits()
}

package mutating func capacity<EventType: Event>(for eventType: EventType.Type) -> Int {
state.isDirty = true
guard let cachedOutput = state.cachedOutput, cachedOutput.isFinal else {
return upstream.capacity(for: eventType)
}
return 0
}
}

extension CombinerElement: StatefulGestureComponent {}

extension CombinerElement: CompositeGestureComponent {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
//
// DynamicCombinerComponent.swift
// OpenGestures
//
// Audited for 9126.1.5
// Status: Complete

// MARK: - DynamicCombinerComponent

package struct DynamicCombinerComponent<Upstream>: Sendable where Upstream: GestureComponent {
package enum Failure: Error, Hashable, Sendable {
case limitExceeded
}

package var upstreams: ReplicatingList<CombinerElement<Upstream>>
package let outputCombiner: GestureOutputArrayCombiner<Upstream.Value>
package let initialCount: Int
package let limit: Int
package let failOnExceedingLimit: Bool
package let resetComponentsOnCompletion: Bool

// TBA
package init(
upstream: Upstream,
outputCombiner: GestureOutputArrayCombiner<Upstream.Value>,
initialCount: Int,
limit: Int,
failOnExceedingLimit: Bool,
resetComponentsOnCompletion: Bool
) {
self.upstreams = ReplicatingList(
prototype: CombinerElement(upstream: upstream),
count: initialCount
)
self.outputCombiner = outputCombiner
self.initialCount = initialCount
self.limit = limit
self.failOnExceedingLimit = failOnExceedingLimit
self.resetComponentsOnCompletion = resetComponentsOnCompletion
}

package init(
upstreams: ReplicatingList<CombinerElement<Upstream>>,
outputCombiner: GestureOutputArrayCombiner<Upstream.Value>,
initialCount: Int,
limit: Int,
failOnExceedingLimit: Bool,
resetComponentsOnCompletion: Bool
) {
self.upstreams = upstreams
self.outputCombiner = outputCombiner
self.initialCount = initialCount
self.limit = limit
self.failOnExceedingLimit = failOnExceedingLimit
self.resetComponentsOnCompletion = resetComponentsOnCompletion
}
}

// MARK: - DynamicCombinerComponent + GestureComponent

extension DynamicCombinerComponent: GestureComponent {
package typealias Value = [Upstream.Value]

package mutating func update(context: GestureComponentContext) throws -> GestureOutput<Value> {
guard !upstreams.isEmpty else {
return .empty(
.filtered,
metadata: GestureOutputMetadata(
traceAnnotation: UpdateTraceAnnotation(value: "no upstreams")
)
)
}

var outputs: [GestureOutput<Upstream.Value>] = []
var noDataCount = 0
for index in upstreams.indices {
let output = try upstreams[index].tracingUpdate(context: context)
outputs.append(output)
if output.emptyReason == .noData {
noDataCount += 1
}
}
if noDataCount == 0, context.updateSource == .event {
let limit = limit
while upstreams.count < limit || failOnExceedingLimit {
upstreams.appendReplications(1)
let index = upstreams.count - 1
let output = try upstreams[index].tracingUpdate(context: context)
guard output.emptyReason != .noData else {
upstreams.removeLast(1)
break
}
if failOnExceedingLimit, limit < upstreams.count {
throw Failure.limitExceeded
} else {
outputs.append(output)
}
}
}
if resetComponentsOnCompletion {
for index in outputs.indices.reversed() where outputs[index].isFinal {
if initialCount >= upstreams.count {
upstreams[index].reset()
} else {
upstreams.remove(at: index)
}
}
}
return try outputCombiner.combine(outputs)
}

package mutating func reset() {
upstreams.resize(to: initialCount)
for index in upstreams.indices {
upstreams[index].reset()
}
}

package mutating func traits() -> GestureTraitCollection? {
var prototype = upstreams.prototype()
return prototype.traits()
}

package mutating func capacity<EventType: Event>(for eventType: EventType.Type) -> Int {
var total = 0
for index in upstreams.indices {
total += upstreams[index].capacity(for: eventType)
}

var prototype = upstreams.prototype()
let prototypeCapacity = prototype.capacity(for: eventType)
guard prototypeCapacity >= 1 else {
return total
}
guard upstreams.count < limit || failOnExceedingLimit else {
return total
}
return Swift.min(limit, total + prototypeCapacity)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ extension CompositeGestureComponent {
upstream.reset()
}

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

public func capacity<E: Event>(for eventType: E.Type) -> Int {
public mutating func capacity<E: Event>(for eventType: E.Type) -> Int {
upstream.capacity(for: eventType)
}
}
4 changes: 2 additions & 2 deletions Sources/OpenGestures/Component/GestureComponent.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ public protocol GestureComponent: Sendable {
associatedtype Value: Sendable
mutating func update(context: GestureComponentContext) throws -> GestureOutput<Value>
mutating func reset()
func traits() -> GestureTraitCollection?
func capacity<E: Event>(for eventType: E.Type) -> Int
mutating func traits() -> GestureTraitCollection?
mutating func capacity<E: Event>(for eventType: E.Type) -> Int
}

// MARK: - GestureComponent + Tracing
Expand Down
Loading
Loading