back

MacOS X screensavers programming
common errors and how-to avoid them


This is my little black page of common (and easily avoidable) errors in MacOS X screensaver programming. Errors are presented in strictly random order or, to be more exact, in the order I discovered them (and resolved).

I hope you find it useful, and happy coding to all !!

Small Index:

  1. Preference not saved (weak error)
  2. [self stopAnimation] don't stop animation (very dangerous error)
  3. Unnecessary exported symbols (very dangerous error)
  4. OpenGL "white shot" on startup (weak error, visual glitch)


Preference not saved (weak error)

Sometimes preferences are not correctly flushed. Adding a call to synchronize method solves the problem.
Example:
ScreenSaverDefaults *defaults = [ScreenSaverDefaults defaultsForModuleWithName:@"fooSaver"];
[defaults setInteger: 1 forKey: @"test"];
[defaults synchronize];


[self stopAnimation] don't stop animation (very dangerous error)

The following code sems to be correct (and it is), however it doesn't act as one might expect:

if( [self isAnimating] )
[self stopAnimation]
// do something very dangerous while screensaver is animating
// like deallocating and re-allocating some memory.
[self startAnimation]

Note that the best (or worst, it depends) place to do this mistake is in the method that changes user preferences. Adding two more lines helps discover the problem:

if( [self isAnimating] )
[self stopAnimation]
if ( ![self isAnimating] ) {
// do something very dangerous while screensaver is animating
// like deallocating and re-allocating some memory.
[self startAnimation]
}

The bug shows up itself differently in the first and second example, however it is the same bug. The problem is that the [self stopAnimation] call does not act immediately.

It seems (even though I cannot be sure of that) that after a [self stopAnimation] call, it is executed at least one [self animateOneFrame]. Pretty dangerous, right ? So in the first example it could be happen that the animateOneFrame is dne in the middle of the allocation and deallocation (terrific), while in the second case the allocation (inside the if statement) isn't done at all.

BTW, don't think that this is a MacOS X bug, it isn't. The bug (or feature) is due to Objective C way of calling method functions _and_ multitasking.

The solution is this: never make an allocation/deallocation outside the animateOneFrame method. For example you cuold set up a bunch of temporary settings and a BOOL flag to force animateOneFrame to call an initializer function. See Coral or Rocks screensaver source for an example.


Unnecessary exported symbols (very dangerous error) - thanks to Mike Trent <mtrent@best.com>

The best thing to do is to report his e-mail...

--------------------
When I look at your binary with nm I see your module is exporting some symbols:

[localhost:Coral.saver/Contents/MacOS] mike% nm -g Coral | grep -v " U "
00000000 A .objc_category_name_CoralView_ErasingComponent
00000000 A .objc_class_name_CoralView
0000ac34 T _make_color_loop
00009e84 T _make_color_ramp
0000b2e0 T _make_random_colormap
0000acf0 T _make_smooth_colormap
0000b1cc T _make_uniform_colormap

There is a risk some other module may come along and define the same symbol, thus interfering with your module (and vice versa). For example, if you made a second module that defines a function called make_color_loop() you will not be able to load both modules into System Preferences.

ObjC symbos must always be exported, so prefixing your symbols with "CoralView" is pretty smart. There are those who advocate a java-like reverse-domain-name convention (ComAppleScreenSaver, or whatever) but I can see how that won't always work for shareware.

The C function symbols here (make_color_loop, make_color_ramp, etc.) can probably be hidden. If these functions are used in only one .c or .m file you can mark these symbols as static. If these functions live in one .c or .m file and are used in another .c or .m file, you can protect them by marking them __private_extern__. This works the same as 'static' in your code, but it means you can share the symbol within your program.

It's best to get your exports down to a small number of ObjC exports. For example, using nm:

[localhost:Coral.saver/Contents/MacOS] mike% nm -g Coral | grep -v " U "
00000000 A .objc_category_name_CoralView_ErasingComponent
00000000 A .objc_class_name_CoralView
--------------------

So, open a terminal, go inside your screensaver package (you can navigate it like a directory) and find the module (it's inside Contents/MacOS). Type nm -g module_name | grep -v " U " and look at the answer. If there are symbols other than .objc_something, you should revise your code.

BTW, this is also useful for "normal" users, since this is a common cause of screensaver conflicts.


OpenGL "white shot" on startup (weak error, visual glitch) - thanks to Mike Trent <mtrent@best.com>

On startup most of OpenGL screensavers fade to black, then to white and again to black. After this they starts. Again, as pointed out by Mike Trent and Eric Peyton in this post to a mailing lits, the error could be easily removed.

Both Mike Trent and Eric Peyton pointed me to the right solution. The screen saver fades to whatever is initialized in StartAnimation.
Mike's solution is summarized here:

- (void)startAnimation
{
NSOpenGLContext *context;

[super startAnimation];

context = [_view openGLContext];
[context makeCurrentContext];
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
glFlush();
}

I also noticed that if you go ahead and draw your first frame in
here, it'll fade from black directly to that frame. Exactly what I
was looking for!

Regards,
Aaron

BTW, in the post the last call was [context flushBuffer] instead of glFlush(), however, maybe for the same problem of stopAnimation, the above code always orks, while with [context flushBuffer] it doesn't.