Tweaked the stop/reset behavior to behave more deterministically on state machines and animations. Added a test view that shows the parity in playback behavior between almost identical animations and state machines (it's commented out in ExamplesMasterTableViewController by default). Fixed bug with triggering inputs on state machines. Deleted riv files we're not using anymore

This commit is contained in:
Zachary Duncan
2022-05-27 17:45:19 -04:00
committed by Zachary Duncan
parent b51be942bb
commit 344d4108b2
15 changed files with 213 additions and 163 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -49,10 +49,12 @@
C324DB5D280728690060589F /* RiveButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C324DB5C280728690060589F /* RiveButton.swift */; };
C324DB5F280740FB0060589F /* rbutton.riv in Resources */ = {isa = PBXBuildFile; fileRef = C324DB5E280740FB0060589F /* rbutton.riv */; };
C3357CA1280F42EC00F03B6F /* ExamplesMaster.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3357CA0280F42EC00F03B6F /* ExamplesMaster.swift */; };
C3460A002800A6CE002DBCB7 /* bird.riv in Resources */ = {isa = PBXBuildFile; fileRef = C34609FD2800A6CE002DBCB7 /* bird.riv */; };
C3468E6227FDCBC6008652FD /* SimpleSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3468E6127FDCBC6008652FD /* SimpleSlider.swift */; };
C3745FCE282AE2320087F4AF /* hero_editor.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3745FCB282AE2320087F4AF /* hero_editor.riv */; };
C3C07482283D582B00E8EB33 /* button.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3C0747F283D582B00E8EB33 /* button.riv */; };
C3C074AB283FC75900E8EB33 /* testanimation.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3C074A7283FC75800E8EB33 /* testanimation.riv */; };
C3C074AC283FC75900E8EB33 /* teststatemachine.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3C074AA283FC75900E8EB33 /* teststatemachine.riv */; };
C3C074EE28414F4600E8EB33 /* SwiftTestParityAnimSM.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3C074ED28414F4600E8EB33 /* SwiftTestParityAnimSM.swift */; };
C3C074F2284161E400E8EB33 /* halloween.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3C074F1284161E400E8EB33 /* halloween.riv */; };
C3D187F3280751A8008B739A /* RiveProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D187F2280751A8008B739A /* RiveProgressBar.swift */; };
C3D187F728075B6C008B739A /* riveslider.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3D187F628075B6C008B739A /* riveslider.riv */; };
C3D187F9280770EA008B739A /* truck.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3D187F8280770EA008B739A /* truck.riv */; };
@@ -153,10 +155,12 @@
C324DB5C280728690060589F /* RiveButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveButton.swift; sourceTree = "<group>"; };
C324DB5E280740FB0060589F /* rbutton.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = rbutton.riv; sourceTree = "<group>"; };
C3357CA0280F42EC00F03B6F /* ExamplesMaster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExamplesMaster.swift; sourceTree = "<group>"; };
C34609FD2800A6CE002DBCB7 /* bird.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = bird.riv; sourceTree = "<group>"; };
C3468E6127FDCBC6008652FD /* SimpleSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleSlider.swift; sourceTree = "<group>"; };
C3745FCB282AE2320087F4AF /* hero_editor.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = hero_editor.riv; sourceTree = "<group>"; };
C3C0747F283D582B00E8EB33 /* button.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = button.riv; sourceTree = "<group>"; };
C3C074A7283FC75800E8EB33 /* testanimation.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = testanimation.riv; sourceTree = "<group>"; };
C3C074AA283FC75900E8EB33 /* teststatemachine.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = teststatemachine.riv; sourceTree = "<group>"; };
C3C074ED28414F4600E8EB33 /* SwiftTestParityAnimSM.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTestParityAnimSM.swift; sourceTree = "<group>"; };
C3C074F1284161E400E8EB33 /* halloween.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = halloween.riv; sourceTree = "<group>"; };
C3D187F2280751A8008B739A /* RiveProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RiveProgressBar.swift; sourceTree = "<group>"; };
C3D187F628075B6C008B739A /* riveslider.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = riveslider.riv; sourceTree = "<group>"; };
C3D187F8280770EA008B739A /* truck.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = truck.riv; sourceTree = "<group>"; };
@@ -253,7 +257,9 @@
C9696B0E24FC6FD10041502A /* Assets */ = {
isa = PBXGroup;
children = (
C3C0747F283D582B00E8EB33 /* button.riv */,
C3C074F1284161E400E8EB33 /* halloween.riv */,
C3C074A7283FC75800E8EB33 /* testanimation.riv */,
C3C074AA283FC75900E8EB33 /* teststatemachine.riv */,
C3E2B5872833ECB500A8651B /* bullet_man_game.riv */,
C3745FCB282AE2320087F4AF /* hero_editor.riv */,
C3E383472837E6B00029D65E /* watch_v1.riv */,
@@ -264,7 +270,6 @@
27108F2C282C96E700A99D81 /* light_switch.riv */,
C3D187F628075B6C008B739A /* riveslider.riv */,
C324DB5E280740FB0060589F /* rbutton.riv */,
C34609FD2800A6CE002DBCB7 /* bird.riv */,
0450445E26B3F71E007B25CA /* constrained.riv */,
04D5B069266A460C004ACA5B /* nothing.riv */,
042C88D42644447500E7DBB2 /* artboard_animations.riv */,
@@ -311,6 +316,7 @@
E5CD7D7027DC331900BFE5E2 /* SwiftMeshAnimation.swift */,
C3ECAC262817BE4600A81123 /* SwiftTouchEvents.swift */,
C3E2B58B2833ECFE00A8651B /* SwiftCannonGame.swift */,
C3C074ED28414F4600E8EB33 /* SwiftTestParityAnimSM.swift */,
);
path = SwiftUI;
sourceTree = "<group>";
@@ -451,7 +457,6 @@
C324DB5F280740FB0060589F /* rbutton.riv in Resources */,
C9D3DE5B264F3B51001BA265 /* liquid.riv in Resources */,
042C88DF2644447500E7DBB2 /* mascot.riv in Resources */,
C3460A002800A6CE002DBCB7 /* bird.riv in Resources */,
C9BD3926263B5FC700696C37 /* truck_v7.riv in Resources */,
042C88DC2644447500E7DBB2 /* pull.riv in Resources */,
042C88E32644447500E7DBB2 /* flux_capacitor.riv in Resources */,
@@ -472,17 +477,19 @@
C3ECAC2C281837B300A81123 /* leg_day_events_example.riv in Resources */,
042C88E12644447500E7DBB2 /* basketball.riv in Resources */,
042C88DE2644447500E7DBB2 /* rope.riv in Resources */,
C3C074AC283FC75900E8EB33 /* teststatemachine.riv in Resources */,
C3ECAC2D281837B300A81123 /* switch_event_example.riv in Resources */,
0480028B2729AA4400F7132B /* clean_icon_set.riv in Resources */,
04F1C80B26A8442300CEE6BE /* two_bone_ik.riv in Resources */,
C3745FCE282AE2320087F4AF /* hero_editor.riv in Resources */,
C3C074F2284161E400E8EB33 /* halloween.riv in Resources */,
C3C074AB283FC75900E8EB33 /* testanimation.riv in Resources */,
C3D187F9280770EA008B739A /* truck.riv in Resources */,
C9C73EA124FC471E00EF9516 /* Preview Assets.xcassets in Resources */,
C3E2B58A2833ECB500A8651B /* bullet_man_game.riv in Resources */,
042C88DD2644447500E7DBB2 /* ui_swipe_left_to_delete.riv in Resources */,
042C88E52644447500E7DBB2 /* explorer.riv in Resources */,
042C88E22644447500E7DBB2 /* f22.riv in Resources */,
C3C07482283D582B00E8EB33 /* button.riv in Resources */,
046AFA712673AF04004ED497 /* blendmodes.riv in Resources */,
042C88EC2644447500E7DBB2 /* vader.riv in Resources */,
C9C73E9E24FC471E00EF9516 /* Assets.xcassets in Resources */,
@@ -512,6 +519,7 @@
E5CD7D7127DC331900BFE5E2 /* SwiftMeshAnimation.swift in Sources */,
042C88902644250D00E7DBB2 /* MultipleAnimations.swift in Sources */,
C3468E6227FDCBC6008652FD /* SimpleSlider.swift in Sources */,
C3C074EE28414F4600E8EB33 /* SwiftTestParityAnimSM.swift in Sources */,
C3357CA1280F42EC00F03B6F /* ExamplesMaster.swift in Sources */,
C324DB5628071EB80060589F /* RiveSwitch.swift in Sources */,
C3ECAC272817BE4600A81123 /* SwiftTouchEvents.swift in Sources */,

View File

@@ -14,11 +14,8 @@ class SimpleAnimationViewController: UIViewController {
var viewModel = RiveViewModel(fileName: "truck")
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let rview = RiveView()
view.addSubview(rview)
viewModel.setView(rview)
rview.frame = view.frame
let riveView = viewModel.createRiveView()
view.addSubview(riveView)
riveView.frame = view.frame
}
}

