=======================
Customizing HSF objects
=======================

In addition to writing and reading a standard HOOPS Stream File, the HOOPS/Stream Toolkit provides support for storing and retrieving user-defined data in the HSF file. This data could be associated with HSF objects, or it could simply be custom data which is convenient to store inside an HSF. The toolkit also supports tagging of objects in the HSF file, which allows for association of HSF objects with user data. The ``#TKE_Start_User_Data`` opcode is used to represent user data.
   
   
This section reviews the process of creating customized versions of default HSF objects. This is achieved by replacing the ``BStreamFileTookit`` class' default handler for a particular opcode with a custom opcode handler which is derived from the default handler class. The custom opcode handler would provide support for writing and reading additional user-data. 
  
* During file writing, you must first access the graphical and user data that you wish to export and initialize the opcode's data structures; we refer to this as 'interpretation'. This work could be done in the opcode's constructor, or you could perform the work in the ``BBaseOpcodeHandler::Interpret`` method of each opcode handler and call that method prior to exporting the opcode to the file. After 'interpretation' is complete, you must call the ``BBaseOpcodeHandler::Write`` method of the opcode handler until writing of the current opcode is complete. This exports the opcode data to an accumulation buffer initially passed to the toolkit. This buffer could then be exported to an HSF file or utilized directly. (This process was previously reviewed :doc:`here </prog_guide/base_stream/02_01_base_writing>`).

* During file reading (which is initiated by calling the ``TK_Read_Stream_File`` function, reviewed in the :doc:`file reading section </prog_guide/base_stream/02_02_base_reading>`), the ``ParseBuffer`` method of the ``BStreamFileToolkit`` object automatically reads the opcode at the start of each piece of binary information and continually calls the ``BBaseOpcodeHandler::Read`` method of the associated opcode handler. After the opcode handler reports that reading is complete, ``BStreamFileToolkit::ParseBuffer`` calls the ``BBaseOpcodeHandler::Execute`` method of the opcode handler. The data which has been read and parsed would typically be mapped to your custom application/graphical data structures within the ``BBaseOpcodeHandler::Execute`` method of each opcode handler. However, this work could also be performed in the ``BBaseOpcodeHandler::Read`` method; doing it in the ``BBaseOpcodeHandler::Read`` method is optional.

* Your custom opcode handler should implement the virtual method called ``BBaseOpcodeHandler::Clone``. This method needs to make a new instance of the opcode handler.
  
For example, let's say we wanted to write out an extra piece of user-data at the end of each piece of 'shell' geometry, (and of course retrieve it during reading) that represents a temperature value for each of the vertices in the shell's points array. Given that the shell primitive is denoted by the ``#TKE_Shell`` opcode, and handled by the ``TK_Shell`` opcode-handler, this would involve the following steps: 
   
**1. Define a new class derived from ``TK_Shell`` that overloads the ``Write`` and ``Read`` methods to process the export and import of extra user data.** 

As previously mentioned, query/retrieval of the user data from custom data structures during the writing process would typically occur within the Interpret method of the opcode handler. Similarly, mapping of the imported user data to custom application data structures would typically occur in the ``Execute`` method. However, this work can be performed in the ``Write`` and ``Read`` methods as well, as the example indicates.

The following sample header expands upon the sample ``My_TK_Shell`` object reviewed in the section :doc:`Writing an HSF </prog_guide/base_stream/02_01_base_writing>`, by also overloading the ``Read`` and ``Write`` methods::

	#include "BOpcodeShell.h"  

	class My_TK_Shell : public TK_Shell  
	{ 
		protected:  
			int				my_stage;   // denotes the current processing stage 
			 
		public:  
			My_TK_Shell()	{ my_stage  = 0; }  

			TK_Status		Execute (BStreamFileToolkit & tk) alter; 
			TK_Status		Interpret (BStreamFileToolkit & tk, HC_KEY key, int lod=-1) alter;  
			TK_Status		Read (BStreamFileToolkit & tk) alter; 
			TK_Status		Write (BStreamFileToolkit  & tk) alter;  
	  
			TK_Status		Clone (BStreamFileToolkit & tk, BBaseOpcodeHandler **) const;                              
			void			Reset () alter; 
	}; 

