Flash AS3 animation in Mac vs Windows - windows

I am working on a short animated story, which has a scrubbable timeline and chapter headings. I used TimelineMax for sequencing it. For the most part, it is working fine. I am seeing some strange behavior that pop up, though: sprites disappear, functions stop responding to user input, seams of the sprites become transparent -- all small issues but pretty hard to nail down because they happen in Mac only.
So I am wondering what is wrong with Flash, and why it misbehaves on a Mac?

I frequently work on the same projects on Windows at work and then my Mac at home. I also see some difference on the Mac compared to Windows. I find that various Flash Player versions for the Mac are generally slower than the Windows players, and I've seen some odd behavior on the Mac that is not happening on Windows.
In most cases I've narrowed this down to AS3's garbage collection. Garbage collection happens when the player determines that an object no longer has a reference in the movie, so it removes that object to free up memory. Let's say you have a class method like this:
function myTweenFunction():void {
var myTween:Tween = new Tween(myDisplayObject, 'x', Strong.easeInOut, 0, 500, 10, true);
myTween.addEventListener(TweenEvent.MOTION_FINISH, onMyTweenDone);
}
The method above will tween myDisplayObject's x value from 0 to 500 over the course of 10 seconds. When that tween is done, it should fire the onMyTweenDone method (not shown). However, myTween was created inside myTweenFunction so it only exists in the scope of myTweenFunction. When myTweenFunction is done, the myTween object is no longer referenced by any object in the movie so it becomes a candidate for garbage collection. You will start to see the tween, but at some point it will stop before it gets to 500 and the finish event will not fire. This means that myTween has been destroyed. To fix this problem, myTween needs to be a member of the class, or just needs to have a reference outside of a class function.
Getting back to the Mac vs. Windows issues, I see that garbage collection on runtime-created objects on the Mac is more apparent than on Windows. Garbage collection happens in the Windows Flash Player, but the tweens and other events may be finishing before garbage collection occurs since the Windows Flash Player has better performance. If the Mac Flash Player is slower (ie. the same tween might take longer), then the garbage collection might happen before the tween is done. Garbage collection does not occur frame-by-frame like an animation; it's a background process that can happen at any time, or not at all if there's enough memory for the Flash Player. Your windows machine might have a pile of RAM and the movie can play fine without the need for garbage collection, so myTween might never go away. If your Mac has less memory, or if you have a ton of apps open at once and the Flash Player memory allotment is limited, then the Flash Player will perform garbage collection more frequently.
I've also used TimelineMax, and there's an auto garbage collection feature that is turned on by default. Try turning that off and test on the Mac.
Ultimately, you should design your project with the assumption that a user may have very limited memory, so your objects need to be created, referenced, and garbage collected accordingly.

I've encountered some rendering issues between plugin versions especially when dealing with transparency, fonts, and embed settings.
If you are doing this in a web browser try playing with the WMODE embed setting and see if your results change.

Related

Contents of build method rebuilt without any setState calls

My app has a lot of API calls and image downloads, that's why i'm using Cache Manager package.
I noticed a lot of content is re downloaded and my app sends API requests without me doing anything but scrolling the screen.
I started checking and the build() method does gets rebuilt any time, but it has nothing to do with any setState() calls whatsoever!
Is there a chance the garbage collector or cache issues have something to do with that?
I get a lot of debug prints like Background concurrent copying GC freed 368(32KB) AllocSpace objects, 18(1632KB) LOS objects, 56% free, 1202KB/2MB, paused 25.610ms total 107.349ms.
If not what can cause it?
Well i deleted the question but thought about it and un-deleted it to answer with the solution if anyone else will ever encounter such a behavior:
It is very weird but seems like the problem was having my widget as a child of a RefreshIndicator widget.
I'm sure i didn't even scroll to the position it should indeed activate my refresh method, but scrolling any direction too far (even horizontally while my refresh indicator was a regular vertical one) activated it for some reason.
Sorry for not providing any example while asking the question, the app was too complicated and i didn't even know what is relevant and what's not...
Edit:
Now it's just a real mystery, i added a print('Refresh') line to my onRefresh method, and it never got printed, even when the widget got rebuilt!
If anyone have some kind of explanation i would really like to hear it.

Updating contents of multiple NSTextView objects in a single operation

