Thursday, 31 December 2009

iPhone - Serialize this

Just implemented saving and loading. It's really really really simple to do.

The basic concept is the following.


// Grab the default archive

NSUserDefaults *archive = [NSUserDefaults standardUserDefaults];


// Create a magic key for the value you're about to load or save

NSString *key = [[NSString alloc] initWithFormat:@"myData"];


// To save

[archive setFloat:vector->x forKey:vectorKeyX];


// To load

vector->x = [archive floatForKey:vectorKeyX];


// Don't forget to release the key

[key release];


Armed with these tools I went with the approach to save out as much of the game state required to re-launch the game back into same state. To do this I set up the engine to process a number of scenes. Each scene is responsible for a number of objects. When I save out the game data, I go through all the objects and save out what scene they're in, so on launch, I reload all the scenes that were currently loaded, which will then create all the required objects. I then pass through all the objects and load in the required data to get them working the way they were.

I organised my objects to create unique keys depending on their object index and the internal data index I was processing. Enough talk, here's the code.

// Creates the unique key for our object

+(NSString*)getSerializeKey:(uint)index key:(NSString*)key

{

if( key == nil )

{

return [[NSString alloc] initWithFormat:@"Obj%i", index];

}

else

{

return [[NSString alloc] initWithFormat:@"[email protected]%i", key, index];

}

}


// Creates our key and passes it into the serializeInternals function for saving the internal data

-(void)serialize:(NSUserDefaults*)archive saving:(BOOL)saving key:(NSString*)key index:(uint)index

{

NSString *objectKey = [ObjectBase getSerializeKey:index key:(NSString*)key];

// The internal index will aid the creation of unique keys for our internal data

uint internalIndex = 0;

[self serializeInternals:archive saving:saving key:key index:&internalIndex];

[objectKey release];

}


// Overridden by child classes to save more data

-(void)serializeInternals:(NSUserDefaults*)archive saving:(BOOL)saving key:(NSString*)key index:(uint*)index

{

// Save our object position by default

[self serializeVector:&position archive:archive saving:saving key:key index:index];

}


// Save/load our scenes with a unique scene key

+(void)serializeScene:(SceneBase*)scene archive:(NSUserDefaults*)archive saving:(BOOL)saving key:(NSString*)key

{

NSString *sceneKey = [[NSString alloc] initWithFormat:@"[email protected]", key];

if( saving )

{

[archive setInteger:scene ? scene->index : -1 forKey:sceneKey];

}

else

{

NSInteger sceneIndex = [archive integerForKey:sceneKey];

[gEngine addScene:sceneIndex];

}

[sceneKey release];

}


// The keys for the objects internal vectors are concatenated by the object key and the internal index key

-(void)serializeVector:(Vector3*)vector archive:(NSUserDefaults*)archive saving:(BOOL)saving key:(NSString*)key index:(uint*)index

{

NSString *vectorKeyX = [[NSString alloc] initWithFormat:@"[email protected]%iX", key, *index];

NSString *vectorKeyY = [[NSString alloc] initWithFormat:@"[email protected]%iY", key, *index];

NSString *vectorKeyZ = [[NSString alloc] initWithFormat:@"[email protected]%iZ", key, *index];

if( saving )

{

[archive setFloat:vector->x forKey:vectorKeyX];

[archive setFloat:vector->y forKey:vectorKeyY];

[archive setFloat:vector->z forKey:vectorKeyZ];

}

else

{

vector->x = [archive floatForKey:vectorKeyX];

vector->y = [archive floatForKey:vectorKeyY];

vector->z = [archive floatForKey:vectorKeyZ];

}

[vectorKeyX release];

[vectorKeyY release];

[vectorKeyZ release];

(*index)++;

}


That's the basic jist of it, create unique keys for saving and loading the data.

Monday, 28 December 2009

iPhone - Posix Threads for the win

So I've been looking into multithreading my iphone engine. Switching from using a timer to threads bumped up my frame rate by about 10% instantly. I found that using Posix threads were slightly (1-2%) faster than NSThreads. Currently, I've set my engine up to have 2 additional threads. One to handle the game updating and rendering, and a second to handling job loading, with jobs being streaming in audio and textures. To handle synchronisation I'm using spin locks, which are basically while loops.