View File

@@ -12,56 +12,11 @@ import RiveRuntime
struct SwiftSimpleAnimation: DismissableView {
var dismiss: () -> Void = {}
var viewModel = RiveViewModel(fileName: "button", autoPlay: false)
var body: some View {
ZStack {
Color.gray
VStack {
viewModel.view()
HStack {
PlayerButton(title: "play") {
viewModel.play(animationName: "active")
}
PlayerButton(title: "pause") {
viewModel.pause()
}
PlayerButton(title: "stop") {
viewModel.stop()
}
PlayerButton(title: "backward.end") {
viewModel.reset()
}
}
}
.padding()
}
.ignoresSafeArea()
}
struct PlayerButton: View {
var title: String
var action: ()->Void
var body: some View {
Button {
action()
} label: {
ZStack {
Color.blue
Image(systemName: title + ".fill")
.foregroundColor(.white)
}
.aspectRatio(1, contentMode: .fit)
.cornerRadius(10)
.padding()
}
}
SwiftVMPlayer(
viewModels:
RiveViewModel(fileName: "halloween", autoPlay: false)
)
}
}

View File

@@ -0,0 +1,23 @@
//
// SwiftTestParityAnimSM.swift
// RiveExample
//
// Created by Zachary Duncan on 5/27/22.
// Copyright © 2022 Rive. All rights reserved.
//
import SwiftUI
import RiveRuntime
/// Test to check the difference in behavior between an Animation and an almost idential StateMachine
struct SwiftTestParityAnimSM: DismissableView {
var dismiss: () -> Void = {}
var body: some View {
SwiftVMPlayer(
viewModels:
RiveViewModel(fileName: "teststatemachine", stateMachineName: "State Machine 1", autoPlay: false),
RiveViewModel(fileName: "testanimation", autoPlay: false, animationName: "Move")
)
}
}