The database application I am working on can have a window with multiple NSTextView elements for displaying and editing data. When the current spot in the database is repositioned, all of the NSTextView objects in the window need to be updated with new contents. This is done with a loop that scans each object and checks to see if it needs to be updated. If it does, the new value is calculated, then updated by using the [NSTextView setString:] method. Here is a simplified version of the code involved.
for formObject in formObjectsInWindow {
NSTextView * objectTextView = [formObject textView];
NSString * updatedValue = [formObject calculateValue];
[objectTextView setString: updatedValue];
}
This works, but if there are a lot of objects, it is somewhat slow. Probably related, the display does not update all at once, you can actually see a "ripple" as the objects are updated, as illustrated in this movie (this movie has been slowed down to 1/4 speed to make the ripple effect more pronounced, but it is definitely visible at full speed).
If you've gotten this far, you might suspect that the calculateValue method is slow, but that isn't the problem. In other places the same code is used and runs at tens of thousands of operations per second. Also, this delay only occurs during update operations, it doesn't occur when the window is first opened, even though the same calculations are required at that time. Here is an example. Notice that when I switch back to the detail view all the NSTextView objects update instantaneously, even though the record changed and all of the values are different.
My suspicion is that the [NSTextView setString:] method is updating the off-screen buffer, then immediately copying that to the on-screen buffer, so that this double buffering is happening over and over again for each item, causing the delay and ripple. If so, I'm sure there must be some way to prevent this so that the screen is only updated at the end after all of the values have been updated. It's probably something simple that I am missing, but I'm afraid I am stumped as to how this is supposed to be done.
By the way, this application does not use layer-backed views, and is not linked against the QuartzCore framework.
I brought up this question with Apple engineers at the WWDC 2018 labs. It turns out the problem is that the setString: method does not mark the NSTextView object as needing display. The system does eventually notice that the text has changed and updates the display, but this happens in an asynchronous process, hence the "ripple" effect. So the workaround is simply to add a call to setNeedsDisplay after calling setString.
[objectTextView setString: updatedValue]
[objectTextView setNeedsDisplay:YES];
Adding this one line of code fixed the problem, no more ripple effect.
I'm told that this is actually a bug, so hopefully this extra line won't be needed in future versions of macOS. As requested, a radar has been filed (41273611 if any Apple engineers are reading this).

What is the recommended frequency for UI changes?

