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(cStrAppVersion.LoadString(IDR_MAINFRAME)); /* 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. */
CObject::Serialize(ar); if (ar.IsStoring()) // Save { ar << cStrAppVersion; ar << _pgame; getActiveView()->Serialize(ar); } 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."; MessageBeep(MB_ICONEXCLAMATION); ::AfxMessageBox(message); ::AfxThrowArchiveException(0, NULL); /* This throws an exception which is caught inside the base class CDocument::OnOpenDocument call and then closes the badly opened document. */ return; } 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); UpdateAllViews(NULL, CPopDoc::VIEWHINT_LOADINGARCHIVE, 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 (lHint == CPopDoc::VIEWHINT_LOADINGARCHIVE) { if (pHint && pHint->IsKindOf(RUNTIME_CLASS(cArchiveHint))) { CArchive *parchive = ((cArchiveHint*)pHint)->parchive(); Serialize(*parchive); } return; } //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.
|
No comments:
Post a Comment