Guiliani  Version 2.5 revision 6773 (build 33)
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Streaming

Page contents

What is streaming?

Streaming means the reconstruction of a GUI at runtime from a descriptive file. This file can be of arbitrary format - usually this will be an XML description (for human readability) or proprietary binary file (for optimized performance). Such a streaming file contains all required information to construct the GUI from it, such as:

  • Which objects there are
  • The Position and size of these objects
  • Images used to visualize the GUI
  • Texts to be displayed on Buttons, Textfields etc.
  • CommandObjects assigned to objects
  • Attributes specific to a certain object (e.g. value range of a Slider)
  • 3D scenes, including 3D Objects (camera, mesh, light)
  • and many more...

This information must be present within the streaming file, but the way in which it is represented is variable. Therefore, the file may have any format - and in fact it is not limited to a physical file somewhere on a data storage, but could as well be read from a streaming source such as a network connection.

Another core concept of streaming is that each streamable object (i.e. each object within the GUI) is capable of reading and writing itself from/to a stream. In other words the object itself knows which attributes it must read/write in order to completely (re)store its current state. This has the advantage that the overall complexity of the system is greatly reduced, since each object encapsulates its own streaming details within its own code.

The main components involved in streaming are therefore the following:

Component Guiliani class description
Streamable object CGUIStreamableObject Base class for all object which are capable of reading/writing themselves from/to a stream.
Stream Reader CGUIStreamReader Abstract interface for reading data from a stream. This offers for example APIs for reading integers, floats, string etc. from a stream and has to be re-implemented per file format. (e.g. one StreamReader for XML, another one for Binary...)
Stream Writer CGUIStreamWriter Abstract interface for writing data to a stream. This offers for example APIs for writing integers, floats, string etc. to a stream and has to be re-implemented per file format. (e.g. one StreamWriter for XML, another one for Binary...)
Factory CGUIFactory This component is responsible for dynamically creating objects at runtime. Before you can tell an object to read itself from a stream, you must obviously create this object first. This is done within the Factory, which instantiates an object of a given class. The class is specified by a dedicated identifier within the streaming file.

Implementation details

Streaming can be divided into two parts: writing GUI definitions into a stream and reading GUI definitions from a stream.

A stream can be anything that can be represented by an eC_File, normally one stream reader or writer is responsible for one file currently streamed.

GUI definitions are the entirety of controls (GUI objects), their layouters, behaviours and commands, usually seen as one control which can be a CGUICompositeObject whose children are then part of the GUI definition.

For writing GUI definitions, the following steps must be performed after creating a GUI. The example uses the XML writer:

CGUIStreamWriter* pkStreamWriter = new CGUIStreamWriterXML();
GETRESMANAGER.WriteDialogToFile("ExampleGUI.xml", pkExampleGUIMainObject);
delete pkStreamWriter;

The method CGUIResourceManager::WriteDialogToFile creates a file with the given name, writes its own comment tags and then calls the supplied object's CGUIObject::WriteToStream method.

For reading (sometimes called streaming) GUI definitions, the following steps must be performed. The example uses the XML reader:

CGUIStreamReader* pkStreamReader = new CGUIStreamReaderXML();
// If there are custom controls, user factories have to be added like this:
GETFACTORY.AddUserFactory(new MyFactory());
CGUIObject* pkStreamedControl = GETFACTORY.LoadDialogFromFile("ExampleGUI.xml");
delete pkStreamReader;

The method CGUIFactoryManager::LoadDialogFromFile opens the file and starts reading it, using the factories to create objects and calling ReadFromStream on the new objects.

The same mechanism can be used for writing and reading global settings like properties (see CGUIProperties::WriteGlobalPropertiesToFile and CGUIProperties::ReadGlobalPropertiesFromFile). ID mappings for images, fonts and sounds can be written and read by calling the appropriate methods of CGUIResourceManager.

Streamer Stack

CGUIStreamReader and CGUIStreamWriter are implemented in a stacked pattern. It is possible to create multiple stream readers or writers, i.e. the last created streamer becomes the active one. Is the last created streamer destroyed when it is not needed anymore, the old streamer becomes active again (is top of the stack).