**2. Implement the custom Write function.** 

This is done in stages, each of which correspond to the discrete pieces of data that need to be written out for the custom shell. We use different versions of the ``BBaseOpcodeHandler::PutData`` method to output the user data, and we return from the writing function during each stage if the attempt to output the data failed (This could happen due to an error or because the user-supplied buffer is full). At this point, review the process of :doc:`Formatting User Data </prog_guide/3dgs_stream/user_data>`.

The following lists in detail the 5 writing stages for our custom shell opcode-handler: 

*Stage 0*: Output the default ``TK_Shell`` object by calling the base class' ``Write`` function ``TK_Shell::Write``

*Stage 1-4*: These stages write out the custom data (the temperature array) as well as formatting information required to denote a block of user data.

a. Output the #TKE_Start_User_Data opcode to identify the beginning of the user data
b. Output the # of bytes of user data. 
c. Output the user data itself. 
d. Output the #TKE_Start_User_Data opcode to identify the end of the user data 

Example::

	TK_Status My_TK_Shell::Write (BStreamFileToolkit & tk)     
	{ 
		TK_Status	status; 

		switch (m_stage)  
		{ 
			// call the base class' Write function to output the default  
			// TK_Shell object 
			case 0:  
			{ 
				if ((status = TK_Shell::Write(tk)) != TK_Normal) 
					return status; 
				
				my_stage++;  
				  
			} nobreak;
			 
			// output the TKE_Start_User_Data    opcode 
			case 1:  
			{ 
				if ((status = PutData (tk, (unsigned char)TKE_Start_User_Data)) != TK_Normal) 
					return status;
					 
				my_stage++; 
				   
			} nobreak;  
		   
			// output the amount of user data in bytes; we're writing out one  
			// float for each vertex value, so we have 4*m_num_values 
			case 2:  
			{ 
				if ((status = PutData (tk, 4*m_num_values)) != TK_Normal) 
					return status;  

				m_progress  = 0;  
				my_stage++; 

			} nobreak;  
			 
			// output our custom  data, which in this example is an array of 
			// temperature values which are stored  in an application 
			// data structure called 'temperature_values'    
			// since the temperature values array  might always be larger 
			// than the buffer, we can't just "try again" so always generate 
			// piecemeal, with m_progress the number of values done so far 
			case 3:  
			{  
				if ((status = PutData (tk, temperature_values, m_num_values)) != TK_Normal)  
					my_stage++;  

			} nobreak; 
			 
			// output the TKE_Stop_User_Data opcode which denotes the end of user data 
			case 4:  
			{ 
				if ((status = PutData (tk, (unsigned char)TKE_Stop_User_Data)) != TK_Normal) 
					return status; 

				   my_stage = -1; 

			} break;  

			default: 
				return TK_Error;    
		}  

		return TK_Normal; 
	} 

**3. Implement the custom Read function**

This is also done in stages, each of which correspond to the discrete pieces of data that need to be read in for the custom shell. We use different versions of the ``BBaseOpcodeHandler::GetData`` method to retreive data, and we return from the reading function during each stage if the attempt to retreive the data failed. Otherwise, the stage counter is incremented and we move on to the next stage. 

The stages during the reading process are analogous to the stages during the writing process outline above, with one exception. The ``#TKE_Start_User_Data`` opcode would still be read during 'Stage 1', but rather than blindly attempting to read our custom data, we need to handle the case where there **isn't** any user data attached to this shell object. Perhaps the file isn't a custom file, or it was a custom file and this particular shell object simply didn't have any user data appended to it. 

