Packed Sheets


A packed sheet is a way of storing multiple Georges sheets in a binary format. What a packed sheet does is load all of the sheets/forms into a single binary file that can be serialized by a loader that can automatically populate the proper data. It also will update the packed sheet (if informed to) to either create a new packed sheet (if one does not already exist) or add new entries to a packed sheet.

Process of Loading

For packed sheets, you must declare a class that will be used by the form packer:

1 - Create a class (or struct) that is needed to conform to the following interface:

 1struct TMyLoader
 3     /**
 4      * \brief Here you read in the form if necessary, storing it in members of the TMyLoader class
 5      */
 6     void readGeorges (const NLMISC::CSmartPtr<NLGEORGES::UForm> &form, const NLMISC::CSheetId &sheetId);
 8     /**
 9      * \brief Here you write a standard NeL serial for all the member of TMyLoader that need to be packed.
10      */
11     void serial(NLMISC::IStream &f);
13     /**
14      * \brief This method returns the version of the packed sheet.
15      * The coder needs to increment the number each time the packed sheet changes and the serial method
16      * is updated so the code will discard old packed sheets.
17      *
18      * \return uint This must always return an integer. Unsigned is probably best.
19      */
20     static uint getVersion();
22     /**
23      * \brief Here you can write custom code for when the packed sheet loader needs to remove an old sheet.
24      * \note This is rarely used.
25      */
26     void removed();

Loader Structure/Class Notes

  • A good example of a packed sheet loader is CSoundSerializer in sound_bank.cpp
  • readGeorges would do something like load variables in from the sheet using UFormElm's and the like and then use that data to create a new object. It essentially is form loading\.
  • serial is basic NeL serialization. This is for when loadForm wants to save or load the loader structure from a stream. It's the same thing as readGeorges except you're doing f.serial(myVar); instead of myVar=root.getValueByName(".MyVar"); Examples of serialization are everywhere in NeL.
  • removed() is called when the loadForm update cycle determines that the file that created this particular object is no longer in the file system. Essentially it just clears the members of the class as a destructor would.
  • getVersion() just tells the version number of the sheets. Any time you change the loader structure/class or the sheet forms for this loader you will want to increment the version number so that old versions of sheets get dropped from the packed sheet. This is very important.

Now that you have your class that conforms to the requirements of the packer and loader you have to create a container for the form loader to populate with sheets. This must always be like a map similar to the one below.

2 - Declare a container for all the loaded sheets:

std::map<CSheetId, TMyLoader> MySheets;

Usually Nevrax declares this globally but there is no constraint about that. It is used for a single call to loadForm (which is described below) and not for all sheets in an application. This method loads all of the sheets into the container.

3 - Call the packed sheet loader:

1loadForm( "my_extension", "packed_sheet_file_name.packed_sheets", MySheets, true);

Essentially what loadForm does is if told to go through the files with my_extension for an extension and check if there are newer ones than in the packed sheet, new ones period, or if some have been removed and marks these files appropriately in memory. It then loads all of the forms and puts them into cached lists and removes any files it found missing from the packed sheet. If it changed the container at all (added new files, updated changed files, or removed old files) it saves the file back out as the file passed in the parameter, which is packed_sheet_file_name.packed_sheets in the above example. In the process your map will be populated either via serialization (unchanged sheets) or by the readGeorges method (added or updated sheets.)

Loading Notes

  • The packed sheet form loader requires that the file extension be .packed_sheets - however the logic lets you get away with .packed_sheetsbar for example. As a matter of practice though the packed sheet extension should stay with the standard.
  • The above function actually converts "my_extension" into a single-entry std::vector and calls:

void loadForm (const std::vector< std::string > &sheetFilters, const std::string &packedFilename, std::map< NLMISC::CSheetId, T > &container, bool updatePackedSheet=true, bool errorIfPackedSheetNotGood=true)

  • loadForm is a template function, as seen above it will deduce it's argument from the ''container'' paramenter, where you will pass ))MySheets((.

Summary / Details

As you have defined you own class to hold packed data, and as you declared you own container to hold them (in fact, the container MUST be a std::map<CSheetId, you_class> ), then it's easy to access the container with a valid sheet id to retreive your data

There is no progress callback for during packing - only a debug log every 10 seconds. But when you only load a packed sheet (without updating it with current sheets data) the loading time is very short.

CSheetId and sheet_id.bin file is a manner to uniquely identify sheet file with a 32 bit integer.

There is a (private) tool that builds the sheet_id.bin from a collection of files. The sheet_id.bin is always apended, never are old sheets removed. This ensures persistent data storage to be efficient and stable.

There is also a tool in the public CVS for retrieving information on sheets called ''disp_sheet_id''.

So, when you program starts, you can init the sheet id system by loading the sheet_id.bin file, than CSheetId instance can be created from file name or from 32 interger and converted to/from the int or filename.

It has to be in your search path, then you call the init method in CSheetId (or somethink near that)