streamstack.png
Stacked stream reader - the operations (left) invoke handling of the stack (right).
  • The stream writer has its own stack, which is independent of the stream reader stack. Usage is analog to the reader.
  • Ensure to keep the order of creation and destruction of multiple streamers, a secondly created streamer needs to be destroyed first.
  • Initially no stream readers or writers are created, both stacks are empty.

How to implement streaming support for your own controls

This section will show you how to add support for Guiliani's streaming feature in your own controls. Adding streaming support is always done by implementing to methods which your object automatically inherited from CGUIStreamableObject, namely ReadFromStream and WriteToStream.

As the names already imply, ReadFromStream() will include be called whenever your object should read itself from a stream, while WriteToStream() will be called in order to let your object dump its current status to a stream. The two functions are therefore generally quite similar and once you have implemented one of them, it will be straightforward to write the other one accordingly.

Changes in the header file

Since you are going to re-implement the streaming methods within your own class, you will have to declare them within your header file. Note that the methods are typically encapsulated within the dedicated GUILIANI_STREAM_GUI and GUILIANI_WRITE_GUI defines, in order to allow the respective code section to be removed from the build, if they are not required.

#ifdef GUILIANI_STREAM_GUI
virtual void ReadFromStream();
#endif
#ifdef GUILIANI_WRITE_GUI
virtual void WriteToStream(const eC_Bool bWriteClassID = false);
#endif

Changes in the CPP file

Within the CPP file you will have to add the implementation of the ReadFromStream and WriteToStream methods, as well as some required includes and - although not necessarily required - it is strongly recommended to also add a define for the current version of your control and set a readable XML tag for your control's name.

Required includes

The following includes will be required so that you gain access to the framework's streaming API's.

#include "GUIStreamWriter.h"
#include "GUIStreamingException.h"

Readable XML name tag

Setting an XML tag with your control's name is optional. It will enhance readability of your control's XML output. You should place the following code in your object's default constructor (since this will be used by the streaming factory to instantiate it). Obviously GUIImage is just an example - you can freely choose a name here.

#ifdef GUILIANI_WRITE_GUI
SetXMLTag("GUIImage");
#endif

If you want your streaming code to be downward compatible towards various versions of your control, you should add a define with your control's current version. You can then check for this version and have specific code paths in your streaming methods for varying versions.

#define GUIIMAGE_CLASS_VERSION 1

The ReadFromStream method

This method will be called by the framework if an instance of your class has been instantiated while parsing a streaming file. First of all the default constructor of your class is being called by the respective factory (CGUIFactory), afterwards ReadFromStream() is being called on the newly created object in order to initialize it with the values found in the streaming file. Let's go through the demo code below line by line to see how it works.

In the first line you will see a call to a DeInit method. Per definition your control must be capable to deal with repetitive calls to the ReadFromStream method. That means it should not lead to any unexpected behaviour and/or memory leaks if ReadFromStream gets called several times. So if you have to do any clean-ups (like e.g. freeing previously requested resources), you should do this here.

The next line reads the control's class version from the streaming file. Noteworthy here are three things:

  1. the usage of the GETINPUTSTREAM macro. Using GETINPUTSTREAM grants you access to the currently used stream reader, without any need of knowing whether you are currently streaming from an XML or binary file.
  2. the usage of the READ_INT macro. This will read an integer value (in this example the version of the class) from the current input stream. The parameter which you have to hand over is the expected XML tag. Guiliani will use this to check whether this is actually the name of the read tag and throw an exception if this is not the case.
  3. the usage of the predefined CGUIStreamableObject::XMLTAG_CLASSVERSION string. This is a predefined tag name which is to be used whenever reading a class version from a stream. For proprietary tags such as the ImageID or StretchBlit tags that you find later in the demo code, you can freely choose a tag name.

The next block checks the read class version against the current implementation's version define and throws an exception if it does not match. Here you might as well implement individual code paths for varying object versions (for example a newer implementation of a control might have more parameters than an older one). Note that the exception which is being thrown here is of type CGUIStreamingException, which allows you to hand over additional information, such as the line number within the streaming file which caused the exception (retrieved by using the CGUIStreamReader::GetCurLine() method).

