Creation of a high score table (Part II): saving to a file store

in
Keywords:

Overview

In Symbian OS, there is several ways to write data on the file system :
-  using classical file interface
-  using streams and store
-  using the DBMS server

All the options could be suitable for our purpose. The file API is not that easy to use for writing arrays of C structure. The DBMS may be quite too complicated to the treatment we need to do. So I choose to use a Direct File Store. This is generally the best option when you want to manage your data in RAM memory (i.e. not inserting element directly on the file system).

The structure of a direct file store is as below: directstore.png

The direct file store could contain several data stream, each of those could be accessed independently. However, once written, they cannot be modified nor deleted (you have to use a permanent file store if you need to do so). you can just delete the whole store.

Stream are easily read or written using << or >> operators. And are quite safe with the possibility to commit or rollback any changes if an error occurs.

Writing the TScore to a file store

The TScore class presented in Creation of a high score table (Part I): using a CArray has two new member functions: ExternalizeL() and InternalizeL():

class TScore
{
public :
       TScore();
       TScore(TInt aScore);
       TScore(TInt aScore,const TDesC& aName);
       TInt Score();
       TInt Score(TPlayerName& aName);
       void ExternalizeL(RWriteStream& aStream) const;
       void InternalizeL(RReadStream& aStream);
       TInt operator>(const TScore& aScore);
       TInt operator<(const TScore& aScore);
       TInt operator==(const TScore& aScore);

public :
       TInt        iValue;
private:
       TPlayerName iName;
};

 

The ExternalizeL() function is responsible for writing a TScore structure to a file store:

void TScore::ExternalizeL(RWriteStream& aStream) const
{
 //
 // Write the score as a 32bit integer
 // (You have to use a WriteXXX function since no << operator
 // is available for integers)
 //
 aStream.WriteInt32L(iValue);
 //
 // Write the name
 //
 aStream << iName;
}

The InternalizeL() function reads a TScore from the store:

void TScore::InternalizeL(RReadStream& aStream)
{
 //
 // Read the score
 //
 iValue  = aStream.ReadInt32L();
 //
 // Read the name
 //
 aStream >> iName;
}

Writing the whole CArray

The score table (iScoreTable) is owned by the CGame class which is then responsible for externalizing the whole array. The class definition in itself has not been really changed:

class CGame : public CBase
{
public :
 static CGame *NewL();
 static CGame *NewLC();
 void ScoreDisplay();
 void AddScoreL(const TScore& aScore);
 void SaveScoreL(const TDesC& aStore);
 void LoadScoreL(const TDesC& aStore);
 void ResetScore();
 ~CGame();

private:
 void ConstructL();

private:
 CArrayFixSeg<TScore> *iScoreTable;
 RFs iFs;  // FileServer Session ID
};

It only has two new functions: SaveScoreL() and LoadScoreL() and a data member to hold a session id to the file server (iFs).

The connection to the file server is made in the CGame::ConstructL(). If the connection cannot been established, I decided to make the function leave since my (hypothetical) game would not run without it:

//
// Connect to the file server and create the directory if needed
//
User::LeaveIfError(iFs.Connect());
iFs.MkDirAll(KFileStore);

The MkDirAll() function will create any necessary directory that may be present in the path to my high score file (specified by KFileStore). This is probably useful for the first execution of my game but will have no action for the following ones.

The big work is made in the SaveScoreL() function and requires several steps:
-  create a full path name for the file, resolving any joker or default directory: this is done using TParse class and the Parse() call:

TParse  parsedName;
iFs.Parse(aStoreName,parsedName);

-  create an empty file store (replacing the existibg one - if any) and open it in write mode (ReplaceLC()

CDirectFileStore::ReplaceLC(iFs,parsedName.FullName(),EFileWrite);

-  give it the type you want. Here, I choose Direct File Store because it is the simplest choice when you want to manage all the data in RAM memory rather than on the file system:

store->SetTypeL(KDirectFileStoreLayoutUid);

-  create the data stream inside the store:

RStoreWriteStream stream;
TStreamId id = stream.CreateLC(*store);

-  write all the data into the stream:

//
// Write the number of score table entries in the store
//
TInt count= iScoreTable->Count();
stream.WriteInt32L(count);

//
// Then write each entry
//
TInt i;
for(i=0;i<count;i++)
{
 stream << iScoreTable->At(i); // This will call the TScore::ExternalizeL method
}

-  and finally commit the changes to the stream and to the store:

//
// Commit the changes to the stream
//
stream.CommitL();
CleanupStack::PopAndDestroy(); // stream id

//
// Set the stream in the store and commit the store
//
store->SetRootL(id);
store->CommitL();
CleanupStack::PopAndDestroy(); // store

The complete code in one shot:

void CGame::SaveScoreL(const TDesC& aStoreName)
{
 //
 // Create a direct file store that will contain the score table
 // (Erase previous one)
 //
 TParse  parsedName;
 iFs.Parse(aStoreName,parsedName);
 CFileStore* store = CDirectFileStore::ReplaceLC(iFs,parsedName.FullName(),EFileWrite);
 store->SetTypeL(KDirectFileStoreLayoutUid);

 //
 // Create the hi-score stream
 //
 RStoreWriteStream stream;
 TStreamId id = stream.CreateLC(*store);

  //
  // Write the number of score table entries in the store
  //
  TInt count= iScoreTable->Count();
  stream.WriteInt32L(count);

  //
  // Then write each entry
  //
  TInt i;
  for(i=0;i<count;i++)
  {
      stream << iScoreTable->At(i); // This will call the TScore::ExternalizeL method
  }

  //
  // Commit the changes to the stream
  //
  stream.CommitL();
  CleanupStack::PopAndDestroy(); // stream id

  //
  // Set the stream in the store and commit the store
  //
  store->SetRootL(id);
  store->CommitL();
  CleanupStack::PopAndDestroy(); // store
}

 
  score2.zip
score2.zip



Tutorial posted March 3rd, 2003 by webmaster categories [ ]

> Creation of a high score table (Part II): saving to a file sto

GOOD, thanks.

TParse issue

The TParse class is a little bit big to be allocated on the stack (in SaveScoreLand LoadScoreL). It would be better to allocate it on the heap this way:

TParse *parsedName;
parsedName=new(ELeave)TParse;
CleanupStack::PushL(parsedName);
..... function code....
CleanupStack::PopAndDestroy(); // parsedName

how can we check if a file does exist or not

e.g.: we need to check if c:\\system\\app\\mygame\\socre.dat exists or not at the very beginning executioin. thanks,

Re: how can we check if a file does exist or not

I know two methods :
-  you try to open your socre.dat file using Open(): if it returns KErrNotFound, the file does not exist and you can create it

-  you check its existence using TFindFile (get the latest SDL, this is well explained)

> Re: how can we check if a file does exist or not

U Can use BaflUtils::FileExists(). also

Meenu