Skip to content
Open
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
2 changes: 1 addition & 1 deletion .github/workflows/create-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
jobs:
create_release:
name: Create Release
runs-on: macos-14
runs-on: macos-15
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
Expand Down
7 changes: 1 addition & 6 deletions .github/workflows/docc-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,10 @@ on: [push]

jobs:
deploy_docs:
runs-on: macos-14
runs-on: macos-15
steps:
- uses: actions/checkout@v2

- name: Setup Swift 5.9
uses: swift-actions/setup-swift@v1
with:
swift-version: '5.9'

- name: Build Docs
uses: sersoft-gmbh/swifty-docs-action@v3.0.0
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:

jobs:
build:
runs-on: macos-14
runs-on: macos-15

steps:
- uses: actions/checkout@v2
Expand Down
5 changes: 4 additions & 1 deletion Benchmark/Sources/MotionBenchmark/MotionBenchmark.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Benchmark
import Motion
import QuartzCore

@MainActor
func createAndAnimateSpringAnimations<Value: SIMDRepresentable>(to toValue: Value, count: Int, state: inout BenchmarkState) {
autoreleasepool {
let springAnimations = (0..<count).map { (_) -> SpringAnimation<Value> in
Expand All @@ -26,6 +27,7 @@ func createAndAnimateSpringAnimations<Value: SIMDRepresentable>(to toValue: Valu
}
}

@MainActor
func createAndAnimateBasicAnimations<Value: SIMDRepresentable>(to toValue: Value, count: Int, state: inout BenchmarkState) {
autoreleasepool {
let basicAnimations = (0..<count).map { (_) -> BasicAnimation<Value> in
Expand All @@ -43,7 +45,7 @@ func createAndAnimateBasicAnimations<Value: SIMDRepresentable>(to toValue: Value
}
}


@MainActor
func createAndAnimateDecayAnimations<Value: SIMDRepresentable>(velocity: Value, count: Int, state: inout BenchmarkState) {
autoreleasepool {
let decayAnimations = (0..<count).map { (_) -> DecayAnimation<Value> in
Expand All @@ -65,6 +67,7 @@ let ToValue = 320.0

// Measure execution of 5000 animations of each type serially for each supported SIMD type.
// SIMD go brrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr
@MainActor
public func RunBenchmark() {
let springAnimationSuite = BenchmarkSuite(name: "SIMD SpringAnimations", settings: TimeUnit(.ms)) { suite in
suite.benchmark("Execute 5000 CGFloat SpringAnimations") { state in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -275,6 +275,8 @@
SDKROOT = iphoneos;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 6.0;
};
name = Debug;
};
Expand Down Expand Up @@ -329,6 +331,8 @@
SDKROOT = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_STRICT_CONCURRENCY = complete;
SWIFT_VERSION = 6.0;
VALIDATE_PRODUCT = YES;
};
name = Release;
Expand All @@ -347,7 +351,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "ca.adambell.MotionExample-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
Expand All @@ -366,7 +369,6 @@
);
PRODUCT_BUNDLE_IDENTIFIER = "ca.adambell.MotionExample-iOS";
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class AnimationWrapper<T: Motion.Animation>: ObservableObject {
self.animation = animation
}


var animation: T

}
Expand Down Expand Up @@ -48,7 +47,7 @@ struct SwiftUIDemoView: View {
// this allows subsequent drags to also work correctly.
animationWrapper.animation.updateValue(to: CGPoint(x: position.x + totalTranslation.x, y: position.y + totalTranslation.y), postValueChanged: true)
animationWrapper.animation.toValue = .zero
animationWrapper.animation.velocity = dragGesture.velocity
animationWrapper.animation.velocity = CGPoint(x: dragGesture.velocity.width, y: dragGesture.velocity.height)
animationWrapper.animation.start()
}
)
Expand All @@ -67,29 +66,6 @@ struct SwiftUIDemoView: View {

}

extension DragGesture.Value {

/// h/t @lukaskubanek https://stackoverflow.com/questions/62906109/what-is-the-best-way-to-get-drag-velocity/73426600#73426600
internal var velocity: CGPoint {
let valueMirror = Mirror(reflecting: self)
for valueChild in valueMirror.children {
if valueChild.label == "velocity" {
let velocityMirror = Mirror(reflecting: valueChild.value)
for velocityChild in velocityMirror.children {
if velocityChild.label == "valuePerSecond" {
if let velocity = velocityChild.value as? CGSize {
return CGPoint(x: velocity.width, y: velocity.height)
}
}
}
}
}
assertionFailure("Unable to retrieve velocity from \(Self.self)")
return .zero
}

}

struct SwiftUIDemoView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIDemoView()
Expand Down
6 changes: 3 additions & 3 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// swift-tools-version:5.9
// swift-tools-version:5.10
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
Expand All @@ -8,7 +8,7 @@ let package = Package(
platforms: [
.iOS(.v13),
.tvOS(.v13),
.macOS(.v10_14),
.macOS(.v10_15),
.visionOS(.v1)
],
products: [
Expand Down Expand Up @@ -42,5 +42,5 @@ let package = Package(
"Motion",
]),
],
swiftLanguageVersions: [.v5]
swiftLanguageVersions: [.v5, .version("6")]
)
90 changes: 47 additions & 43 deletions Sources/Graphing/ValueAnimationGraph.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import Motion
import SwiftUI

@available(iOS 14.0, macOS 11.0, tvOS 14.0, *)
@MainActor
public struct ValueAnimationShape: Shape {

public enum GraphType {
public enum GraphType: Sendable {
case position
case velocity
}
Expand All @@ -36,52 +37,55 @@ public struct ValueAnimationShape: Shape {
}

public func path(in rect: CGRect) -> Path {
let dt = 1.0 / 60.0

animation.stop()
animation.updateValue(to: 0.0)

let height = rect.size.height / 2.0

let points: [CGPoint] = stride(from: 0.0, to: duration, by: dt).map { (i) -> CGPoint in
let percent: CGFloat = CGFloat(i / duration)

let point: CGPoint

let position = { () -> CGPoint in
if let decayAnimation = animation as? DecayAnimation {
return CGPoint(x: rect.width * percent, y: height - decayAnimation.value)
} else {
return CGPoint(x: rect.width * percent, y: height + ((animation.toValue - animation.value) * height))
// lol.
MainActor.assumeIsolated {
let dt = 1.0 / 60.0

animation.stop()
animation.updateValue(to: 0.0)

let height = rect.size.height / 2.0

let points: [CGPoint] = stride(from: 0.0, to: duration, by: dt).map { (i) -> CGPoint in
let percent: CGFloat = CGFloat(i / duration)

let point: CGPoint

let position = { () -> CGPoint in
if let decayAnimation = animation as? DecayAnimation {
return CGPoint(x: rect.width * percent, y: height - decayAnimation.value)
} else {
return CGPoint(x: rect.width * percent, y: height + ((animation.toValue - animation.value) * height))
}
}
}

let velocity = { () -> CGPoint in
let velocity: CGFloat
if type(of: animation).supportsVelocity {
velocity = animation.velocity
} else {
return position()

let velocity = { () -> CGPoint in
let velocity: CGFloat
if type(of: animation).supportsVelocity {
velocity = animation.velocity
} else {
return position()
}

return CGPoint(x: rect.width * percent, y: height + velocity * height / 3.0)
}

return CGPoint(x: rect.width * percent, y: height + velocity * height / 3.0)
}

switch graphType {
case .position:
point = position()
case .velocity:
point = velocity()

switch graphType {
case .position:
point = position()
case .velocity:
point = velocity()
}

animation.tick(frame: .init(timestamp: 0, targetTimestamp: dt))

return point
}

animation.tick(frame: .init(timestamp: 0, targetTimestamp: dt))

return point
var path = Path()
path.addLines(points)
return path
}

var path = Path()
path.addLines(points)
return path
}

}
Expand Down
8 changes: 4 additions & 4 deletions Sources/Motion/Animations/BasicAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public final class BasicAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu
}

#if DEBUG
internal func solveAccumulatedTime<SIMDType: SupportedSIMD>(easingFunction: EasingFunction<SIMDType>, range: inout ClosedRange<SIMDType>, value: inout SIMDType) -> CFTimeInterval? {
nonisolated internal func solveAccumulatedTime<SIMDType: SupportedSIMD>(easingFunction: EasingFunction<SIMDType>, range: inout ClosedRange<SIMDType>, value: inout SIMDType) -> CFTimeInterval? {
/* Must Be Mirrored Below */

if !range.contains(value) {
Expand All @@ -107,7 +107,7 @@ public final class BasicAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu
@_specialize(kind: partial, where SIMDType == SIMD32<Double>)
@_specialize(kind: partial, where SIMDType == SIMD64<Float>)
@_specialize(kind: partial, where SIMDType == SIMD64<Double>)
internal func solveAccumulatedTime<SIMDType: SupportedSIMD>(easingFunction: EasingFunction<SIMDType>, range: inout ClosedRange<SIMDType>, value: inout SIMDType) -> CFTimeInterval? {
nonisolated internal func solveAccumulatedTime<SIMDType: SupportedSIMD>(easingFunction: EasingFunction<SIMDType>, range: inout ClosedRange<SIMDType>, value: inout SIMDType) -> CFTimeInterval? {
/* Must Be Mirrored Above */

if !range.contains(value) {
Expand Down Expand Up @@ -231,7 +231,7 @@ public final class BasicAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu

// See docs in SpringAnimation.swift for why this `@_specialize` stuff exists.
#if DEBUG
internal func tickOptimized<SIMDType: SupportedSIMD>(easingFunction: EasingFunction<SIMDType>, range: inout ClosedRange<SIMDType>, fraction: SIMDType.Scalar, value: inout SIMDType) where SIMDType.Scalar == SIMDType.SIMDType.Scalar {
nonisolated internal func tickOptimized<SIMDType: SupportedSIMD>(easingFunction: EasingFunction<SIMDType>, range: inout ClosedRange<SIMDType>, fraction: SIMDType.Scalar, value: inout SIMDType) where SIMDType.Scalar == SIMDType.SIMDType.Scalar {
/* Must Be Mirrored Below */

value = easingFunction.solveInterpolatedValueSIMD(range, fraction: fraction)
Expand All @@ -251,7 +251,7 @@ public final class BasicAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu
@_specialize(kind: partial, where SIMDType == SIMD32<Double>)
@_specialize(kind: partial, where SIMDType == SIMD64<Float>)
@_specialize(kind: partial, where SIMDType == SIMD64<Double>)
internal func tickOptimized<SIMDType: SupportedSIMD>(easingFunction: EasingFunction<SIMDType>, range: inout ClosedRange<SIMDType>, fraction: SIMDType.Scalar, value: inout SIMDType) where SIMDType.Scalar == SIMDType.SIMDType.Scalar {
nonisolated internal func tickOptimized<SIMDType: SupportedSIMD>(easingFunction: EasingFunction<SIMDType>, range: inout ClosedRange<SIMDType>, fraction: SIMDType.Scalar, value: inout SIMDType) where SIMDType.Scalar == SIMDType.SIMDType.Scalar {
/* Must Be Mirrored Above */

value = easingFunction.solveInterpolatedValueSIMD(range, fraction: fraction)
Expand Down
4 changes: 2 additions & 2 deletions Sources/Motion/Animations/DecayAnimation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public final class DecayAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu

// See docs in SpringAnimation.swift for why this exists.
#if DEBUG
internal func tickOptimized<SIMDType: SupportedSIMD>(_ dt: SIMDType.SIMDType.Scalar, decay: DecayFunction<SIMDType>, value: inout SIMDType, velocity: inout SIMDType) where SIMDType.SIMDType == SIMDType {
nonisolated internal func tickOptimized<SIMDType: SupportedSIMD>(_ dt: SIMDType.SIMDType.Scalar, decay: DecayFunction<SIMDType>, value: inout SIMDType, velocity: inout SIMDType) where SIMDType.SIMDType == SIMDType {
/* Must Be Mirrored Below */

value = decay.solveSIMD(dt: dt, x0: value, velocity: &velocity)
Expand All @@ -210,7 +210,7 @@ public final class DecayAnimation<Value: SIMDRepresentable>: ValueAnimation<Valu
@_specialize(kind: partial, where SIMDType == SIMD32<Double>)
@_specialize(kind: partial, where SIMDType == SIMD64<Float>)
@_specialize(kind: partial, where SIMDType == SIMD64<Double>)
internal func tickOptimized<SIMDType: SupportedSIMD>(_ dt: SIMDType.SIMDType.Scalar, decay: DecayFunction<SIMDType>, value: inout SIMDType, velocity: inout SIMDType) where SIMDType.SIMDType == SIMDType {
nonisolated internal func tickOptimized<SIMDType: SupportedSIMD>(_ dt: SIMDType.SIMDType.Scalar, decay: DecayFunction<SIMDType>, value: inout SIMDType, velocity: inout SIMDType) where SIMDType.SIMDType == SIMDType {
/* Must Be Mirrored Above */

value = decay.solveSIMD(dt: dt, x0: value, velocity: &velocity)
Expand Down
2 changes: 1 addition & 1 deletion Sources/Motion/Animations/Functions/DecayFunction.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public let UIScrollViewDecayConstant: Double = 0.998
- Note: This can be used on its own, but it's mainly used by `DecayAnimation`'s `tick` method.
- SeeAlso: `DecayAnimation`
*/
public struct DecayFunction<Value: SIMDRepresentable> {
public struct DecayFunction<Value: SIMDRepresentable>: Sendable {

/// The rate at which the velocity decays over time. Defaults to `UIKitDecayConstant`.
public var decayConstant: Value.SIMDType.Scalar {
Expand Down
Loading