Things to note
  • When setting up your thread you must always, start and release an NSAutoRelease pool.
  • The pool should be restarted every now and then to release the objects released during the runtime of the thread.
  • When using Posix threads it's recommended you run an empty NSThread just to let the OS know that the app will be multithreaded.
  • Setting up an OpenAL context stalls the engine by around half a second on start up, so I tend to launch it in the jobs thread to avoid stalls.
  • Bumping up the priority of the render thread does improve performance slightly.

Sample code
When called the start function will launch a dummy thread which does nothing, so the thread will terminate immediately. It then launches two threads, a game thread and a jobs thread. The game thread is given a higher priority than the jobs thread.

-(void)start

{

// iPhone SDK recommends launching an empty NSThread when using POSIX threads with Cocoa applications

[NSThread detachNewThreadSelector:@selector( emptyThread ) toTarget:self withObject:nil];

// Create the game thread using POSIX routines.

createThread( &PosixGameThread, self, 0, +2 );

// Create the jobs thread

createThread( &PosixJobsThread, self, 0, -2 );

}

Empty thread does nothing, so the thread will terminate naturally upon launching.

-(void)emptyThread

{

}


Create Thread wraps the PSIOX routines required to launch a thread with a set priority.

void createThread(void *(*start_routine)(void*), void *restrict arg, int prioritySet, int priorityAdj)

{

// Create the game thread using POSIX routines.

pthread_attr_t attr;

pthread_t posixThreadID;

int returnVal;

returnVal = pthread_attr_init( &attr );

assert( !returnVal );

returnVal = pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_DETACHED );

assert( !returnVal );

returnVal = pthread_create( &posixThreadID, &attr, start_routine, arg );

assert( !returnVal );

struct sched_param param;

int policy;

pthread_getschedparam( posixThreadID, &policy, &param );

assert( !returnVal );

if( prioritySet != 0 )

{

param.sched_priority = prioritySet;

}

else if( priorityAdj != 0 )

{

param.sched_priority += priorityAdj;

}

assert( param.sched_priority > 0 && param.sched_priority < 100 );

returnVal = pthread_setschedparam( posixThreadID, policy, &param );

assert( !returnVal );

returnVal = pthread_attr_destroy( &attr );

assert( !returnVal );

}


The game thread owns the OpenGL context and spins around the while loop updating the frames while the game is running, while making a call lazily to refreshReleasePool to restart the pool.

void* PosixGameThread(void* data)

{

// Note: autorelease pools should be created and released more frequentley if using this feature

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

uint poolRefreshCounter = 0;

EAGLView *view = (EAGLView*)data;

[view setupGameThread];

[pool release];

pool = [[NSAutoreleasePool alloc] init];

view->runningGame = YES;

while( view->runningGame )

{

if( view->paused == NO )

{

[view update];

#if DEBUGON && TARGET_IPHONE_SIMULATOR

// 20 frames a second in debug

usleep( 50000 );

#endif

}

refreshReleasePool( &pool, &poolRefreshCounter, 100 );

}

[view shutdown];

[pool release];

return NULL;

}


The jobs thread works the same way as the game thread, but owns the OpenAL context instead and starts the file I/O operations requested by the game thread.

void* PosixJobsThread(void* data)

{

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

uint poolRefreshCounter = 0;

EAGLView *view = (EAGLView*)data;

while( view->runningGame == NO )

{

usleep( 1000000 );

}

[view->audioManager load];

usleep( 200000 );

while( view->runningGame )

{

if( [view startJobs] )

{

// 5 jobs a second

usleep( 200000 );

}

else

{

usleep( 1000000 );

}

refreshReleasePool( &pool, &poolRefreshCounter, 10 );

}

[pool release];

return NULL;

}


Refresh Release Pool handles, reseting the NSAutoreleasePool lazily for both threads.

void refreshReleasePool(NSAutoreleasePool **pool, uint *count, const uint target)

{

if( (*count)++ > target )

{

[*pool release];

*pool = [[NSAutoreleasePool alloc] init];

*count = 0;

}

}


Any questions? Feel free to ask.

Wednesday, 23 December 2009

Stock market listings bug

Just patched a lingering stock market listing bug. The API we get the latest stock market lists and news feeds from have been modified.

I've patched the market lists today, so you should now have the correct stock market listing (in case any new companies have been added/removed from the markets) and I'll get the news feed fixed soon.