View File

@@ -27,6 +27,7 @@ class ExamplesMasterTableViewController: UITableViewController {
// MARK: SwiftUI View Examples
/// Made from custom `Views`
private lazy var swiftViews: [(String, AnyView)] = [
//("Test Anim/SM Parity", typeErased(dismissableView: SwiftTestParityAnimSM())),
("Touch Events!", typeErased(dismissableView: SwiftTouchEvents())),
("Widget Collection", typeErased(dismissableView: SwiftWidgets())),
("Animation Player", typeErased(dismissableView: SwiftSimpleAnimation())),

View File

@@ -6,7 +6,7 @@
// Copyright © 2021 Rive. All rights reserved.
//
import Foundation
import SwiftUI
import RiveRuntime
@available(*, deprecated, message: "Use method in RiveFile+Extensions instead")
@@ -27,3 +27,63 @@ func getRiveFile(resourceName: String, resourceExt: String=".riv") throws -> Riv
let byteArray = getBytes(resourceName: resourceName, resourceExt: resourceExt)
return try RiveFile(byteArray: byteArray)
}
struct SwiftVMPlayer: View {
let viewModels: [RiveViewModel]
init(viewModels: RiveViewModel...) {
self.viewModels = viewModels
}
var body: some View {
ZStack {
Color.gray
VStack {
ForEach(0 ..< viewModels.count) { i in
viewModels[i].view()
}
HStack {
PlayerButton(title: "play") {
viewModels.forEach { $0.play() }
}
PlayerButton(title: "pause") {
viewModels.forEach { $0.pause() }
}
PlayerButton(title: "stop") {
viewModels.forEach { $0.stop() }
}
PlayerButton(title: "backward.end") {
viewModels.forEach { $0.reset() }
}
}
}
.padding()
}
.ignoresSafeArea()
}
struct PlayerButton: View {
var title: String
var action: ()->Void
var body: some View {
Button {
action()
} label: {
ZStack {
Color.blue
Image(systemName: title + ".fill")
.foregroundColor(.white)
}
.aspectRatio(1, contentMode: .fit)
.cornerRadius(10)
.padding()
}
}
}
}

View File

@@ -29,7 +29,6 @@ open class RiveModel: ObservableObject {
// MARK: - Setters
open func setArtboard(_ name: String) throws {
guard artboard?.name() != name else { return }
do { artboard = try riveFile.artboard(fromName: name) }
catch { throw RiveModelError.invalidArtboard(name: name) }
}
@@ -46,7 +45,6 @@ open class RiveModel: ObservableObject {
}
open func setStateMachine(_ name: String) throws {
guard stateMachine?.name() != name else { return }
do { stateMachine = try artboard.stateMachine(fromName: name) }
catch { throw RiveModelError.invalidStateMachine(name: name) }
}

View File

@@ -95,14 +95,10 @@ open class RiveView: RiveRendererView {
/// Asks the render loop to stop on the next cycle
internal func stop() {
playerDelegate?.player(stoppedWithModel: self.riveModel)
lastTime = 0
playerDelegate?.player(stoppedWithModel: riveModel)
isPlaying = false
if !isPlaying {
advance(delta: 0)
} else {
isPlaying = false
}
reset()
}
internal func reset() {

View File

@@ -9,10 +9,12 @@
import SwiftUI
import Combine
/// An object used for controlling a RiveView. For most common Rive files you should only need to interact with a `RiveViewModel` object.
/// An object used for controlling a RiveView. For most common Rive files you should only need
/// to interact with a `RiveViewModel` object.
///
/// - Usage:
/// - You should initialize with either an Animation name or a StateMachine name, but not both. Only one will be used and if both are given the StateMachine will be used.
/// - You should initialize with either an Animation name or a StateMachine name, but not both.
/// Only one will be used and if both are given the StateMachine will be used.
/// - Default StateMachine or Animation from the file can be used by leaving their parameters nil
/// - Examples:
///
@@ -56,7 +58,7 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
super.init()
try! configureModel(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
sharedInit(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
}
public init(
@@ -75,7 +77,7 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
super.init()
try! configureModel(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
sharedInit(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
}
public init(
@@ -97,6 +99,18 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
defaultModel = RiveModelBuffer(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
}
private func sharedInit(artboardName: String?, stateMachineName: String?, animationName: String?) {
try! configureModel(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
defaultModel = RiveModelBuffer(
artboardName: artboardName,
stateMachineName: stateMachineName,
animationName: animationName
)
try! riveView?.setModel(riveModel!, autoPlay: autoPlay)
}
// MARK: - RiveView
open private(set) var riveModel: RiveModel? {
@@ -152,33 +166,23 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
riveView?.play()
}
/// Haults the active Animation or StateMachine and will resume from it's current position when next played
/// Halts the active Animation or StateMachine and will resume from it's current position when next played
open func pause() {
riveView?.pause()
}
/// Haults the active Animation or StateMachine and will restart from the beginning when next played
/// Halts the active Animation or StateMachine and sets it at its starting position
open func stop() {
resetScene()
resetCurrentModel()
riveView?.stop()
}
/// Sets the active Animation or StateMachine back to their starting position
open func reset() {
resetScene()
resetCurrentModel()
riveView?.reset()
}
private func resetScene() {
if let stateMachine = riveModel?.stateMachine {
// StateMachine can't reset itself so we reload from the Artboard
try! riveModel!.setStateMachine(stateMachine.name())
}
else if let animation = riveModel?.animation {
animation.setTime(0)
}
}
// MARK: - RiveModel
/// Instantiates elements in the model needed to play in a `RiveView`
@@ -206,13 +210,21 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
// Set default Animation
try riveModel?.setAnimation()
}
try riveView?.setModel(riveModel!, autoPlay: autoPlay)
defaultModel = RiveModelBuffer(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
}
private func resetModelToDefault() throws {
try configureModel(
/// Puts the active Animation or StateMachine back to their starting position
private func resetCurrentModel() {
guard let model = riveModel else { fatalError("Current model is nil") }
try! configureModel(
artboardName: model.artboard.name(),
stateMachineName: model.stateMachine?.name(),
animationName: model.animation?.name()
)
}
/// Sets the Artboard, StateMachine or Animation back to the first one given to the RiveViewModel
open func resetToDefaultModel() {
try! configureModel(
artboardName: defaultModel.artboardName,
stateMachineName: defaultModel.stateMachineName,
animationName: defaultModel.animationName
@@ -220,7 +232,7 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
}
open func triggerInput(_ inputName: String) throws {
riveModel?.stateMachine?.getTrigger(inputName)
riveModel?.stateMachine?.getTrigger(inputName).fire()
play()
}
@@ -324,7 +336,7 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
public func riveFileDidLoad(_ riveFile: RiveFile) throws {
riveModel = RiveModel(riveFile: riveFile)
try! configureModel(
sharedInit(
artboardName: defaultModel.artboardName,
stateMachineName: defaultModel.stateMachineName,
animationName: defaultModel.animationName

View File

@@ -267,70 +267,70 @@ class DelegatesTest: XCTestCase {
}
func testStateMachineLayerStatesComplex() throws {
// let delegate = DrDelegate()
// let file = try RiveFile(testfileName: "what_a_state")
// let model = RiveModel(riveFile: file)
// let viewModel = RiveViewModel(model, stateMachineName: "State Machine 1", autoPlay: true)
// let view = viewModel.createRiveView()
//
// view.stateMachineDelegate = delegate
// view.advance(delta:0.0)
// XCTAssertEqual(delegate.stateMachineStates.count, 0)
// viewModel.play()
// // MARK: Input
// // lets just start, expect 1 change.
// try viewModel.triggerInput("right")
// // TODO: looks like we got a bit of a bug here
// // If we do not call this advance, the first animation doesnt seem to get the delta applied.
// view.advance(delta:0.0)
// view.advance(delta:0.4)
// XCTAssertEqual(delegate.stateMachineStates.count, 1)
// XCTAssertEqual(delegate.stateMachineStates[0], "go right")
// XCTAssertEqual(delegate.stateMachineNames.count, 1)
// XCTAssertEqual(delegate.stateMachineNames[0], "State Machine 1")
// delegate.stateMachineStates.removeAll()
//
// // should be in same animation still. no state change
// view.advance(delta:0.4)
// XCTAssertEqual(0, delegate.stateMachineStates.count)
// XCTAssertEqual(true, viewModel.isPlaying)
//
// // animation came to an end inside this time period, this still means no state change
// view.advance(delta:0.4)
// XCTAssertEqual(false, viewModel.isPlaying)
// XCTAssertEqual(0, delegate.stateMachineStates.count)
//
// // animation is just kinda stuck there. no change no happening.
// view.advance(delta:0.4)
// XCTAssertEqual(false, viewModel.isPlaying)
// XCTAssertEqual(0, delegate.stateMachineStates.count)
//
// // MARK: Input
// // ok lets change thigns up again.
// try viewModel.triggerInput("change")
// view.advance(delta:0.0)
// view.advance(delta:0.4)
// XCTAssertEqual(true, viewModel.isPlaying)
// XCTAssertEqual(1, delegate.stateMachineStates.count)
//
// XCTAssertEqual("change!", delegate.stateMachineStates[0])
// delegate.stateMachineStates.removeAll()
//
// // as before lets advance inside the animation -> no change
// view.advance(delta:0.4)
// XCTAssertEqual(true, viewModel.isPlaying)
// XCTAssertEqual(0, delegate.stateMachineStates.count)
//
// // as before lets advance beyond the end of the animaiton, in this case change to exit!
// view.advance(delta:0.4)
// XCTAssertEqual(false, viewModel.isPlaying)
// XCTAssertEqual(1, delegate.stateMachineStates.count)
// XCTAssertEqual("ExitState", delegate.stateMachineStates[0])
// delegate.stateMachineStates.removeAll()
//
// // chill on exit. no change.
// view.advance(delta:0.4)
// XCTAssertEqual(false, viewModel.isPlaying)
// XCTAssertEqual(0, delegate.stateMachineStates.count)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "what_a_state")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, stateMachineName: "State Machine 1", autoPlay: true)
let view = viewModel.createRiveView()
view.stateMachineDelegate = delegate
view.advance(delta:0.0)
XCTAssertEqual(delegate.stateMachineStates.count, 0)
viewModel.play()
// MARK: Input
// lets just start, expect 1 change.
try viewModel.triggerInput("right")
// TODO: looks like we got a bit of a bug here
// If we do not call this advance, the first animation doesnt seem to get the delta applied.
view.advance(delta:0.0)
view.advance(delta:0.4)
XCTAssertEqual(delegate.stateMachineStates.count, 1)
XCTAssertEqual(delegate.stateMachineStates[0], "go right")
XCTAssertEqual(delegate.stateMachineNames.count, 1)
XCTAssertEqual(delegate.stateMachineNames[0], "State Machine 1")
delegate.stateMachineStates.removeAll()
// should be in same animation still. no state change
view.advance(delta:0.4)
XCTAssertEqual(0, delegate.stateMachineStates.count)
XCTAssertEqual(true, viewModel.isPlaying)
// animation came to an end inside this time period, this still means no state change
view.advance(delta:0.4)
XCTAssertEqual(false, viewModel.isPlaying)
XCTAssertEqual(0, delegate.stateMachineStates.count)
// animation is just kinda stuck there. no change no happening.
view.advance(delta:0.4)
XCTAssertEqual(false, viewModel.isPlaying)
XCTAssertEqual(0, delegate.stateMachineStates.count)
// MARK: Input
// ok lets change thigns up again.
try viewModel.triggerInput("change")
view.advance(delta:0.0)
view.advance(delta:0.4)
XCTAssertEqual(true, viewModel.isPlaying)
XCTAssertEqual(1, delegate.stateMachineStates.count)
XCTAssertEqual("change!", delegate.stateMachineStates[0])
delegate.stateMachineStates.removeAll()
// as before lets advance inside the animation -> no change
view.advance(delta:0.4)
XCTAssertEqual(true, viewModel.isPlaying)
XCTAssertEqual(0, delegate.stateMachineStates.count)
// as before lets advance beyond the end of the animaiton, in this case change to exit!
view.advance(delta:0.4)
XCTAssertEqual(false, viewModel.isPlaying)
XCTAssertEqual(1, delegate.stateMachineStates.count)
XCTAssertEqual("ExitState", delegate.stateMachineStates[0])
delegate.stateMachineStates.removeAll()
// chill on exit. no change.
view.advance(delta:0.4)
XCTAssertEqual(false, viewModel.isPlaying)
XCTAssertEqual(0, delegate.stateMachineStates.count)
}
}