29.9 Serializing the view and version

A difficulty in properly serializing a Pop Framework game is that we want to serialize the view information as well as the game information. If you've set your viewpoint to a certain location, direction, and zoom, you'd prefer to have it restored when you reload a saved game.

Since a document can have more than one view open, the default behavior for an MFC CDocument class is to not save any of the view information. The Pop Framework changes this by having a CPopDoc save the view information of the active view and, when loading, signal the active view to load its parameters from the archive being loaded.

Another point to worry about is versioning. When you make repeated builds of a program, you will occasionally change the number of fields in your key structures. If you then try and load an archive file from an earlier build, you'll get a hideous crash, because you'll be writing, say, 1003 bytes of file data onto, say, 998 or 1107 bytes of allocated RAM for the object you think you're reading in. So you'll end up by overwriting or non-initializing some bytes, and when the program goes to read those bytes there will be trouble.

MFC provides a method for versioning by putting an integer version number into the third argument of the IMPLEMENT_SERIAL macros. But changing these numbers is time-consuming and hard to remember to do, particularly as your code is going to have dozens of IMPLEMENT_SERIAL lines. So what we do in the Pop Framework is to treat the string in the program's caption bar as if it were a version name. In order to make this work for you, you need to remember to use the Resource Editor to change the IDR_MAINFRAME string each time you do a new build. You do this with the control sequence View | Workspace | Resource View | String Table | IDR_MAINFRAME | Alt+Enter.

Here's a copy of our code to both serialize the active view and do a version check based on the caption.

void CPopDoc::Serialize(CArchive& ar) 
    /* So as to make sure that (a) I load and save my files with 
        the same build and (b) I don't try and load non-Pop files, 
        I'm going to write a version string at the head of each 
        archive. */ 
    CString cStrAppVersion; 
        /* VERIFY means always evaluate the expression, but if you 
        are in the debug build and the expression is 0, then interrupt 
        just like a failed assertion. */ 

    if (ar.IsStoring()) // Save 
        ar << cStrAppVersion; 
        ar << _pgame; 
    else //Load 
        CString cStrFileVersion; 
        ar >> cStrFileVersion; 
        if (cStrFileVersion.GetLength() > 256) //Then you opened some 
            totally bogus file cStrFileVersion = 
            cStrFileVersion.Left(16) + "..."; //Truncate 
        if (cStrFileVersion != cStrAppVersion) 
            CString message = "File Version:\n" + cStrFileVersion + 
            "\n\nDoesn't Match App Version:\n" + cStrAppVersion + 
                "\n\nWill Abort the Load."; 
            ::AfxThrowArchiveException(0, NULL); /* This throws an 
                exception which is caught inside the base class 
                CDocument::OnOpenDocument call and then closes the 
                badly opened document. */ 
        delete _pgame; /*At CPopDoc construction a document creates a 
            default cGame *_pgame. So if we're loading a game we need 
            to delete the existing game first or there will be a 
            memory leak.*/ 
        _pgame = NULL; 
        ar >> _pgame; /* Uses CreateObject to creates a new cGame* 
            object of the correct child class, copies the new objects 
            fields out of the file, and places the pointer to the new 
            object in _pgame. Constructor makes pnewgame-> 
            _gameisfreshlyinitialized be TRUE, so when you press ENTER 
            it won't reseed. The CPopDoc constructor calls 
            setGameClass. */ 
        _pgame->setGameover(TRUE); /* So you can press ENTER to 
            actually start it running. _gameisfreshlyinitialized is 
            true, as mentioned just above, so ENTER won't randomize 
            things. */ 
            /* We used to not bother to try to load the CPopView info, 
                and we just called UpdateAllViews(NULL, 
                CPopDoc::VIEWHINT_STARTGAME, 0); But as of 9/2001, 
                we wrap the CArchive in a cArchiveHint and pass it to 
                the views. */ 
        cArchiveHint *parchivehint = new cArchiveHint(&ar); 
            parchivehint); /* This call jumps right to 
            CPopView::OnUpdate, so the ar information 
            is still good. */ 
        delete parchivehint; 
        parchivehint = NULL; 

The CPopView::OnUpdate code process the archive hint in the most obvious kind of way.

void CPopView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) 
    //If you've just loaded a new game, use the game's initialization 
    //code on this view. 
        if (pHint && pHint->IsKindOf(RUNTIME_CLASS(cArchiveHint))) 
            CArchive *parchive = ((cArchiveHint*)pHint)->parchive(); 
    //More code for all the other lHint cases.... 

Since our cPopDoc::Serialize now does version-checking on its own, we don't really need to use numbered file extensions like *.p21 as was suggested in the tweaking the file Dialog subsection of 23.9. So the Pop framework just uses *.pop for its file extensions.

    Part I: Software Engineering and Computer Games
    Part II: Software Engineering and Computer Games Reference