I am trying to make the enemy in my game move using DispatchQueue. I tried fixing this error and it keeps telling me attributes do not match any available overloads.
func makeAIMove() {
let strategistTime = CFAbsoluteTimeGetCurrent()
DispatchQueue.global(attributes: .qosUserInitiated).async { [unowned self] in
guard let move = self.strategist.bestMoveForActivePlayer() as? Move else { return }
let delta = CFAbsoluteTimeGetCurrent() - strategistTime
DispatchQueue.main.async{ [unowned self] in
self.rows[move.row][move.col].setPlayer(.choose)
let aiTimeCeiling = 2.0
let delay = min(aiTimeCeiling - delta, aiTimeCeiling)
DispatchQueue.main.after(when: .now() + delay) { [unowned self] in
self.makeMove(row: move.row, col: move.col)
}
}
}
}
I think what you want is DispatchQueue.global(qos: .userInitiated); indeed, there is no overload taking an attributes argument.
Related
I'm trying to build a timer that starts at 15 seconds and count down until 0.
The thing is that I'll want to update that timer by 2 seconds more based on an event.
This is what I've tried to do so far:
struct ViewModel {
struct Input {
let add_time: Observable<Void>
}
struct Output {
let current_time: Observable<String>
let timer_over: Observable<Void>
}
private let current_time = BehaviorRelay(value: 15)
private let timer_over = PublishSubject<Void>()
func transform(input: Input) -> Output {
let current_time = self.current_time
.flatMapLatest { time in
Observable<Int>
.timer(.seconds(0), period: .seconds(1), scheduler: MainScheduler.instance)
.take(self.current_time.value + 1)
.map { "\(self.current_time.value - $0)" }
.do(onCompleted: { self.timer_over.onNext(()) })
}
return Output(
current_time: current_time,
timer_over: timer_over
)
}
}
let disposeBag = DisposeBag()
let add_time_subject = PublishSubject<Void>()
let input = ViewModel.Input(
add_time: add_time_subject.asObservable()
)
let output = ViewModel().transform(input: input)
output.current_time
.subscribe(onNext: { (time) in
print(time)
})
.disposed(by: disposeBag)
output.timer_over
.subscribe(onNext: {
print("timer_over")
})
.disposed(by: disposeBag)
The thing is when I run add_time_subject.onNext(()) I'd want to update the timer by 2 seconds more only if the timer hasn't reached 0 seconds.
How should I do that?
What you're trying to achieve kinda sounds like a state machine.
You could achieve it by splitting the timer actions into actual "Actions" and merging them based on the trigger (one being the manual "add two seconds", and thee other is automated as "reduce one second").
I haven't fully tested it, but this can be a good starting ground:
enum TimerAction {
case tick
case addTwoSeconds
}
let trigger = PublishRelay<Void>()
let timer = Observable<Int>
.interval(.seconds(1), scheduler: MainScheduler.instance)
.map { _ in TimerAction.tick }
let addSeconds = trigger.map { TimerAction.addTwoSeconds }
Observable
.merge(timer, addSeconds)
.scan(into: 15) { totalSeconds, action in
totalSeconds += action == .addTwoSeconds ? 2 : -1
}
.takeUntil(.inclusive) { $0 == 0 }
.subscribe()
.disposed(by: disposeBag)
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
trigger.accept(()) // increase the timer by two seconds after 5 seconds
}
I have such code as below. How can I achieve it in one chain, without using subscribe on timer? I would like to attach 'timerInterval' to 'timer' and then call subscribe.
var timerInterval: BehaviorRelay<String> = BehaviorRelay(value: "")
...
func doLogic() {
let timer = Observable<Int>.interval(0.05, scheduler: MainScheduler.instance)
timer.subscribe({ [weak self] value in
let doubleValue = Double(value.element ?? 0)
let dividedValue = doubleValue / 20.0
let text = String(format: "%.2f", dividedValue)
self?.timerInterval.accept(text)
}).disposed(by: disposeBag)
}
You'd go for map operator. I'm not sure why you need BehaviourRelay but I'd do something even more simpler:
let timer = Observable<Int>.interval(0.05, scheduler: MainScheduler.instance)
var timerInterval: Observable<String> {
return timer.map { value -> String in
let doubleValue = Double(value.element ?? 0)
let dividedValue = doubleValue / 20.0
let text = String(format: "%.2f", dividedValue)
return text
}
}
In Swift 4 i am loaded/holding a set of animations from dae files in an array:
var animations = [String: CAAnimation]()
loadAnimation(withKey: "stepL", sceneName: "art.scnassets/StepL", animationIdentifier: "StepL-1")
loadAnimation(withKey: "stepR", sceneName: "art.scnassets/StepR", animationIdentifier: "StepR-1")
func loadAnimation(withKey: String, sceneName:String, animationIdentifier:String) {
let sceneURL = Bundle.main.url(forResource: sceneName, withExtension: "dae")
let sceneSource = SCNSceneSource(url: sceneURL!, options: nil)
if let animationObject = sceneSource?.entryWithIdentifier(animationIdentifier, withClass: CAAnimation.self) {
// The animation will only play once
animationObject.repeatCount = 1
// To create smooth transitions between animations
animationObject.fadeInDuration = CGFloat(0.8)
animationObject.fadeOutDuration = CGFloat(0.6)
// Store the animation for later use
animations[withKey] = animationObject
}
}
Later on i am playing the animation by adding it to my scene:
func playAnimation(key: String) {
print("fire animation: " + key)
scnView.scene?.rootNode.addAnimation(animations[key]!, forKey: key)
}
This seems to work when doing them individually from touches, but i want to be able to trigger a sequence of these with very specific timing.
I've tried building a loop and sending them off to a DispatchQueue with specific timing outlined:
var delaytime = state.steptime!
for _ in 0..<state.trCount! {
for wSide in state.walkSeq! {
walkQ.asyncAfter(deadline: .now() + delaytime){
self.playAnimation(key: wSide)
}
delaytime += state.steptime! + 1.0
}
}
And i've tried a similar, but slightly different approach using wallDeadline:
let wt = DispatchWallTime.now() + state.pausetime!
//build sequence
var train_seq = [(name:"stepL", time:wt), (name:"stepR",time:wt + state.pausetime! + state.steptime!)]
for _ in 0..<state.trCount! {
let lasttime = train_seq[ train_seq.count - 1 ].time
train_seq += [(name:"stepL", time:lasttime + state.steptime!)]
train_seq += [(name:"stepR", time:lasttime + state.pausetime! + state.steptime!)]
}
//send it
for i in 0..<train_seq.count {
walkQ.asyncAfter(wallDeadline: train_seq[i].time){
self.playAnimation(key: train_seq[i].name)
}
}
What inevitably happens is that the first few run as expected, then around the 4th set of sequences they start to stack on top of each other and fire at the same time, no longer adhering to the timing that i've defined
What is the best way to trigger animations in a sequence on an expected timeline? Is there a way to pause/sleep a loop until the previous animation stops, then trigger the next in the series?
Thanks!
i ended up making a sequence of SCNActions in the order i needed, including a wait
var seq = [SCNAction]()
for _ in 0..<state.trCount!{
let act1 = SCNAction.run {_ in
self.playAnimation(key: "stepL")
}
seq.append(act1)
let act2 = SCNAction.wait(duration: state.steptime!)
seq.append(act2)
let act3 = SCNAction.run { _ in
self.playAnimation(key: "stepR")
}
seq.append(act3)
seq.append(act2)
let act4 = SCNAction.run { _ in
self.playAnimation(key: "idle")
}
seq.append(act4)
let act5 = SCNAction.wait(duration: state.pausetime!)
seq.append(act5)
}
let sequence = SCNAction.sequence(seq)
scnView.scene?.rootNode.runAction(sequence, completionHandler:nil )
Is there elegant solution for creating delay for retry? When error occurs I want to wait for 5 seconds and restart Observable (retry)
Just create a PrimitiveSequence extension that wraps around retry().
(Swift5.1 RxSwift 4.3.1)
extension PrimitiveSequence{
func retry(maxAttempts: Int, delay: TimeInterval) -> PrimitiveSequence<Trait, Element> {
return self.retryWhen { errors in
return errors.enumerated().flatMap{ (index, error) -> Observable<Int64> in
if index < maxAttempts {
return Observable<Int64>.timer(RxTimeInterval(delay), scheduler: MainScheduler.instance)
} else {
return Observable.error(error)
}
}
}
}
}
Usage example: (retry 3 times, with 2 sec delay each)
yourRxStream.retry(maxAttempts: 3, delay: 2)
Here is the code for swift 4.0+
// sample retry function with delay
private func sampleRetryWithDelay(){
let maxRetry = 3
let retryDelay = 1.0 // seconds
let _ = sampleStreamWithErrors() // sample observable stream, replace with the required observable
.retryWhen { errors in
return errors.enumerated().flatMap{ (index, error) -> Observable<Int64> in
return index < maxRetry ? Observable<Int64>.timer(RxTimeInterval(retryDelay), scheduler: MainScheduler.instance) : Observable.error(error)
}
}.subscribe(onNext:{ value in
print("Result:\(value)")
})
}
// Sample stream with errors, helper function only - generating errors 70% of the time
private func sampleStreamWithErrors() -> Observable<Int>{
return Observable.create { observer in
let disposable = Disposables.create() {} // nothing to cancel, we let it complete
let randomInt = Int(arc4random_uniform(100) + 1)
if randomInt > 70 {
observer.on(.next(randomInt))
observer.on(.completed)
} else {
let sampleError = NSError(domain: "SampleDomain", code: randomInt, userInfo: nil)
print("Result:Error:\(randomInt)")
observer.on(.error(sampleError))
}
return disposable
}
}
I have to wait for multiple background process before doing other, I am looking for and trying to do something with this, but I can't. Here is the code. I commented the parts to explain what I want to reach. I could do it with completion handler but this function is called for a few times, depending on number of photos.
func prepareArray(stext: NSMutableAttributedString, completionHandler: CompletionHandler) {
self.finalForUpload = [String]()
var attributedArray = [AnyObject]()
var lastRng = 0
let range:NSRange = NSMakeRange(0,stext.length)
stext.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions(rawValue: 0), usingBlock: {(dic: [String:AnyObject]?, range :NSRange!, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let attachement = dic as NSDictionary!
attachement.enumerateKeysAndObjectsUsingBlock({ (key:AnyObject?, obj:AnyObject?, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let stri = key as! NSString!
if stri == "NSAttachment"{
let att:NSTextAttachment = obj as! NSTextAttachment
if att.image != nil{
print("range location: \(range.location)")
if range.location != lastRng {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, range.location-lastRng)))
lastRng = range.length
}
let im:UIImage = att.image!
self.httpManager.uploadPhoto(im, completionHandler: { (imgName) -> Void in
let imName = imgName.characters.dropFirst()
let imageName = String(imName.dropLast())
//WAIT FOR THIS
dispatch_async(dispatch_get_main_queue(), {
print("append")
//stext.attributedSubstringFromRange(range)
attributedArray.append(imageName)
lastRng = range.location
})
})
}
}
})
})
//BEFORE DOING THIS
dispatch_async(dispatch_get_main_queue(), {
if lastRng == 0 && attributedArray.count < 1{
attributedArray.append(stext)
} else {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, stext.length-lastRng)))
}
for var i = 0; i<attributedArray.count; i++ {
print("\n \n \(i) : \(attributedArray[i])")
}
})
}
You can do this using dispatch_group methods.
However I would recommend using something like PromiseKit or similar which gives you a much nicer way of controlling dependencies of multiple async blocks.
Here is your code using dispatch_group methods:
func prepareArray(stext: NSMutableAttributedString, completionHandler: CompletionHandler) {
// create the dispatch group
dispatch_group_t aGroup = dispatch_group_create();
self.finalForUpload = [String]()
var attributedArray = [AnyObject]()
var lastRng = 0
let range:NSRange = NSMakeRange(0,stext.length)
stext.enumerateAttributesInRange(range, options: NSAttributedStringEnumerationOptions(rawValue: 0), usingBlock: {(dic: [String:AnyObject]?, range :NSRange!, stop: UnsafeMutablePointer<ObjCBool>) -> Void in
let attachement = dic as NSDictionary!
attachement.enumerateKeysAndObjectsUsingBlock({ (key:AnyObject?, obj:AnyObject?, stop:UnsafeMutablePointer<ObjCBool>) -> Void in
let stri = key as! NSString!
if stri == "NSAttachment"{
let att:NSTextAttachment = obj as! NSTextAttachment
if att.image != nil{
print("range location: \(range.location)")
if range.location != lastRng {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, range.location-lastRng)))
lastRng = range.length
}
let im:UIImage = att.image!
// use dispatch_group to coordinate.
// must call before uploadPhoto as we don't know how long it will take
dispatch_group_enter(aGroup);
self.httpManager.uploadPhoto(im, completionHandler: { (imgName) -> Void in
let imName = imgName.characters.dropFirst()
let imageName = String(imName.dropLast())
dispatch_async(aGroup, dispatch_get_main_queue(), {
print("append")
//stext.attributedSubstringFromRange(range)
attributedArray.append(imageName)
lastRng = range.location
// indicate block has completed (reduced group count)
dispatch_group_leave(aGroup);
})
})
}
}
})
})
// wait for all items in group to finish before running this block
dispatch_group_notify(aGroup, dispatch_get_main_queue(), {
if lastRng == 0 && attributedArray.count < 1{
attributedArray.append(stext)
} else {
attributedArray.append(stext.attributedSubstringFromRange(NSMakeRange(lastRng, stext.length-lastRng)))
}
for var i = 0; i<attributedArray.count; i++ {
print("\n \n \(i) : \(attributedArray[i])")
}
})
}
I have to wait for multiple background process before doing other
You should really look at using NSOperationQueue for this requirement as it allows you to configure dependencies between all of the registered operations. In this way you could configure and add a number of downloads and then configure a post download action which is dependent on all of those downloads. The operation queue will deal with starting that operation when all others are complete.
The operations also give you a nice way to cancel the pending uploads / downloads if you don't need them any more.
You could alternatively look at using dispatch_groups to manage this. It's harder to ensure that what you've done is entirely correct.
The main queue is a serial queue, meaning that it executes on block at a time in first-in-first-out order. All of the dispatches in the enumerate should be done before the one at the bottom is run.
EDIT: just noticed that you async in a completion handler.
One simple thing to do is to just count down (because you know how many there are).
Just add var expectedCompletions = attachement.count before the enumerate and then count them down (expectedCompletions--) inside the block. When it gets to 0, run the code at the end.