First of all, I know there are a few other StackOverflow questions about this subject, but I have read them all and I still am confused about what to do for my situation. I'm probably missing something obvious here, if you could help clarify that would be much appreciated!
I have a app which is doing a lot of work to animate images within a view - mainly comprised of a number of images moving in straight lines for a second or two at a time. I considered at first making them all simple, once off animations using UIView animateWithDuration for the whole duration of the movement. But I found that didn't give me a lot of power to intercept the movement or stop it or check where it was up to, so I scrapped that. My new approach is to use an NSTimer, firing 20 times per second, doing incremental movements. This way I also can intervene (almost) instantly to change the animation or stop it or update a status label based on how far through it is, etc, etc.
First of all...there probably is a better way than this. Feel free to suggest something better!
Assuming this is acceptable though, my issue now is that while these animations are happening, I can't click any of the other controls on the UI. I get no response. It's not like it's just slow or delayed either - the click never comes through. It seems that the NSTimer processing totally locks the UI - but only from new interactions. Changes I make to the UI within the timer processing method happen just fine, and are very snappy.
From what I've read this shouldn't happen. However I also saw a comment on this question saying that if the timer processing is intensive then it could lock the UI thread. I don't see my processing to be that intensive here - certainly no resource requests, just a bit of data manipulating and animating some objects - but I could be underplaying it.
What are my options here? At first I thought I might create a new thread to kick off the timer. But I remember reading that the UI updates have to happen on the main thread anyway. Why is this? And plus, would that really solve the issue? Am I just asking too much of the device to process this timer as well as UI interactions? Is there something else I'm missing?
Any and all advice would be appreciated.
Edit:
I've just found the cause of my UI blocking problem. I was using the animateWithDuration with blocks, but was not setting the options. Therefore UIViewAnimationOptionAllowUserInteraction was not set. I changed it to set this option and my UI is happily responding now.
That said, I'll still leave this question open for specific suggestions regarding my overall approach. Thanks.
I would consider using CADisplayLink. From the documentation:
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
Your application creates a new display link, providing a target object and a selector to be called when the screen is updated. Next, your application adds the display link to a run loop.
Once the display link is associated with a run loop, the selector on the target is called when the screen’s contents need to be updated. The target can read the display link’s timestamp property to retrieve the time that the previous frame was displayed. For example, an application that displays movies might use the timestamp to calculate which video frame will be displayed next. An application that performs its own animations might use the timestamp to determine where and how displayed objects appear in the upcoming frame. The duration property provides the amount of time between frames. You can use this value in your application to calculate the frame rate of the display, the approximate time that the next frame will be displayed, and to adjust the drawing behavior so that the next frame is prepared in time to be displayed.
Your application can disable notifications by setting the paused property to YES. Also, if your application cannot provide frames in the time provided, you may want to choose a slower frame rate. An application with a slower but consistent frame rate appears smoother to the user than an application that skips frames. You can increase the time between frames (and decrease the apparent frame rate) by changing the frameInterval property.
When your application finishes with a display link, it should call invalidate to remove it from all run loops and to disassociate it from the target.
CADisplayLink should not be subclassed.
I'm not totally sure how everything is handled in your program, but you might want to just consider having one thread/timer that controls all of the objects and their movements. There's really no need to create a separate thread/timer for every single object, as that will easily cause problems.
You can just create a class for your moving items with some variables that contain information about their direction, speed, duration, etc, and then have a controlling thread/timer calculate and move the objects. You can then intervene onto the one main controller object instead of having to deal with many other objects.
I think you'll find that even if you optimize this, timer based animation like this is not going to perform well.
You might want to ask about the specific things that you think you couldn't do with CoreAnimation. If you solve those issues, you'll end up with a much better result than trying to roll your own.
Related
I have a timeline where I basically want the user to have the ability to tap on the screen to fast forward the animation (well, more like skip part of the animation) to a later part. Obviously, this would have to be done using code.
I've researched this quite a bit and I don't think there's an easy solution. CCBSequence and CCBSequenceProperty offer a bit of hope, but my ideas have run dry and can't see a way to do it.
Obviously I'm aware of the animation manager and using it to run timelines and stop timelines, or even call something when the timeline is completed, but my capabilities with manipulating timelines made in Spritebuilder doesn't extend too far beyond that.
I can think of a messy workaround, where basically I just duplicate the section of the timeline I want to skip to and when the screen is tapped stop the current timeline and go to that, but it seems cumbersome and messy and I'd like to avoid that if possible.
Thanks for help!
I found the easy solution after a bit more looking
So if you have a look in CCAnimationManager there is a method for this called:
- (void)jumpToSequenceNamed:(NSString*)name time:(float)time;
So with your node you can call
[node.animationManager jumpToSequenceNamed:#"MyTimeline" time:1.0]; // 1 Second
If your animations aren't extremely complicated, try to implement it via cocos2d actions http://www.cocos2d-swift.org/docs/api/index.html
Using engine API you can create animation with custom speed on every tap.
In relation to How to determine if a user has scrolled to the end of an NSTableView
Thanks Josh.
Is there a way to use this mechanism to implement a NSTableView that provides some sort of infinite scroll or pagination.
The idea is to tell NSTableView to load up to a certain number of records, say 1k records at once and than as user scrolls closer to the end pull another 1k records and maybe forget the first 1k records.
This pattern is well defined/used in web applications and java. Only the visible number of rows is loaded initially and the rest is pulled async as user scrolls up and down the table.
I am interested in some obj-c code or tips on how to code this.
I know about filtering/limiting the number of records that go into the tableview but lets ignore that for a moment.
Thanks.
Given the details you've provided, I'll generalize a bit but here's how I might solve it:
First, I'd set a MUCH SMALLER batch size than 1000 records. If the result count or "the most anybody is ever going to want to see" is indeterminate (and it sounds like it is in your case), the user probably doesn't even care past the first 100 or so. If your user often requests a large, expensive list and immediately wants to see stuff so far away from the beginning they hurl the scroller downward for two minutes straight before they stop and look around, perhaps a more intuitive sort order is needed instead of asking Google Image for 1000 more animated kitten gifs. ;-)
The controller behind the (definitely view-based for view reuse) table view will need some sort of request queue since I assume you're batching things in because they're expensive to retrieve individually. This will manage the asynchronous requesting/okay-now-it's-loaded machinery (I know that's vague but more detail is needed to get more specific). You'll make sure any "currently alive" views will somehow get this "it's ready" notification and will go from some "busy" UI state to displaying the ready item (since we NEVER want to keep the table waiting for a ready-to-display view for the object at a given row, so the view should at least show some "still waiting for details" indication so quick scrolls over lots of rows won't stall anything).
Using a view-based NSTableView and associated data source methods will let the table view handle only keeping enough copies of your custom NSTableCellView around to reuse during scrolling. Since you have to provide a configured view when asked, the view's default state can either be "draw nothing if not ready" or some visually generic placeholder until the object is realized and ready (then you can just reload that row instead of the whole table). This way the table keeps scrolling and drawing rapidly because it doesn't care about what your controller is doing to fulfill the promise of updating the visible rows (that custom cell view of yours will observe its represented object's updates).
You probably want the scrollers to reflect the total number of rows batched in so far if the upper bound is astronomical - reflecting that size would make the scroll grip both tiny and very sensitive. Instead, just grow the scroller (via the table view's row count) by what the user has "requested" so far, all the way back to the beginning of the list. Any time more are batched in, you'll want to add the batch size to your controller's total batched row count. This still lets the scroller zoom by rows the user couldn't distinguish at that speed anyway. You communicate the row count change to the table view by sending it -noteNumberOfRowsChanged and replying to its resulting data source request ( -numberOfRowsInTableView: ) with the updated total row count you stashed in a property of your controller. It'll ask for views for the newly visible rows as needed (which will be in some neutral, unfulfilled visual state until it's realized as before), update the scroll view, lather, rinse, repeat.
You could use NSCache to keep memory usage low. Set its countLimit to several times your batch size and let it drop previous batches if it decides it needs to dump the first n model objects, then batch them back in if the table view suddenly asks for a view for a row no longer in the batch window's range.
Without knowing more about your requirements and architecture, it's hard to get more specific. If I haven't hit the mark, consider editing your question to include more detail. If I'm totally off base from what you're asking for, please clarify. :-)
I know more about iOS, but I think the answer is similar. Table views are intrinsically finite, but you can roll your own custom scroll view to do this. The trick is to set a large content size and implement layout in your subclass (which will get called on every scroll change). In that method, check to see if the content offset is near zero or near the content size. If it is, then translate the content offset back to the center of the content size and translate all the subviews (keep them on one parent content view) by the same distance so the user doesn't see any motion. Make a datasource protocol and keep asking your datasource for "cells" that tile the visible part of the view.
It should be up to the datasource to recognize what we would have called a page-fault in the olden days, to decide that some of the model in memory should be discarded in favor of the model where the user is scrolling.
I poked around for an NS equivalent, but didn't see one on cursory search. Here's a decent-looking reference on the idea done in iOS.
According to a Qt blog post from 2011, there is supposed to be an environment variable QML_FLASH_MODE that can be set to 1 to see which areas of a QML application are being repainted.
I have set this variable for a Qt Quick 2 application that I'm writing and there is no visual difference to running the application without this variable.
Is there any other way to see QML redrawing activity? I'm trying to troubleshoot performance issues.
It looks like QML_FLASH_MODE was removed in this commit as part of refactoring to support separate rendering loops per window. Reason why it was removed is not obvious, but anyway its gone now.
If you think QML_FLASH_MODE will help you to analyse your scene, you can always get back to Qt 5.1.1 where it still should be present.
On the other hand, to visually analyse scene is not always efficient. I suggest to use qtcreator's profiling. Take a look at property bindings that reevaluated just before painting happens. This should help you to figure which property changes have triggered redraw and could give you some hints on how to optimise your scene.
If you just want to count fps or log when frames being painted, check QQuickWindow::frameSwapped signal.
In an attempt to optimize my app's performance, I've tried to cut down the number of elements in its main carousel as much as possible. I'm down to 3 first-order items (the one being viewed, along with N-1 and N+1).
I'd also like to cut out most of the contents of the N-1 and N+1 carousel items and programmatically add the contents for the item that is currently being viewed (so that the carousel items would look like this: http://i.imgur.com/gm7cL7E.png).
activeItemChange is the obvious choice here for the event listener. Problem is, it apparently is relatively slow, especially on low-end Android phones. It can take as long as 4 seconds to fire.
I've tried the carousel's onDrag property as a different way to fire the content creation, but asking the phone to do two operations (animate the carousel moving & create the new content items) makes the carousel movement animation choppy.
Is there any way to modify activeItemChange or the carousel to improve the listener's firing speed? Or any other listener I could be looking at that would perform better? I've done quite a lot of work optimizing overall performance on the app (shrinking the DOM, event delegation, etc) and the rest of the app runs quite well, so I'm not sure that doing general performance work will free up sufficient CPU to get activeItemChange to the level that I'd like.
Edit: I've done even more performance improvements in an attempt to free up enough CPU, to no avail. Any additional tips would be greatly appreciated.
Edit 2: It's been suggested to use order: 'before' for the event, as in:
listeners : {
order : 'before',
activeitemchange : someFunction
}
That does fire much more quickly. However, since activeItemChange has necessarily not been fired yet, I can't determine which item should get the carousel contents added. If I could determine which direction the carousel is going, that would be sufficient, but I can't seem to get that either.
Additionally, since Sencha seems to automatically paint N+1 and N-1 and erases N+2 and N-2, I was thinking I could use that as a more rapid determinant of where the item is in the carousel; unfortunately, based on the performance issues brought up in the Sencha documentation, it seems like it would end up with worse performance rather than better in the end.
Good question. Just a suggestion may or may not work. Instead of concentrating on the activeItemChange of the carousel maybe you could try the painted event of the container into which you want to put your items, or initialize event if the data is not going to change, initialize is faster than painted but only fires once so if your content is going to change use painted, otherwise try it out with initialize it may be the better way to do it.
I'm working on a 2D shmup, and the idea is that the level continuously scrolls automatically, and your character can move around the screen.
Now, I'm having trouble figuring out how I would implement this and Google hasn't been any help. Right now I have a scrolling background (the background position is simply decremented for each frame) and the player can move around freely in the window, but how would I go about creating the objects in the level? Would I just use a timer to trigger objects and enemies or is there a way to do it based on the position/width of the background (I'd prefer the second method...But I have no clue how that would be done)?
Since this is a general question and doesn't really pertain to any of my code that I've already written as far as I know, I don't think I need to include any of it...But I'll be happy to provide any part of it if needed.
I'd recommend either:
Use physical triggers
Use a list of timed events
Physical triggers
Simply place a box on your level. When it scrolls partially or completely onto the screen (whichever makes more sense - maybe use both in different cases?), you trigger the event associated with that trigger.
This would be simpler to support in a level editor because the physical nature is inherently very easy to visualize.
Timed events
You basically create a timer object at the beginning of the level, and an ordered queue of events. In your game update loop, peek at the head of the queue. If the trigger time of the item at the head of the queue is less than the current elapsed time, pop the item off the queue and trigger the event.
Timed events would be more generically useful because it would also support non-scrolling level, or non-scrolling portions of levels.
Combination of both
You could also do some sort of combination of these to get the benefits of both styles: Easier visualization/level editing, and supporting non-scrolling sections or time-based events.
Each physical trigger will have its own script queue. When the trigger is hit, a timer is started and an event queue is created. That timer and queue is added to a list of currently running timers and queues.
In your update function, you check all items on the list, and trigger events the same way you did with the timed event queue above. Once a queue is emptied, you remove it from the list of timers/queues.
How to detect that a trigger is on-screen
You should implement scrolling first.
One you have scrolling, calculate the rectangle that matches where the screen is located in your pixel/world coordinate system. This will give you the "bounding box" of the screen.
From here, do an intersection test between your event trigger's "bounding box" and the screen.
Here is a test to see if there is any overlap between two rectangles. It isn't order-specific:
rect1.left < rect2.right && rect1.right > rect2.left
&& rect1.top < rect2.bottom && rect1.bottom > rect2.top
If the rects are touching at all, it will return true.
Here is a test to make sure rect1 contains rect2. Order is important:
rect1.left <= rect2.left && rect1.right >= rect2.right
&& rect1.top <= rect2.top && rect1.bottom >= rect2.bottom
If rect2 is completely contained by rect1 (it is completely on-screen), it will return true.
How to implement simple timers
Simply get some sort of clock value (could be SDL_GetTicks), and store that value.
To see how long has elapsed since that timer was started, call the function again and subtract. Compare the values with < to see if the difference is greater than the target time.
Unfortunately, this is where you should use pointers. Something along the lines of:
vector<BadGuy*> Listofbaddies;
//Place enemy just off screen
newYposition = SCREEN_HEIGHT + 20;
//an infinite (almost) amount of badguys can be created with this code:
Listofbaddies.push_back(new Badguy(newXposition,
newYposition,
EnemyType,
blahblah);
Which means that the badguy will need a constructor like:
Badguy::Badguy(float newX, float newY, string Type, whateverelseyouwant){
actualSpritePartOfBadguyClass.setPosition(newX, newY);
}
Does that make sense? I'll elaborate if you ask :D
I'm making a game now that uses something similar :)