Do not forget to call your base class streaming implementation. Skipping this will leave all your inherited attributes un-initialized! It is important to do this before streaming of your attributes to get a consistent order when listed in the GSE Editor or within the xml files.

The three lines following that block read this object's individual attributes from the stream. You can see that there are different interfaces available for reading varying types of data, such as image IDs, boolean values or integers. You should obviously use the one best matching the variable that you wish to set. Also make sure that you are using the same tag names (e.g. StretchBlit) for both reading and writing that value.

Fianlly you may want to put other required initialization code here as well, such as requesting an ImageID which you have just read from the stream. This is done in the next line of the demo code.

#ifdef GUILIANI_STREAM_GUI
{
// Deinitialize
DeInit();
// Read and check class version
eC_Int iVersion = GETINPUTSTREAM.READ_INT(XMLTAG_CLASSVERSION);
if( iVersion > GUIIMAGE_CLASS_VERSION)
{
throw GUILOG_EXCEPTION(CGUIStreamingException("", GETINPUTSTREAM.GetCurLine()) ,
"CGUIImage::ReadFromStream: Incompatible class version! "
"Current version is " + eC_String(GUIIMAGE_CLASS_VERSION) +
", read version is " + eC_String(iVersion) + ".\n");
}
//Read attributes of base class
//Read attributes
m_eImage = GETINPUTSTREAM.READ_IMAGEID ("ImageID");
m_bStretchBlit = GETINPUTSTREAM.READ_BOOL ("StretchBlit");
m_ubAlpha = (eC_UByte)GETINPUTSTREAM.READ_INT ("Alpha");
//Request new image
GETRESMANAGER.RequestImageResource(m_eImage);
}
#endif

The WriteToStream method

The ReadFromStream and WriteToStream methods of an object are always linked very closely and therefore usually look quite similar. All the attributes which you desire to read from a stream will also need to be written beforehand.

One general difference is the very first block within the WriteToStream() method, though. This is where you are writing your object's class ID to the stream. The class ID is an enumeration value which needs to be present in your UserControlResource.h file. This value will be read by the streaming factory later on and thus allow it to instantiate an object of this type. It is mandatory to have the blocks encapsulated by the if(bWriteClassID) statements in your WriteToStream implementation. What needs to be adapted though is the actual class ID (in the shown example GUI_IMAGE) to that of your own class.

Similarly to the GETINPUTSTREAM macro, there is also a GETOUTPUTSTREAM macro which grants you write access to the current stream without having to know whether you are currently writing XML or binary output. Once again, there are also matching APIs for the diverse types of data. A difference is that you need to supply two parameters, namely the value which you wish to write, and the corresponding tag name.

Just like within the ReadFromStream method, you must first call the base class implementation of the streaming method to write inherited attributes, too. The WriteToStream method ends by writing the closing tag with the object's name. This will appear within the XML file as the XML tag that you set earlier using the SetXMLTag() interface.

#ifdef GUILIANI_WRITE_GUI
void CGUIImage::WriteToStream(const eC_Bool bWriteClassID)
{
// Write class ID
if (bWriteClassID)
{
GETOUTPUTSTREAM.WriteCommentTag(GetXMLTag());
GETOUTPUTSTREAM.WriteInt(GUI_IMAGE,XMLTAG_CONTROLCLASSID);
}
// Write class version
GETOUTPUTSTREAM.WriteInt(GUIIMAGE_CLASS_VERSION,XMLTAG_CLASSVERSION);
// Write base class attributes
// Write attributes
GETOUTPUTSTREAM.WriteImageID(m_eImage,"ImageID");
GETOUTPUTSTREAM.WriteBool(m_bStretchBlit, "StretchBlit");
GETOUTPUTSTREAM.WriteInt(m_ubAlpha,"Alpha");
// Write closing tag
if (bWriteClassID)
{
GETOUTPUTSTREAM.WriteCommentTag(eC_String("/")+GetXMLTag());
}
}
#endif

Four rules for implementing streaming

When implementing streaming methods, you need to respect four simple rules:

  1. You must read and write the attributes in identical order.
  2. Call the base class streaming implementation.
  3. You must make sure that the tag names used during reading and writing are identical.
  4. You must use the appropriate READ macros (i.e. do not use READ_BOOL when trying to read an integer value).