Code release version: 0.8
ARJF © 2001-2006
MFSM (Modular Flow Scheduling Middleware) is an architectural middleware implementing the architectural abstractions defined by the SAI style for design, analysis and implementation of complex integrated systems involving distributed asynchronous processing of generic data streams.
MFSM is an open source project, released under the GNU Lesser General Public License. A core library (named FSF for Flow Scheduling Framework) contains an extensible set of classes that can be specialized to define new data structures and processes, or encapsulate existing ones (e.g. from specialized libraries). Software modules regroup specialized elements that implement specific algorithms or data structures. The mission of the MFSM project encompasses cataloging and publishing of such modules. The MFSM project also publishes extensive documentation, including this user guide, a reference guide and a number of tutorials.
This user guide is an example-based, code-level tutorial on the use of the MFSM architectural middleware for implementing software systems designed in the SAI style.
The guide first presents a gentle introduction to SAI
concepts, and specifies the notation conventions
adopted throughout the document.
A last section (currently under construction...) will demonstrate the use of the barrier synchronization elements that are part of FSF.
The code appearing in this guide was developed by the author, and is part
of open source libraries, modules and examples released under the GNU Lesser General Public
License. They are available for download on the MFSM web site. This code is provided
here in the hope that it will be useful, but without any warranty;
without even the implied warranty of merchantability or fitness for a
particular purpose. Complete license information is provided in the
downloadable packages.
The Software Architecture for Immersipresence (SAI) framework (Francois, 2003) offers a unifying approach to the
distributed implementation of algorithms and their easy integration into
complex systems that exhibit desirable qualities such as efficiency,
scalability, extensibility, reusability and interoperability. SAI provides a
general formalism for the design, analysis and implementation of complex
software systems of asynchronous interacting processing components. The
SAI model builds on three pilars: (1) explicit account of time both in data
and processing models; (2) distinction between persistent and volatile data;
(3) asynchronous parallelism.
SAI specifies a formal architectural style (Francois,
2004) comprised of an extensible data model and an hybrid (shared memory
and message-passing) distributed asynchronous parallel processing model. Figure 1 presents an overview of SAI defining elements in
their standard notation.
In SAI, all data is encapsulated in pulses. A pulse is the carrier
for all the synchronous data corresponding to a given time stamp. Information
in a pulse is organized as a mono-rooted composition hierarchy of
node instances. The nodes constitute an extensible set of atomic
data units that implement or encapsulate specific data structures. Pulses
holding volatile data flow down streams defined by connections between
processing centers called cells, in a message passing fashion. They
trigger computations, and are thus called active pulses. In
contrast, pulses holding persistent information are held in repositories
called sources, where the processing centers can access them in a
concurrent shared memory access fashion. Processing in a cell may result in
the augmentation of the active pulse (input data), augmentation and/or update
of the passive pulse (process parameters). The processing of active pulses is
carried in parallel, as they are received by the cell. Data binding is
performed dynamically in an operation called {em filtering}. Active and
passive filters qualitatively specify, for each cell, the target data
in respective pulses. This hybrid model combining message passing and shared
repository communication, combined with a unified data model, constitutes a
universal processing framework.
A particular system architecture is specified at the conceptual level by a set
of source and cell instances, and their inter-connections. The logical level
specification of a design describes, for each cell, its active and passive
filters and its output structure, and for each source, the structure of its
passive pulse.
The graphical notation introduced above is sufficient for specifying systems
at the conceptual level--a set of source and cell instances, and their
inter-connections. The specialized cells may be accompanied by a description
of the task they implement. Source and cell instances may be given names for
easy reference. In some cases, important data nodes and outputs may be
specified schematically to emphasize some design aspects.
Logical level specifications require additional notation conventions to
represent nodes (and pulses) and filters.
Specialized node types are identified by their type name (string). Node
instances are identified by their (instance) name. The notation adopted to
represent node instances and hierarchies of node instances makes use of nested
parentheses, e.g.: (NODE_TYPE_ID ``Node name'' (...) ... ). This notation will
be used to specify a cell's output, and for logical specification of active
and passive pulses.
The notation adopted for specifying filters and hierarchies of filters is
nested square brackets. Each filter specifies a node type, a node instance
name or name pattern (with wildcard characters), an optional handle name, and
an eventual list of subfilters, e.g.: [NODE_TYPE_ID ``Node name''
handle_id [...] ... ]. Optional filters are indicated by a star,
e.g.: [NODE_TYPE_ID ``Node name'' handle_id]*.
The table below summarizes the notations for logical level cell definition
using the nested square brackets and nested parentheses notations introduced
above for filters and nodes respectively. By convention, in the cell output
specification, (x) represents the pulse's root, (.) represents the node
corresponding to the root of the active filter, and (..) represents its parent
node.
In this section, a simple example console application, called example1,
illustrates all the steps from design to implementation.
The code for the example can be downloaded from the MFSM project page on
SourceForge.net.
Suppose the goal of the program is to generate a stream at a given frame
rate, and display the time stamp of each pulse in seconds and in h/m/s
formats. Note that in MFSM version 0.8 and higher, the time stamp is the
number of nanoseconds elapsed since the Epoch (00:00:00 UTC, January 1, 1970)
on Mac OS X and Linux platforms, and the number of nanoseconds elapsed since
the system was last booted on Win32 platforms.
FSF contains a fundamental cell used to generate empty pulses at a given
frame rate: the Pulsar. Its specifications are as follows:
A pulsar does not have any input. One parameter, of integral type,
specifies the time delay between two consecutive output pulses.
In this section, a cell capable of looking up the time stamp of each
incoming active pulse and printing it to the console in a given format, is
supposed to be available. The actual implementation of this cell is addressed
in the next section about custom cells. The cell
specifications are as follows:
It is straightforward to implement our simple application from from one
instance of Pulsar and one instance of MyCell. Figure 4
shows the conceptual system graph.
Two functional units can be identified:
The following subsections explain in detail how to code the simple
application in C++: setup the system, build and run the application graph,
clean-up the objects allocated.
The unique instance of the system class is automatically created when first
needed (Singleton pattern). First and foremost, the nodes and cells defined in
each module used must be registered with the system. As a result, although FSF
is highly multi-threaded, the unique instance will be allocated before
multi-threading kicks in, and thus a double lock mechanism is probably not
needed in the implementation of the CSystem class.
Factories can be registered in any order. They are not necessary for
pre-coded application graphs, although they are used by the scripting module
functions (see below). Factories are necessary for dynamic, run-time
application graph building and/or modification, which is out of the scope of
this introduction. Each module is required to declare and implement a function
called RegisterFactories for registering factories in the system for
all nodes and cells implemented in the module.
A scripting module (namespace
scripting) provides shortcut functions to instantiate sources and
cells, instantiate nodes and place them in source pulses, connect cells to
other cells and to sources. The name ``scripting'' comes from the fact that
the functions provided by this module are coding equivalents of user actions
in an interactive system. In particular, the scripting module uses aspects of
the MFSM implementation that are related to dynamic system evolution, such as
class factories. Note that the scripting module itself does not implement any
node or cell class and thus does not register any factory (there is no
scripting::RegisterFactories).
The code for building an application graph instantiates and connects all
the elements of the conceptual graph. In this simple example, the graph can be
divided into two functional subgraphs: the pulsing unit, built around
the Pulsar instance, and the display unit built around the MyCell
instance. Each functional subgraph in this case corresponds to one source and
one cell (minimal computing units).
Each minimal unit, consisting of one cell and one source whose pulse
contains the cell's parameters, can be coded following these steps:
These principles can be used as general guidelines and adapted to code any
graph. The following code builds the graph for example1, first the pulsing
unit, then the display unit. Successful instantiation of all graph elements is
checked, as failure to register the appropriate factories will result in the
failure to instantiate a given cell or node.
Once the graph is completed, the cells must be activated. The Pulsar
instance is the origin of the active stream and starts generating empty pulses
as soon as it is activated. The MyCell instance, once activated, will process
incoming pulses in parallel, asynchronously. The cells can be started in any
order.
Although this aspect is not evident in this simple example, cells can be
turned on and off at any time, elements (sources and cells) can be connected
and disconnected at any time, new ones can be created and existing ones
destroyed at any time.
The following code stops the cells, disconnects and destroys the different
elements instantiated when building the graph, and finally destroys the unique
system instance.
The example implementation allows to specify the pulse rate on the command
line (the default rate is 30 Hz). Below is a sample output from the program.
If one of the goals of MFSM is to allow rapid development of applications
from existing modules, one of its main strenghts is to allow easy development
of custom elements that will interoperate seamlessly with existing or third
party components. The example developed in the previous section,
example1, uses the cell MyCell to look up the time stamp of each
incoming active pulse and print it to the console in a given format. This
section details how to describe and implement such custom cells.
Any processing cell must be derived from the base Cell (CCell). It is
characterized by an identification string (used for to link it to its
factory). A complete description includes the active and passive filters, the
process output, a list of data members and member functions with a short
description.
A custom cell must implement the default constructor, and overload a number
of virtual functions which characterize the cell:
The specifications of the cell used in example1 are as follows:
Print time stamp of incoming pulse to the console in seconds and in h/m/s
format.
The following code is the class declaration
In the constructor, the default output name base is set, and both passive and active filters are instantiated
from the corresponding template classes.
When the process function is executed, filtering of passive and active
streams has succeeded. The active and passive handles are thus bound to the
nodes satisfying the filters. When the filters are complex (hierarchies of
filters), the passive and active handles point to the roots). For this very
simple cell, no output is generated.
Any module must define a RegisterFactories function that
registers its node and cell factories with the system. Following is the code
for the RegisterFactories function that registers the factory for
CMyCell cell class.
Here is a slightly more complex example, called example2. The goal
is now to display not the pulse time stamp, but the pulse frequency. This
requires to compute the time delay between two consecutive pulses. Some data
must therefore be shared between the threads processing each pulse: the time
stamp of the last pulse processed is saved in a node on the passive
pulse. Each time the process function is called, the last time stamp value is
retrived from the node, and the node value is updated with the time stamp of
the pulse being processed. The difference between the two time stamps is then
computed and formatted to be output to the console. The corresponding cell
will be called MyCell2.
Figure 5 shows the conceptual level system graph.
The specifications for MyCell2 are as follows:
Compute and print pulse frequency to the console in Hz.
The class declaration is identical to that of MyCell. The only differences
are to the passive filter in the constructor, and to the process function.
The code for the new constructor is as follows:
Note the change in the passive filter: in order to compute a delay, the
process function must access and update the value of the time stamp of the
last pulse handled.
The code for the new process function is as follows:
The code for building the application graph for example2 is very similar to
that of example1. The only difference is in the output subgraph: the node
storing the "last time stamp" value must be instantiated and placed on the
passive pulse held by pMySource. Of course, the cell to be instantiated now is
of type MY_CELL2. The corresponding code is as follows:
The example implementation allows to specify the pulse rate on the command
line (the default rate is 30 Hz). Below is a sample output from the program.
The definition and implementation of custom nodes is illustrated with a
generic image node. The specifications for the image node
image::CImage, defined in the Image module, are as follows.
Image node.
[ImageModule.h]
One solution when designing the image node was to encapsulate an existing
image structure. Unfortunately, each image processing library comes with its
own image structure. Committing to a given library might prevent access to
other libraries, and prove restrictive in the long term. The image node
defined in the Image Module provides a minimum representation to ensure its
compatibility with existing image structures. However the image node does not
contain any field specific of particular image formats, to ensure the widest
compatibility. When needed, more specific image nodes may be derived from this
base image node for leveraging specific library features. Because of
inheritance properties, these specialized image nodes will be usable with all
processes defined for the base image node.
Any node type specialization must be derived from the base node
fsf::CNode or a derived node. A node type is characterized by an
identification string (used to link it to its factory). A complete node type
description includes a list of data members and member functions, and a short
description of its semantics.
The image node is derived from the character buffer node
fsf::CCharBuffer defined in the FSF library. An image buffer is
indeed a character buffer. The smallest set of parameters needed to make the
character buffer usable as an image buffer are the image width and height, the
number of channels and the pixel depth. Since some libraries require data line
alignment for optimal performance, the actual aligned width (width step) must
also be stored. A utility protected member function is used to compute the
aligned width.
Any custom node class must implement a number of constructors: the default
constructor, all the constructors defined in the the base node class (these
must define default values for the local data members), additional
constructors for specifying local data members initial values, and the copy
constructor. When necessary, the virtual destructor must also be overloaded.
No destructor overload is needed here, since the destructor for the
character buffer parent class takes care of deallocating the buffer if needed.
The custom node class must also overload a number of virtual functions
which characterize the node:
A set of member functions provides basic access to local data members (set
and get operations). A memory allocation function and high level parameter and
image data (buffer content) copy functions complete the set of tools offered
by the image node.
When an image node instance is created, its parameters must be
set. Constructors provide default values, set functions allow to change the
values explicitly. The corresponding buffer must then be allocated by a call
to the Allocate function. The image node instance can then be
used for processing.
Any module must define a RegisterFactories function that
registers its node and cell factories with the system. Following is the code
for the image::RegisterFactories function. Apart from the image
node image::CImage, the module also implements a number of cells
that provide access to its various data members. Their description can be
found in the Image module
documentation. Since an example of cell factory registration is provided in
the previous section, the code for cell factory registration has been ommitted
below.
Coming soon...
Alexandre R.J. François,
Software Architecture for Immersipresence,
IMSC Technical Report IMSC-03-001,
University of Southern California, Los Angeles,
December 2003.
[pdf]
[BibRef]
Alexandre R.J. François,
"A Hybrid Architectural Style for Distributed Parallel Processing of Generic Data Streams,"
Proceedings of the International Conference on Software Engineering, pp. 367-376, Edinburgh, Scotland, UK, May 2004.
[pdf]
[BibRef]
Alexandre R.J. François,
SAI: Architecting Distributed Asynchronous Software Systems,
IMSC Technical Report IMSC-05-003,
University of Southern California, Los Angeles,
September 2005.
[pdf]
[BibRef]
Disclaimer
SAI in a nutshell
Figure 1: Overview of SAI elements. Cells are represented as squares,
sources as circles. Source-cell connections are drawn as fat lines, while
cell-cell connections are drawn as thin arrows crossing over the cells. When
color is available, cells are colored in green (reserved for processing);
sources, source-cell connections, passive pulses are in colored in red
(persistent information); streams and active pulses are colored in blue
(volatile information).
notations
ClassName (ParentClass) CELL_TYPE_ID Active filter [NODE_TYPE_ID "Node name" [...] ... ] Passive filter [NODE_TYPE_ID "Node name" [...] ... ] Output (NODE_TYPE_ID "default output base name--more if needed" (...) ... ) a first example
fsf::CPulsar (fsf::CCell) FSF_PULSAR Active filter (no input) Passive filter [FSF_INT64_NODE "Pulse delay"] Output (FSF_ACTIVE_PULSE "Root")
myspace::CMyCell (fsf::CCell) MY_CELL Active filter [FSF_ACTIVE_PULSE "Root"] Passive filter [FSF_PASSIVE_PULSE "Root"] Output (no output)
Figure 4: Conceptual system graph for example1
Setting-up the system
// register system factories
fsf::RegisterFactories();
// register myspace factories
myspace::RegisterFactories();
Building the graph
// build graph
bool bSuccess=true;
////////////
// Pulsar //
////////////
// create the source
fsf::CSource *pPSource=new fsf::CSource;
bSuccess &= (pPSource!=NULL);
// parameter nodes
fsf::Int64Node *pPulseRate=static_cast<fsf::Int64Node*>(
scripting::CreateNode(std::string("FSF_INT64_NODE"),pPSource->GetPulse()));
bSuccess &= (pPulseRate!=NULL);
if(bSuccess){
// set name
pPulseRate->SetName(std::string("Pulse delay"));
// set parameter values
fsf::Time tPulseDelay=static_cast<fsf::Time>(1000000000.0f/fPulseRate);
pPulseRate->SetData(nPulseDelay);
}
// cell
fsf::CCell *pPcell=static_cast<fsf::CCell*>(
scripting::CreateCell(std::string("FSF_PULSAR")));
bSuccess &= (pPcell!=NULL);
if(bSuccess){
// connect with source
scripting::ConnectSource(pPcell,pPSource);
}
/////////////
// My cell //
/////////////
// create the source
fsf::CSource *pPSource=new fsf::CSource;
bSuccess &= (pMySource!=NULL);
// cell
fsf::CCell *pMyCell=static_cast<fsf::CCell*>(
scripting::CreateCell(std::string("MY_CELL")));
bSuccess &= (pMyCell!=NULL);
if(bSuccess){
// connect with source
scripting::ConnectSource(pMyCell,pMySource);
// connect with Pcell
scripting::ConnectUpstreamCell(pMyCell,pPcell);
}
// Check everything went OK...
if(bSuccess==false){
cout << "Some elements in the graph "
<< "could not be instantiated..." << endl;
return (-1);
}
Running the graph
// Run
pMyCell->On();
pPcell->On();
Cleaning-up
// Stop the cells
cout << "Stopping the cells..." << endl;
pPcell->Off();
pMyCell->Off();
// Clean up
scripting::DisconnectSource(pPcell);
scripting::DisconnectStream(pPcell);
delete pPcell;
delete pPSource;
scripting::DisconnectSource(pMyCell);
scripting::Disconnectstream(pMyCell);
delete pMyCell;
delete pMySource;
// Delete unique system instance
fsf::CSystem::DeleteInstance();
Running the program
>example1 -f 30
Pulse: 1120808859 s = 311335 h 47 min 39 s
Pulse: 1120808859 s = 311335 h 47 min 39 s
Pulse: 1120808859 s = 311335 h 47 min 39 s
Pulse: 1120808859 s = 311335 h 47 min 39 s
Pulse: 1120808859 s = 311335 h 47 min 39 s
custom cells
Time stamp display
When
the function is called, the binding has already succeeded and it is executed
in a separate thread.
For the cell to be useful, the process function
must be described carefully. In particular, the way the input is processed and
any output generated should be carefully documented.
myspace::CMyCell (fsf::CCell) MY_CELL Active filter [FSF_ACTIVE_PULSE "Root"] Passive filter [FSF_PASSIVE_PULSE "Root"] Output (no output) Member functions
Constructors, destructor and other functions that are part of any derived
cell class
Active stream processing
class CMyCell : public fsf::CCell {
public:
CMyCell();
// Factory mapping key
virtual void GetTypeID(std::string &str) { str.assign("MY_CELL"); }
// Specialized processing function
virtual void Process(fsf::CPassiveHandle *pPassiveHandle,
fsf::CActiveHandle *pActiveHandle,
fsf::CActivePulse *pActivePulse);
};
CMyCell::CMyCell()
: CCell() {
// default output name
m_strOutputName.assign("Doesn't matter..."); // no output
// set the filters
m_pPassiveFilter=new fsf::CPassiveFilter<fsf::CPassivePulse>(std::string("Root"));
m_pActiveFilter=new fsf::CActiveFilter<fsf::CActivePulse>(std::string("Root"));
}
void CMyCell::Process(fsf::CPassiveHandle *pPassiveHandle,
fsf::CActiveHandle *pActiveHandle,
fsf::CActivePulse *pActivePulse) {
// get input active node from active handle
fsf::CNode *pNode=static_cast<fsf::CNode*>(pActiveHandle->GetNode());
// compute the node time stamp in h/m/s format
long tsec=static_cast<long>(pNode->GetTime()/1000000000); // time unit is ns
long h=tsec/3600;
long m=(tsec-h*3600)/60;
long s=tsec-h*3600-m*60;
// print time stamp in s and in h/m/s on the console
cout << "Pulse: " << tsec << " s = "
<< h << " h " << m << " min " << s << " s " << endl;
}
Factory registration
// Factory registration
void RegisterFactories(){
using namespace fsf;
CSystem *pSystem=CSystem::GetInstance();
std::string strAlex("Alexandre R.J. Francois");
////////////////////////////////////////////////
// Node factories
////////////////////////////////////////////////////////
////////////////////////////////////////////////
// Cell factories
////////////////////////////////////////////////////////
pSystem->RegisterCellFactory(std::string("MY_CELL"),
new CCellFactory<CMyCell>(std::string("My cell"),strAlex));
}
Pulse frequency display
Figure 5: Conceptual system graph for example2
myspace::CMyCell2 (fsf::CCell) MY_CELL2 Active filter [FSF_ACTIVE_PULSE "Root"] Passive filter [FSF_INT64_NODE "Last time"] Output (no output) Member functions
Constructors, destructor and other functions that are part of any derived
cell class
Active stream processing
CMyCell2::CMyCell2()
: CCell() {
// default output name
m_strOutputName.assign("Doesn't matter..."); // no output
// set the filters
m_pPassiveFilter=new fsf::CPassiveFilter<fsf::Int64Node>(std::string("Last time"));
m_pActiveFilter=new fsf::CActiveFilter<fsf::CActivePulse>(std::string("Root"));
}
void CMyCell2::Process(fsf::CPassiveHandle *pPassiveHandle,
fsf::CActiveHandle *pActiveHandle,
fsf::CActivePulse *pActivePulse) {
// get pointer to last time node from passive handle
fsf::Int64Node *pLastTime=static_cast<fsf::Int64Node*>(pPassiveHandle->GetNode());
// get input active node from active handle
fsf::CNode *pNode=static_cast<fsf::CNode*>(pActiveHandle->GetNode());
if(m_bReset){
// set inital time stamp
pLastTime->SetData(pNode->GetTime());
m_bReset=false;
}
else{
// get last time value
fsf::Time tLastTime=static_cast<fsf::Time>(pLastTime->GetData());
// update node value with current pulse time stamp value
pLastTime->SetData(pNode->GetTime());
// compute and print time difference and corresponding frequency
fsf::Time dtms=((pNode->GetTime()-tLastTime)/1000000LL;
cout << "Pulse interval: " << dtms << " ms <=> "
<< 1000.0f/dtms << " Hz" << endl;
}
}
///////////////
// My cell 2 //
///////////////
// create the source
fsf::CSource *pPSource=new fsf::CSource;
bSuccess &= (pMySource!=NULL);
// last time stamp
fsf::Int64Node *pLastTime=static_cast<fsf::Int64Node*>(
scripting::CreateNode(std::string("FSF_INT64_NODE"),pMySource->GetPulse()));
bSuccess &= (pLastTime!=NULL);
if(bSuccess){
// set name
pLastTime->SetName(std::string("Last time"));
}
// cell
fsf::CCell *pMyCell=static_cast<fsf::CCell*>(
scripting::CreateCell(std::string("MY_CELL2")));
bSuccess &= (pMyCell!=NULL);
if(bSuccess){
// connect with source
scripting::ConnectSource(pMyCell,pMySource);
// connect with Pcell
scripting::ConnectUpstreamCell(pMyCell,pPcell);
}
>example2 -f 30
Pulse interval: 34 ms <=> 29.4118 Hz
Pulse interval: 34 ms <=> 29.4118 Hz
Pulse interval: 34 ms <=> 29.4118 Hz
Pulse interval: 34 ms <=> 29.4118 Hz
Pulse interval: 34 ms <=> 29.4118 Hz
Pulse interval: 34 ms <=> 29.4118 Hz
Pulse interval: 34 ms <=> 29.4118 Hz
Pulse interval: 34 ms <=> 29.4118 Hz
Pulse interval: 34 ms <=> 29.4118 Hz
custom nodes
CImage (fsf::CCharBuffer) "IMAGE_IMAGE"
Data members
Member functions
Constructors, destructor and other functions that are part of any derived
node class
Memory allocation
Data copy
Image parameters access
Image parameters setting
Drawing utilities
class CImage : public fsf::CCharBuffer {
protected:
int m_nNbChannels; // Number of channels
int m_nDepth; // Pixel depth IMAGE_DEPTH_*
int m_nWidth; // Image width
int m_nHeight; // Image height
int m_nWidthStep; // Aligned width (in bytes)
// utility protected member function
int ComputeWidthStep(bool bNoAlign=false);
public:
// Default constructor
CImage() : CCharBuffer(),
m_nNbChannels(3), m_nDepth(IMAGE_DEPTH_8U),
m_nWidth(0), m_nHeight(0), m_nWidthStep(0) {}
// Constructors with default values for local data members
CImage(fsf::CNode *pParent, fsf::Time tTime=0)
: CCharBuffer(pParent,tTime),
m_nNbChannels(3), m_nDepth(IMAGE_DEPTH_8U),
m_nWidth(0), m_nHeight(0), m_nWidthStep(0) {}
CImage(const string &strName,
fsf::CNode *pParent=NULL, fsf::Time tTime=0)
: CCharBuffer(strName,pParent,tTime),
m_nNbChannels(3), m_nDepth(IMAGE_DEPTH_8U),
m_nWidth(0), m_nHeight(0), m_nWidthStep(0) {}
// Constructors with local data members initial values input
CImage(int nWidth, int nHeight,
int nNbChannels=3, int nDepth=IMAGE_DEPTH_8U,
fsf::CNode *pParent=NULL, fsf::Time tTime=0)
: CCharBuffer(pParent,tTime),
m_nNbChannels(nNbChannels), m_nDepth(nDepth),
m_nWidth(nWidth), m_nHeight(nHeight), m_nWidthStep(0) {}
CImage(const string &strName, int nWidth, int nHeight,
int nNbChannels=3, int nDepth=IMAGE_DEPTH_8U,
fsf::CNode *pParent=NULL, fsf::Time tTime=0)
: CCharBuffer(strName,pParent,tTime),
m_nNbChannels(nNbChannels), m_nDepth(nDepth),
m_nWidth(nWidth), m_nHeight(nHeight), m_nWidthStep(0) {}
// Copy constructor
CImage(const CImage&);
// Assignment operator
CImage& operator=(const CImage&);
// Cloning: necessary for run-time polymorphism
virtual fsf::CNode *Clone() { return new CImage(*this); }
// Factory mapping key
virtual void GetTypeID(string &str) { str.assign("IMAGE_IMAGE"); }
void CopyParameters(const CImage&);
void CopyImageData(const CImage&);
// Image parameters setting
void SetWidth(int nWidth) { m_nWidth=nWidth; }
void SetHeight(int nHeight) { m_nHeight=nHeight; }
void SetNbChannels(int nNbChannels) { m_nNbChannels=nNbChannels; }
void SetPixelDepth(int nDepth) { m_nDepth=nDepth; }
void SetWidthStep(int nWidthStep) { m_nWidthStep=nWidthStep; }
// Image parameters access
int Width() const { return m_nWidth; }
int Height() const { return m_nHeight; }
int NbChannels() const { return m_nNbChannels; }
int PixelDepth() const { return m_nDepth; }
int WidthStep() const { return m_nWidthStep; }
// Memory allocation
void Allocate(bool bNoAlign=false);
// Drawing primitives
void DrawPoint(int x, int y, const unsigned char *col);
void DrawLine(int xo, int yo, int xd, int yd, const unsigned char *col);
void DrawRectangle(int x, int y, int w, int h, const unsigned char *col);
void FillRectangle(int x, int y, int w, int h, const unsigned char *col);
};
void image::RegisterFactories(){
using namespace fsf;
using namespace image;
CSystem *pSystem=CSystem::GetInstance();
// Node factories
pSystem->RegisterNodeFactory(std::string("IMAGE_IMAGE"),
new CNodeFactory
barrier synchronization
references