It is also appropriate at this time to bring up the issue of versioning and user data; it is also possible that there **is** user data following this shell object, but it is not 'our' user data. Meaning, it is not temperature data that was written out by our custom shell object, and therefore it is data that we don't understand; as a result, we could attempt to read to much or too little data. If custom versioning information was written at the beginning of our custom file, and this versioning information was used to verify that this was a file written out by our custom logic, then it is generally safe to proceed with processing user data since we 'know' what it is. The versioning issue, including details on how to write custom versioning information in the file, is discussed in more detail in the next section, :doc:`Versioning and storing additional user data </prog_guide/base_stream/04_02_base_versioning_storing>`.

Note that to check if there is any user data, we first call ``BBaseOpcodeHandler::LookatData`` to simply look at (but not get) the next byte and verify that it is indeed a ``#TKE_Start_User_Data`` opcode. If not, we return. 

Example::

	TK_Status My_TK_Shell::Read (BStreamFileToolkit & tk)     
	{ 
		TK_Status	status;  

		switch (my_stage)  
		{ 
			case 0: 
			{ 
				if ((status = TK_Shell::Read (tk)) != TK_Normal) 
					return status; 

				my_stage++; 
				   
			} nobreak;  
			case 1:  
			{ 
				unsigned    char temp;  
				
				// look at the next byte since it may not be the TKE_Start_User_Data opcode 
				if ((status = LookatData(tk, temp)) != TK_Normal) 
					return status;  

				if (temp != TKE_Start_User_Data) 
					return TK_Normal;   // there isn't any user data, so return!   
				   
				// get the opcode from the buffer 
				if ((status = GetData (tk, temp)) != TK_Normal) 
					return status; 

				my_stage++;    

			} nobreak;  
			case 2:  
			{ 
				int	length;  

				// get the integer denoting the amount of user data 
				if ((status = GetData (tk, length)) != TK_Normal) 
					return status; 

				my_stage++;    

			} nobreak;  
			case 3:  
			{ 
				// get the  temperature value array; this assumes we've 
				// already determined the length of the array and identified 
				// it using m_num_values 
				if ((status = GetData (tk, temperature_values, m_num_values)) != TK_Normal) 
					return status; 

				my_stage++; 
				   
			} nobreak;  
			case 4:  
			{ 
				unsigned	char temp;  

				// get the TKE_Stop_User_Data opcode which denotes the end of user data    
				if ((status = GetData (tk, temp)) != TK_Normal) 
					return status;  
				
				if (temp != TKE_Stop_User_Data) 
					return TK_Error;  

				my_stage = -1; 

			} break;  
			default: 
				return TK_Error;    
		}  

		return TK_Normal; 

} 

**4. Implement the custom BBaseOpcodeHandler::Reset Function** 
The toolkit will call the opcode handler's ``Reset`` function after it has finished processing the opcode. This method should reinitialize any opcode handler variables, free up temporary data and then call the base class implementation. 

Example::

	void My_TK_Shell::Reset() 
	{ 
		my_stage = 0; 
		TK_Shell::Reset(); 
	} 

**5. Implement the custom BBaseOpcodeHandler::Clone function**

Example::

	TK_Status My_TK_Shell::Clone (BStreamFileToolkit & tk, BBaseOpcodeHandler **newhandler) const 
	{ 
		*newhandler = new My_TK_Shell(); 

		if (*newhandler != null)      
			return TK_Normal; 
		else 
			return tk.Error(); 
	} 

**6. Instruct the toolkit to use our custom shell opcode handler in place of the default handler by calling BStreamFileToolkit::SetOpcodeHandler.** 

We specify the type of opcode that we want to replace, and pass in a pointer to the new opcode handler object::

	tk->SetOpcodeHandler (TKE_Shell, new My_TK_Shell);  

This will also cause the toolkit to delete it's default handler object for the TKE_Shell opcode. **Note:** As the HOOPS/Stream Reference Manual points out, all opcode handler objects stored in the ``BStreamFileToolkit`` object will be deleted when the ``BStreamFileTookit`` object is deleted. Therefore, we would not delete the ``My_TK_Shell`` object created in the above example. 
   