I have a cocoa application window (NSWindow) which position on the screen should be updated frequently (depending on some calculation). As noticed in the documentation, UI changes should be made on the main thread:
void calculationThread()
{
while(true)
{
calculatePosition();
if(positionChanged)
{
dispatch_async(dispatch_get_main_queue(), ^{ setWindowPos(); });
}
}
}
void setWindowPos()
{
[window setFrame:_newFrame display:YES];
}
Now the problem I have is that the window movement is very slow and delayed. After making some profiling I see that the calculation process takes about 40mSec, meaning that I'm queueing up a backlog of UI updates 25 times a second.
I've read here that this might be faster than they can be processed and timer should be used to fire the changes every tenth of a second or so. But, wouldn't it be too slow for the human eye (I mean, in that case the movement wouldn't be delayed but would be lagged causing pretty much the same affect).
I will appreciate some knowledge sharing on this. Actually my main 2 questions are:
Are 25-30 UI updates per second really to much?
If yes, what is the recommended UI changes frequency?
The frequency at which a window can be moved around onscreen without problems will of course depend upon the speed of the user's machine, the video card they have, the size of the window, and probably a bunch of other factors. There is no single good answer to this. However, if you just drag a window around on your screen, you will notice that it can probably be moved very smoothly (unless your machine is very busy or very low on memory or something); I would not expect 25 times per second to produce a problem on a modern Mac. Not even close, in fact.
#RobNapier's points about Core Animation etc. are fine, but overstated I think; there is nothing inherently wrong with changing your UI using a timer or other periodic update if that is what you actually want to do. CoreAnimation is a toolkit for making some types of animation easier; using it is not required, and it is not suited to every problem. Similarly, if you want to make changes that are actually synched to screen refresh then CVDisplayLink is useful, but it doesn't really sound like that's what you want to do.
For your purposes, your basic approach seems fine, although I would suggest adding an NSDate check in order to skip updates if the previous update was less than, say, 1/60th of a second previous. After all, the calculation appears to take 40mSec on your machine, but it might be much faster on some other machine; you want to throttle your drawing to a reasonable rate just to be a good citizen.
So what is the problem, then? I suspect the issue might actually be your call [window setFrame:_newFrame display:YES]. If you look at Apple's docs for that method, they state "When YES the window sends a displayIfNeeded message down its view hierarchy, thus redrawing all views." Each time you call that method, then, you are not only moving your window (which I gather is your intention); you are redrawing all of the contents of the window, too, and that is slow. If you don't need to do that, then that is the overhead you need to eliminate. Call setFrameOrigin: or setFrameTopLeftPoint: instead (which make the semantics clear, that you are moving the window without resizing it or redrawing it), or perhaps just setFrame:display: passing NO instead of YES, and I'm guessing your performance problem will vanish.
If you do in fact need to redraw the window contents every time, then please edit the problem description to reflect that. In that case, the solution will have to involve profiling why your window drawing is slow, and figuring out ways to optimize that, which is an entirely different problem.
As you've discovered, you should never try to drive the UI from a tight loop. You should let the UI drive you. There are three primary tools for that.
For simple problems, AppKit is capable of moving windows around the screen. Just call [NSWindow setFrame:display:animate:]. You can override animationResizeTime: to modify the timing.
In many cases AppKit doesn't give enough control. In those case, the best tool is almost always Core Animation. You should tell the system using Core Animation how you where you want UI elements to wind up, and over what period and path, and let it do the work of getting them there. See the Core Animation Programming Guide for extensive documentation on how to use that. It focuses on animating CALayer, but the techniques are similar for NSWindow. You'll use [NSWindow setAnimations:] to add your animation. Look at the NSAnimatablePropertyContainer protocol (which NSWindow conforms to) for more information. For a simple sample project of animating NSWindow, see Just Say No from CIMGF.
In a few cases, you really do need to update the screen manually at the screen update frequency. I must stress how rare this situation is. In almost all cases, Core Animation is the correct tool. But in those rare case (some kinds of video for instance), you can use a CVDisplayLink to handle this. That will call you each time the screen would like to refresh, giving you an opportunity to update your content to match.

Memory allocation always increasing, despite not using my app

I am currently updating my app, and I have been facing a very strange and complex problem for the last few days. The part of the application that is problematic is made of one UITableViewController that is a list of news, and (after you click on a news) a detail view which is in fact a UICollectionView with as many details CollectionViewCells as there are news.
Each of these can have an infinite amount of elements, and are loaded 20 by 20 when the user scrolls to the bottom of the TableView (or to cell that is at the furthest position right on the CollectionView). Also, inside a DetailsCollectionViewCell, there can be another UICollectionView, containing images.
My problem is that after scrolling a few details views, after behaving normally (ie memory is allocated when I change the page, then stabilize until I change the page again, and so on), the memory allocation start to ramp up slowly but steadily, even if I stop doing anything at all. Also, the CPU usage will go to 100-120% and stay there, whatever I do, even, again, if I don't touch anything. After a while, the UICollectionView and the UITableView will not render any animation anymore, thus loosing the paging, and the inertia when scrolling, and overall resulting in a very poor user experience.
The strange thing is, I can observe these behaviours via XCode 5's Debug Navigator, but when I try to use instrument to find the source of the leaks/allocations, the allocation graph is normal, and I get 40-60 MB mem usage, no more, despite still observing the animations/paging problems.
Has anyone already met such a strange behaviour, and can someone help me in finding the cause of all this fuss ? (maybe by fixing Instruments ?)
Thank you all in advance, don't hesitate to ask me for more infos if needed !
I found the solution to my problems in the meantime.
About the difference in Memory usage between Xcode 5's Debug Navigator and Instruments, it was caused by my use of NSZombies. I have the habit of always setting them on, and that just flew off my mind... To remove them : Product > Scheme > Edit Scheme > Diagnostics > Enable Zombie Objects (just unmark it).
The cause of my CPU usage was an animation that was going on indefinitely in the background on multiple pages. The solution was to first of all stop it as soon as it is not seen/useful anymore, the optimise it by changing my approach (I was using CAAnimation and moved on to using UIView's animate function).
I think I might have pulled the trigger a bit too fast here, but hey... if this can help someone later, then it will not be a waste !

InvalidateRect called too frequently blocks other windows from redrawing

I develop audio plugins, which are run inside their hosts and work realtime. Each plugin has its own window with controls, which often contains some kind of analysis pane, a pretty big rectangle that gets repeatedly painted (e.g. 20-50x per second). This is all working well.
The trouble comes when the user adjusts a parameter - the plugin uses WM_MOUSEMOVE to track mouse movements and on each change calls ::InvalidateRect to make the relevant portion of the window be redrawn. If you move quickly enough, the window really gets quickly repainted, however there seems no time for the host and other windows to be redrawn and these usually perform some kind of analysis feedback too, so it is really not ideal.
No my questions:
1) Assuming the host and other window are using ::InvalidateRect too, why mine is prioritized?
2) How to make ::InvalidateRect not prioritized, meaning the window needs to be invalidated, but it may be later, the rest of the system must get time for their redrawing too.
Thanks in advance!

Resources