Made some runtime refinements. Updated tests to use new simplified runtime.

This commit is contained in:
Zachary Duncan
2022-05-18 01:21:09 -04:00
committed by Zachary Duncan
parent 971c180926
commit e5f1b24941
10 changed files with 391 additions and 363 deletions

Binary file not shown.

View File

@@ -10,7 +10,6 @@
04026DC427CE3ED6002B3DBF /* SwiftSimpleAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04026DC327CE3ED6002B3DBF /* SwiftSimpleAnimation.swift */; };
04026DC827CE3EE6002B3DBF /* SwiftLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04026DC727CE3EE6002B3DBF /* SwiftLayout.swift */; };
04026DCA27CE3EF6002B3DBF /* SwiftMultipleAnimations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04026DC927CE3EF6002B3DBF /* SwiftMultipleAnimations.swift */; };
04026DCC27CE3F03002B3DBF /* SwiftLoopMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04026DCB27CE3F03002B3DBF /* SwiftLoopMode.swift */; };
04026DCE27CE3F0F002B3DBF /* SwiftStateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04026DCD27CE3F0F002B3DBF /* SwiftStateMachine.swift */; };
042C88832643D6B900E7DBB2 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 042C88822643D6B900E7DBB2 /* Main.storyboard */; };
042C88882643DB7100E7DBB2 /* SimpleAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 042C88872643DB7100E7DBB2 /* SimpleAnimation.swift */; };
@@ -56,6 +55,8 @@
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 */; };
C3E2B58A2833ECB500A8651B /* bullet_man_game.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3E2B5872833ECB500A8651B /* bullet_man_game.riv */; };
C3E2B58C2833ECFE00A8651B /* SwiftCannonGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E2B58B2833ECFE00A8651B /* SwiftCannonGame.swift */; };
C3ECAC252817BE1100A81123 /* magic_8-ball_v2.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3ECAC222817BE1100A81123 /* magic_8-ball_v2.riv */; };
C3ECAC272817BE4600A81123 /* SwiftTouchEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3ECAC262817BE4600A81123 /* SwiftTouchEvents.swift */; };
C3ECAC2B281837B300A81123 /* play_button_event_example.riv in Resources */ = {isa = PBXBuildFile; fileRef = C3ECAC28281837B300A81123 /* play_button_event_example.riv */; };
@@ -113,7 +114,6 @@
04026DC327CE3ED6002B3DBF /* SwiftSimpleAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftSimpleAnimation.swift; sourceTree = "<group>"; };
04026DC727CE3EE6002B3DBF /* SwiftLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLayout.swift; sourceTree = "<group>"; };
04026DC927CE3EF6002B3DBF /* SwiftMultipleAnimations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftMultipleAnimations.swift; sourceTree = "<group>"; };
04026DCB27CE3F03002B3DBF /* SwiftLoopMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftLoopMode.swift; sourceTree = "<group>"; };
04026DCD27CE3F0F002B3DBF /* SwiftStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftStateMachine.swift; sourceTree = "<group>"; };
042C88822643D6B900E7DBB2 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = "<group>"; };
042C88872643DB7100E7DBB2 /* SimpleAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleAnimation.swift; sourceTree = "<group>"; };
@@ -158,6 +158,8 @@
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>"; };
C3E2B5872833ECB500A8651B /* bullet_man_game.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = bullet_man_game.riv; sourceTree = "<group>"; };
C3E2B58B2833ECFE00A8651B /* SwiftCannonGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftCannonGame.swift; sourceTree = "<group>"; };
C3ECAC222817BE1100A81123 /* magic_8-ball_v2.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = "magic_8-ball_v2.riv"; sourceTree = "<group>"; };
C3ECAC262817BE4600A81123 /* SwiftTouchEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftTouchEvents.swift; sourceTree = "<group>"; };
C3ECAC28281837B300A81123 /* play_button_event_example.riv */ = {isa = PBXFileReference; lastKnownFileType = file; path = play_button_event_example.riv; sourceTree = "<group>"; };
@@ -249,6 +251,7 @@
C9696B0E24FC6FD10041502A /* Assets */ = {
isa = PBXGroup;
children = (
C3E2B5872833ECB500A8651B /* bullet_man_game.riv */,
C3745FCB282AE2320087F4AF /* hero_editor.riv */,
C3ECAC30281840EF00A81123 /* watch_v1.riv */,
C3ECAC29281837B300A81123 /* leg_day_events_example.riv */,
@@ -301,10 +304,10 @@
C9CB2F12264C92D200E7FF0D /* SwiftWidgets.swift */,
04026DC727CE3EE6002B3DBF /* SwiftLayout.swift */,
04026DC927CE3EF6002B3DBF /* SwiftMultipleAnimations.swift */,
04026DCB27CE3F03002B3DBF /* SwiftLoopMode.swift */,
04026DCD27CE3F0F002B3DBF /* SwiftStateMachine.swift */,
E5CD7D7027DC331900BFE5E2 /* SwiftMeshAnimation.swift */,
C3ECAC262817BE4600A81123 /* SwiftTouchEvents.swift */,
C3E2B58B2833ECFE00A8651B /* SwiftCannonGame.swift */,
);
path = SwiftUI;
sourceTree = "<group>";
@@ -473,6 +476,7 @@
C3745FCE282AE2320087F4AF /* hero_editor.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 */,
@@ -497,6 +501,7 @@
files = (
C324DB5D280728690060589F /* RiveButton.swift in Sources */,
C9C73E9824FC471E00EF9516 /* AppDelegate.swift in Sources */,
C3E2B58C2833ECFE00A8651B /* SwiftCannonGame.swift in Sources */,
C9CB2F13264C92D200E7FF0D /* SwiftWidgets.swift in Sources */,
042C888E2644230700E7DBB2 /* utility.swift in Sources */,
C3D187F3280751A8008B739A /* RiveProgressBar.swift in Sources */,
@@ -508,7 +513,6 @@
C3ECAC272817BE4600A81123 /* SwiftTouchEvents.swift in Sources */,
C3ECAC2F281840A300A81123 /* ClockViewModel.swift in Sources */,
04026DC827CE3EE6002B3DBF /* SwiftLayout.swift in Sources */,
04026DCC27CE3F03002B3DBF /* SwiftLoopMode.swift in Sources */,
C9C73E9A24FC471E00EF9516 /* SceneDelegate.swift in Sources */,
04C4C83E2646FE410047E614 /* StateMachine.swift in Sources */,
C324DB5B2807216B0060589F /* RiveSlider.swift in Sources */,

View File

@@ -0,0 +1,18 @@
//
// SwiftCannonGame.swift
// RiveExample
//
// Created by Zachary Duncan on 5/17/22.
// Copyright © 2022 Rive. All rights reserved.
//
import SwiftUI
import RiveRuntime
struct SwiftCannonGame: DismissableView {
var dismiss: () -> Void = {}
var body: some View {
RiveViewModel(fileName: "bullet_man_game", stateMachineName: "State Machine 1").view()
}
}

View File

@@ -1,87 +0,0 @@
////
//// LoopMode.swift
//// RiveExample
////
//// Created by Maxwell Talbot on 01/03/2022.
//// Copyright © 2022 Rive. All rights reserved.
////
//
//import SwiftUI
//import RiveRuntime
//
//struct SwiftLoopMode: DismissableView {
// var dismiss: () -> Void = {}
//
// var loopy = RiveViewModel(fileName: "loopy", autoplay: false)
//// var direction = Direction.directionAuto
//
// var body: some View {
// ScrollView {
// VStack {
//// RiveViewSwift(
//// resource: "loopy", autoplay: false, controller:controller
//// )
//
// loopy.view()
// .frame(height:300)
// HStack {
// Button("Reset") {
// try? loopy.reset()
// }
//
//// TODO: work out direction controls
//// Button("Forwards", action:{direction = .directionForwards})
//// Button("Auto", action:{direction = .directionAuto})
//// Button("Backwards", action:{direction = .directionBackwards})
// }
//
// }
// HStack {
// Text("Oneshot")
// Button("Play") {
// try? loopy.play(animationName: "oneshot")
// }
// Button("OneShot") {
// try? loopy.play(animationName: "oneshot", loop: .loopOneShot)
// }
// Button("Loop") {
// try? loopy.play(animationName: "oneshot", loop: .loopLoop)
// }
// Button("PingPong") {
// try? loopy.play(animationName: "oneshot", loop: .loopPingPong)
// }
// }
// HStack {
// Text("Loop")
// Button("Play") {
// try? loopy.play(animationName: "loop")
// }
// Button("OneShot") {
// try? loopy.play(animationName: "loop", loop: .loopOneShot)
// }
// Button("Loop") {
// try? loopy.play(animationName: "loop", loop: .loopLoop)
// }
// Button("PingPong") {
// try? loopy.play(animationName: "loop", loop: .loopPingPong)
// }
// }
// HStack {
// Text("Pingpong")
// Button("Play") {
// try? loopy.play(animationName: "pingpong")
// }
// Button("OneShot") {
// try? loopy.play(animationName: "pingpong", loop: .loopOneShot)
// }
// Button("Loop") {
// try? loopy.play(animationName: "pingpong", loop: .loopLoop)
// }
// Button("PingPong") {
// try? loopy.play(animationName: "pingpong", loop: .loopPingPong)
// }
// }
// }
// }
//}
//

View File

@@ -12,10 +12,45 @@ import RiveRuntime
struct SwiftSimpleAnimation: DismissableView {
var dismiss: () -> Void = {}
var viewModel = RiveViewModel(fileName: "truck", autoPlay: false)
var body: some View {
RiveViewModel(fileName: "truck").view()
viewModel.view()
HStack {
PlayerButton(title: "▶︎") {
viewModel.play()
}
PlayerButton(title: " ▍▍") {
viewModel.pause()
}
PlayerButton(title: "◼︎") {
viewModel.stop()
}
}
.padding()
}
struct PlayerButton: View {
var title: String
var action: ()->Void
var body: some View {
Button {
action()
} label: {
ZStack {
Color.blue
Text(title)
.foregroundColor(.white)
}
.aspectRatio(1, contentMode: .fit)
.cornerRadius(10)
.padding()
}
}
}
}

View File

@@ -29,10 +29,10 @@ class ExamplesMasterTableViewController: UITableViewController {
private lazy var swiftViews: [(String, AnyView)] = [
("Touch Events!", typeErased(dismissableView: SwiftTouchEvents())),
("Widget Collection", typeErased(dismissableView: SwiftWidgets())),
("Simple Animation", typeErased(dismissableView: SwiftSimpleAnimation())),
("Animation Player", typeErased(dismissableView: SwiftSimpleAnimation())),
("Layout", typeErased(dismissableView: SwiftLayout())),
("MultipleAnimations", typeErased(dismissableView: SwiftMultipleAnimations())),
//("Loop Mode", typeErased(dismissableView: SwiftLoopMode())),
("Cannon Game", typeErased(dismissableView: SwiftCannonGame())),
("State Machine", typeErased(dismissableView: SwiftStateMachine())),
("Mesh Animation", typeErased(dismissableView: SwiftMeshAnimation()))
]

View File

@@ -11,8 +11,8 @@ import Foundation
open class RiveModel: ObservableObject {
internal private(set) var riveFile: RiveFile
public private(set) var artboard: RiveArtboard!
public private(set) var stateMachine: RiveStateMachineInstance?
public private(set) var animation: RiveLinearAnimationInstance?
public internal(set) var stateMachine: RiveStateMachineInstance?
public internal(set) var animation: RiveLinearAnimationInstance?
public init(riveFile: RiveFile) {
self.riveFile = riveFile

View File

@@ -22,17 +22,19 @@ open class RiveView: RiveRendererView {
private var eventQueue = EventQueue()
// MARK: Delegates
internal var playerDelegate: RivePlayerDelegate?
internal var stateMachineDelegate: RiveStateMachineDelegate?
public var playerDelegate: RivePlayerDelegate?
public var stateMachineDelegate: RiveStateMachineDelegate?
// MARK: Debug
private var fpsCounter: FPSCounterView? = nil
public var showFPS: Bool = false {
didSet {
if showFPS {
if showFPS && fpsCounter == nil {
fpsCounter = FPSCounterView()
addSubview(fpsCounter!)
} else {
}
if !showFPS {
fpsCounter?.removeFromSuperview()
fpsCounter = nil
}
@@ -55,8 +57,9 @@ open class RiveView: RiveRendererView {
/// This resets the view with the new model. Useful when the `RiveView` was initialized without one.
open func setModel(_ model: RiveModel, autoPlay: Bool = true) throws {
stop()
self.riveModel = model
stopTimer()
isPlaying = false
riveModel = model
isOpaque = false
if autoPlay {
@@ -64,10 +67,13 @@ open class RiveView: RiveRendererView {
} else {
advance(delta: 0)
}
showFPS = true
}
// MARK: - Controls
/// Starts the render loop
internal func play(loop: Loop = .loopAuto, direction: Direction = .directionAuto) {
eventQueue.add {
self.playerDelegate?.player(playedWithModel: self.riveModel)
@@ -89,20 +95,20 @@ open class RiveView: RiveRendererView {
startTimer()
}
/// Asks the render loop to stop on the next cycle
internal func pause() {
eventQueue.add {
self.playerDelegate?.player(pausedWithModel: self.riveModel)
if isPlaying {
eventQueue.add {
self.playerDelegate?.player(pausedWithModel: self.riveModel)
}
isPlaying = false
}
isPlaying = false
stopTimer()
}
/// Asks the render loop to stop on the next cycle
internal func stop() {
playerDelegate?.player(stoppedWithModel: riveModel)
self.playerDelegate?.player(stoppedWithModel: self.riveModel)
isPlaying = false
stopTimer()
lastTime = 0
}
// MARK: - Render Loop
@@ -126,13 +132,14 @@ open class RiveView: RiveRendererView {
displayLinkProxy?.invalidate()
displayLinkProxy = nil
lastTime = 0
fpsCounter?.stopped()
}
/// Start a redraw:
/// - determine the elapsed time
/// - advance the artbaord, which will invalidate the display.
/// - if the artboard has come to a stop, stop.
@objc func tick() {
@objc fileprivate func tick() {
guard let displayLink = displayLinkProxy?.displayLink else {
stopTimer()
return
@@ -146,6 +153,7 @@ open class RiveView: RiveRendererView {
// Calculate the time elapsed between ticks
let elapsedTime = timestamp - lastTime
fpsCounter?.elapsed(time: elapsedTime)
lastTime = timestamp
advance(delta: elapsedTime)
if !isPlaying {
@@ -158,29 +166,37 @@ open class RiveView: RiveRendererView {
///
/// - Parameter delta: elapsed seconds since the last advance
@objc open func advance(delta: Double) {
let wasPlaying = isPlaying
eventQueue.fireAll()
if let stateMachine = riveModel.stateMachine {
isPlaying = stateMachine.advance(by: delta) && isPlaying
stateMachine.stateChanges().forEach { stateMachineDelegate?.stateMachine?(stateMachine, didChangeState: $0) }
isPlaying = stateMachine.advance(by: delta) && wasPlaying
if !isPlaying {
pause()
for stateChange in stateMachine.stateChanges() {
stateMachineDelegate?.stateMachine?(stateMachine, didChangeState: stateChange)
}
// stateMachine.stateChanges().forEach { stateMachineDelegate?.stateMachine?(stateMachine, didChangeState: $0) }
}
else if let animation = riveModel.animation {
isPlaying = animation.advance(by: delta) && isPlaying
isPlaying = animation.advance(by: delta) && wasPlaying
animation.apply()
if !isPlaying {
pause()
} else {
if isPlaying {
if animation.didLoop() {
playerDelegate?.player(loopedWithModel: riveModel, type: Int(animation.loop()))
}
}
}
if !isPlaying {
stopTimer()
// This will be true when coming to a hault automatically
if wasPlaying {
playerDelegate?.player(pausedWithModel: riveModel)
}
}
// advance the artboard
riveModel.artboard.advance(by: delta)
playerDelegate?.player(didAdvanceby: delta, riveModel: riveModel)

View File

@@ -11,18 +11,25 @@ import Combine
open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStateMachineDelegate, RivePlayerDelegate {
open private(set) var riveView: RiveView?
private var asyncModelBuffer: AsyncModelBuffer? = nil
private var defaultModel: RiveModelBuffer!
public init(
_ model: RiveModel,
stateMachineName: String? = nil,
fit: RiveRuntime.Fit = .fitContain,
alignment: RiveRuntime.Alignment = .alignmentCenter,
autoPlay: Bool = true
autoPlay: Bool = true,
artboardName: String? = nil,
animationName: String? = nil
) {
self.riveModel = model
self.fit = fit
self.alignment = alignment
self.autoPlay = autoPlay
super.init()
try! configureModel(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
}
public init(
@@ -53,13 +60,14 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
artboardName: String? = nil,
animationName: String? = nil
) {
super.init()
riveModel = RiveModel(webURL: webURL, delegate: self)
self.fit = fit
self.alignment = alignment
self.autoPlay = autoPlay
asyncModelBuffer = AsyncModelBuffer(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
super.init()
riveModel = RiveModel(webURL: webURL, delegate: self)
defaultModel = RiveModelBuffer(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
}
// MARK: - RiveView
@@ -74,7 +82,7 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
open var isPlaying: Bool { riveView?.isPlaying ?? false }
open var autoPlay: Bool = true
open var autoPlay: Bool
open var fit: RiveRuntime.Fit = .fitContain {
didSet { riveView?.fit = fit }
@@ -101,6 +109,7 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
open func stop() {
riveView?.stop()
try! resetModelToDefault()
}
@available(*, deprecated, renamed: "stop")
@@ -115,10 +124,16 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
if let name = artboardName {
try riveModel?.setArtboard(name)
} else {
// Set default Artboard
try riveModel?.setArtboard()
// Keep current Artboard if there is one
if riveModel?.artboard == nil {
// Set default Artboard if not
try riveModel?.setArtboard()
}
}
riveModel?.animation = nil
riveModel?.stateMachine = nil
if let name = stateMachineName {
try riveModel?.setStateMachine(name)
}
@@ -131,6 +146,15 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
}
try riveView?.setModel(riveModel!, autoPlay: autoPlay)
defaultModel = RiveModelBuffer(artboardName: artboardName, stateMachineName: stateMachineName, animationName: animationName)
}
private func resetModelToDefault() throws {
try configureModel(
artboardName: defaultModel.artboardName,
stateMachineName: defaultModel.stateMachineName,
animationName: defaultModel.animationName
)
}
open func triggerInput(_ inputName: String) throws {
@@ -153,38 +177,7 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
play()
}
// MARK: - RiveFile Delegate
/// Needed for when the RiveViewModel is initialized with a webURL so we can make a RiveModel
/// when the RiveFile is finished downloading
private struct AsyncModelBuffer {
var artboardName: String?
var stateMachineName: String?
var animationName: String?
}
/// Called by RiveFile when it finishes downloading an asset asynchronously
public func riveFileDidLoad(_ riveFile: RiveFile) throws {
riveModel = RiveModel(riveFile: riveFile)
try! configureModel(
artboardName: asyncModelBuffer?.artboardName,
stateMachineName: asyncModelBuffer?.stateMachineName,
animationName: asyncModelBuffer?.animationName
)
asyncModelBuffer = nil
}
// MARK: - RivePlayer Delegate
open func player(playedWithModel riveModel: RiveModel?) { }
open func player(pausedWithModel riveModel: RiveModel?) { }
open func player(loopedWithModel riveModel: RiveModel?, type: Int) { }
open func player(stoppedWithModel riveModel: RiveModel?) { }
open func player(didAdvanceby seconds: Double, riveModel: RiveModel?) { }
// MARK: SwiftUI Helpers
// MARK: - SwiftUI Helpers
/// Makes a new `RiveView` for the instance property with data from model which will
/// replace any previous `RiveView`. This is called when first drawing a `StandardView`.
@@ -193,7 +186,7 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
let view: RiveView
if let model = riveModel {
view = RiveView(model: model)
view = RiveView(model: model, autoPlay: autoPlay)
} else {
view = RiveView()
}
@@ -244,7 +237,7 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
}
}
// MARK: UIKit Helper
// MARK: - UIKit Helper
/// This can be used to connect with and configure an `RiveView` that was created elsewhere.
/// Does not need to be called when updating an already configured `RiveView`. Useful for
@@ -254,6 +247,35 @@ open class RiveViewModel: NSObject, ObservableObject, RiveFileDelegate, RiveStat
registerView(view)
try! riveView!.setModel(riveModel!, autoPlay: autoPlay)
}
// MARK: - RiveFile Delegate
/// Needed for when resetting to defaults or the RiveViewModel is initialized with a webURL so
/// we are then able make a RiveModel when the RiveFile is finished downloading
private struct RiveModelBuffer {
var artboardName: String?
var stateMachineName: String?
var animationName: String?
}
/// Called by RiveFile when it finishes downloading an asset asynchronously
public func riveFileDidLoad(_ riveFile: RiveFile) throws {
riveModel = RiveModel(riveFile: riveFile)
try! configureModel(
artboardName: defaultModel.artboardName,
stateMachineName: defaultModel.stateMachineName,
animationName: defaultModel.animationName
)
}
// MARK: - RivePlayer Delegate
open func player(playedWithModel riveModel: RiveModel?) { }
open func player(pausedWithModel riveModel: RiveModel?) { }
open func player(loopedWithModel riveModel: RiveModel?, type: Int) { }
open func player(stoppedWithModel riveModel: RiveModel?) { }
open func player(didAdvanceby seconds: Double, riveModel: RiveModel?) { }
}
/// This makes a SwiftUI digestable view from an `RiveViewModel` and its `RiveView`

View File

@@ -9,25 +9,26 @@
import XCTest
import RiveRuntime
func getBytes(resourceName: String, resourceExt: String=".riv") -> [UInt8] {
guard let url = Bundle(for: DelegatesTest.self).url(forResource: resourceName, withExtension: resourceExt) else {
fatalError("Failed to locate \(resourceName) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(url) from bundle.")
extension RiveFile {
convenience init(testfileName: String, extension ext: String = ".riv") throws {
let byteArray = RiveFile.getBytes(fileName: testfileName, extension: ext)
try self.init(byteArray: byteArray)
}
// Import the data into a RiveFile
return [UInt8](data)
static func getBytes(fileName: String, extension ext: String = ".riv") -> [UInt8] {
guard let url = Bundle(for: DelegatesTest.self).url(forResource: fileName, withExtension: ext) else {
fatalError("Failed to locate \(fileName) in bundle.")
}
guard let data = try? Data(contentsOf: url) else {
fatalError("Failed to load \(url) from bundle.")
}
// Import the data into a RiveFile
return [UInt8](data)
}
}
func getRiveFile(resourceName: String, resourceExt: String=".riv") throws -> RiveFile {
let byteArray = getBytes(resourceName: resourceName, resourceExt: resourceExt)
let riveFile = try RiveFile(byteArray: byteArray)
return riveFile
}
class MrDelegate: RivePlayerDelegate, RStateDelegate {
class DrDelegate: RivePlayerDelegate, RiveStateMachineDelegate {
var stateMachinePlays = [String]()
var stateMachinePauses = [String]()
var stateMachineStops = [String]()
@@ -38,167 +39,183 @@ class MrDelegate: RivePlayerDelegate, RStateDelegate {
var stateMachineNames = [String]()
var stateMachineStates = [String]()
func loop(animation animationName: String, type: Int) {
loops.append(animationName)
}
func play(animation animationName: String, isStateMachine: Bool) {
if (isStateMachine){
stateMachinePlays.append(animationName)
func player(playedWithModel riveModel: RiveModel?) {
if let stateMachineName = riveModel?.stateMachine?.name() {
stateMachinePlays.append(stateMachineName)
}
else {
else if let animationName = riveModel?.animation?.name() {
linearAnimaitonPlays.append(animationName)
}
}
func pause(animation animationName: String, isStateMachine: Bool) {
if (isStateMachine){
stateMachinePauses.append(animationName)
func player(pausedWithModel riveModel: RiveModel?) {
if let stateMachineName = riveModel?.stateMachine?.name() {
stateMachinePauses.append(stateMachineName)
}
else {
else if let animationName = riveModel?.animation?.name() {
linearAnimaitonPauses.append(animationName)
}
}
func stop(animation animationName: String, isStateMachine: Bool) {
if (isStateMachine){
stateMachineStops.append(animationName)
func player(loopedWithModel riveModel: RiveModel?, type: Int) {
if let stateMachineName = riveModel?.stateMachine?.name() {
loops.append(stateMachineName)
}
else {
else if let animationName = riveModel?.animation?.name() {
loops.append(animationName)
}
}
func player(stoppedWithModel riveModel: RiveModel?) {
if let stateMachineName = riveModel?.stateMachine?.name() {
stateMachineStops.append(stateMachineName)
}
else if let animationName = riveModel?.animation?.name() {
linearAnimaitonStops.append(animationName)
}
}
func stateChange(_ stateMachineName:String, _ stateName: String) {
stateMachineNames.append(stateMachineName)
func player(didAdvanceby seconds: Double, riveModel: RiveModel?) { }
func stateMachine(_ stateMachine: RiveStateMachineInstance, didChangeState stateName: String) {
stateMachineNames.append(stateMachine.name())
stateMachineStates.append(stateName)
}
}
// Technical Note:
// We manually call view.advance(0) in these tests because the results are based on
// messages received by a delegate which is triggered by a timer. We can't wait for
// the timer so we trigger it manually
class DelegatesTest: XCTestCase {
func testPlay() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
try view.play(animationName: "one")
view.advance(delta:0)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
view.playerDelegate = delegate
viewModel.play(animationName: "one")
// This is necessary because play starts a timer that will eventually get to
// .advance(n) which triggers the event queue which sends word to the delegate...
// But the assert happens too fast, so we need to advance manually beforehand.
view.advance(delta: 0)
XCTAssertEqual(delegate.linearAnimaitonPlays.count, 1)
}
func testPlayTwice() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
try view.play(animationName: "one")
try view.play(animationName: "one")
view.advance(delta:0)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
view.playerDelegate = delegate
viewModel.play(animationName: "one")
viewModel.play(animationName: "one")
view.advance(delta: 0)
XCTAssertEqual(delegate.linearAnimaitonPlays.count, 2)
}
func testPause() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
try view.play(animationName: "one")
view.pause(animationName: "one")
view.advance(delta:0)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
view.playerDelegate = delegate
viewModel.play(animationName: "one")
viewModel.pause()
view.advance(delta: 0)
XCTAssertEqual(delegate.linearAnimaitonPauses.count, 1)
}
func testPauseWhenNotPlaying() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
view.pause(animationName: "one")
view.advance(delta:0)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
view.playerDelegate = delegate
viewModel.pause()
view.advance(delta: 0)
XCTAssertEqual(delegate.linearAnimaitonPauses.count, 0)
}
func testStop() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
try view.play(animationName: "one")
view.stop(animationName: "one")
view.advance(delta:0)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
view.playerDelegate = delegate
viewModel.play(animationName: "one")
viewModel.stop()
view.advance(delta: 0)
XCTAssertEqual(delegate.linearAnimaitonStops.count, 1)
}
func testStopNotMounted() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
view.stop(animationName: "one")
view.advance(delta:0)
XCTAssertEqual(delegate.linearAnimaitonStops.count, 0)
view.playerDelegate = delegate
viewModel.stop()
view.advance(delta: 0)
XCTAssertEqual(delegate.linearAnimaitonStops.count, 1)
}
func testStopPaused() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
try view.play(animationName: "one")
view.pause(animationName: "one")
view.stop(animationName: "one")
view.advance(delta:0)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
view.playerDelegate = delegate
viewModel.play(animationName: "one")
viewModel.pause()
viewModel.stop()
view.advance(delta: 0)
viewModel.riveView?.advance(delta: 0)
XCTAssertEqual(delegate.linearAnimaitonStops.count, 1)
}
func testLoopOneShot() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
try view.play(animationName: "one", loop: .loopOneShot)
view.advance(delta:Double(view.animations.first!.effectiveDurationInSeconds()+0.1))
// rough. we need an extra advance to flush the stop.
view.advance(delta:0.1)
view.playerDelegate = delegate
viewModel.play(animationName: "one", loop: .loopOneShot)
view.advance(delta: Double(viewModel.riveModel!.animation!.effectiveDurationInSeconds()+0.1))
XCTAssertEqual(delegate.loops.count, 0)
XCTAssertEqual(delegate.linearAnimaitonPlays.count, 1)
XCTAssertEqual(delegate.linearAnimaitonPauses.count, 0)
XCTAssertEqual(delegate.linearAnimaitonStops.count, 1)
XCTAssertEqual(delegate.linearAnimaitonPauses.count, 1)
XCTAssertEqual(delegate.linearAnimaitonStops.count, 0)
}
func testLoopLoop() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
try view.play(animationName: "one", loop: .loopLoop)
view.advance(delta:Double(view.animations.first!.effectiveDurationInSeconds()+0.1))
view.playerDelegate = delegate
viewModel.play(animationName: "one", loop: .loopLoop)
view.advance(delta: Double(viewModel.riveModel!.animation!.effectiveDurationInSeconds()+0.1))
XCTAssertEqual(delegate.loops.count, 1)
XCTAssertEqual(delegate.linearAnimaitonPlays.count, 1)
@@ -207,15 +224,15 @@ class DelegatesTest: XCTestCase {
}
func testLoopPingPong() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"multiple_animations"),
autoplay: false,
playerDelegate: delegate
)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "multiple_animations")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, autoPlay: false)
let view = viewModel.createRiveView()
try view.play(animationName: "one", loop: .loopPingPong)
view.advance(delta:Double(view.animations.first!.effectiveDurationInSeconds()+0.1))
view.playerDelegate = delegate
viewModel.play(animationName: "one", loop: .loopPingPong)
view.advance(delta: Double(viewModel.riveModel!.animation!.effectiveDurationInSeconds()+0.1))
XCTAssertEqual(delegate.loops.count, 1)
XCTAssertEqual(delegate.linearAnimaitonPlays.count, 1)
@@ -224,93 +241,96 @@ class DelegatesTest: XCTestCase {
}
func testStateMachineLayerStates() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"what_a_state"),
stateMachineName: "State Machine 2",
playerDelegate: delegate,
stateChangeDelegate: delegate
)
let delegate = DrDelegate()
let file = try RiveFile(testfileName: "what_a_state")
let model = RiveModel(riveFile: file)
let viewModel = RiveViewModel(model, stateMachineName: "State Machine 2", autoPlay: true)
let view = viewModel.createRiveView()
view.playerDelegate = delegate
view.stateMachineDelegate = delegate
view.advance(delta:0.1)
XCTAssertEqual(delegate.stateMachinePlays.count, 1)
XCTAssertEqual(delegate.stateMachineStates.count, 1)
XCTAssertEqual(delegate.stateMachineNames[0], "State Machine 2")
XCTAssertEqual(delegate.stateMachineStates[0], "go right")
view.advance(delta:1.1)
XCTAssertEqual(delegate.stateMachineStates.count, 2)
XCTAssertEqual(delegate.stateMachineNames[1], "State Machine 2")
XCTAssertEqual(delegate.stateMachineStates[1], "ExitState")
// takes an extra advance to trigger
view.advance(delta:0)
XCTAssertEqual(delegate.stateMachinePauses.count, 1)
}
func testStateMachineLayerStatesComplex() throws {
let delegate = MrDelegate()
let view = try RiveView.init(
riveFile: getRiveFile(resourceName:"what_a_state"),
stateMachineName: "State Machine 1",
stateChangeDelegate: delegate
)
view.advance(delta:0.0)
XCTAssertEqual(delegate.stateMachineStates.count, 0)
// lets just start, expect 1 change.
try view.fireState("State Machine 1", inputName: "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. i think its all because of
// how the
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, view.isPlaying)
// animation came to an end inside this time period, this still means no state change
view.advance(delta:0.4)
XCTAssertEqual(false, view.isPlaying)
XCTAssertEqual(0, delegate.stateMachineStates.count)
// animation is just kinda stuck there. no change no happening.
view.advance(delta:0.4)
XCTAssertEqual(false, view.isPlaying)
XCTAssertEqual(0, delegate.stateMachineStates.count)
// ok lets change thigns up again.
try view.fireState("State Machine 1", inputName: "change")
view.advance(delta:0.0)
view.advance(delta:0.4)
XCTAssertEqual(true, view.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, view.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, view.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, view.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)
}
}