Information Methods in Module NeatTools | Display Related Methods in Module | Module persistency related Methods | Property Related Methods in Module | Polymorph Data Type | Thred in Module | Concurrency Issues when Design Module
There three method include engine, broadcast, and access. Usually, programmer will have to override engine and access method to define the module's behavior. The behavior here means how module react to external events and change its internal state to respond to the event. Also, some module could change its state or broadcast message without external trigger by using its own thread.
// Link Object class JLinkObj : public JViewSet{ ... public: ... JLinkObj(); ... void access(const JDataType& data); void engine(); ... JModuleObj *from, *to; int fn, tn; };
NeatTools use to record the logical connection between modules.
void access(const JDataType& data); void engine();
Module programmer could decide to broadcast event on a particular port at any time. It could happen in its thread loop or just respond to incoming event from other modules. When broadcast(fn) is called, NeatTools will invoke all the connected module's engine(tn, link). When a module's engine method got invoked, it knows an event occur and it comes from which module, which output port, and it goes into which input port by access the data member of the link parameter. When programmer inplement the engine method, he will first use tn to identify which input port got event and then use link.access(data) to access the event content. the link.access(data) will translate into call source module's access(fn, link, data) method. So before call broadcast, programmer should prepare the value in data member so when the destination module call its access method, it could provide the value right on time. Why, we have this trouble to have the engine call the access in order to get the data? Because, in some situation, like AND gate, its input port could accept any number of connections. And When an event occur through one of the connection, the AND has to go through all the input connection to figure out the result value. In this case, The AND gate call module's access even when that module did not call broadcast. So, programmer should keep it in mind that access should always provide current value.
JModuleObj *from, *to; int fn, tn;
In this object, the public data member include the Module pointer from and to, and Interger fn and tn. Pointer from reference the source module and Pointer to reference the destination module. fn stand for the output port number of the source module and tn stand for the input port number of the destination module. In NeatTools module, programmer can define the number of input or output by fixed number or dynamically change the number of input or output port. Each port could accept one or more connections depend on the behavior of module. NeatTools will initiate a link and put it in a link list of links and then each module on the particular port will have separate link list which has the reference to the link itself.(for performance reason, so module could direct access to the link object without search). In order to see how it works, let see a simple example on a module which calculate X^2 as an output when integer event going in. We will just focus on the broadcast related mehtods at this time.
// JSquareObj.h class JSquareObj : public JModuleObj { protected: virtual void writeContent(class JOutputStream& os); virtual void readContent(class JDictionary& dict); public: virtual const char* className() const; virtual JObject* clone() const; JSquareObj(); virtual void draw(JGraphics g, int x, int y, int w, int h); virtual void access(int n, JLinkObj& link, const JDataType& data); virtual boolean inputAllowed(int n); virtual boolean outputAllowed(int n); virtual void engine(int n, JLinkObj& link); protected: int value; }; //JSquareObj.cpp #include "JSquareObj.h" #include "JLinkObj.h" #include "JIntegerData.h" char* theJSquareObj = JSquareObj().Register(); void JSqureObj::writeContent(JOutputStream& os) { JModuleObj::writeContent(os); putInteger(os, "value", value); } void JSquareObj::readContent(JDictionary& dict) { JModuleObj::readContent(dict); value = getInteger(dict, "value"); } const char* JSquareObj::className() const { return "JSquareObj";} JObject* JSquareObj::clone() const { return new JSquareObj(*this);} JSquareObj::JSquareObj() { igm = 1; value = 0;} void JSquareObj::draw(JGraphics g, int x, int y, int w, int h) { JRect rect(x, y, w, h); drawText(g, JString("Sqr"), rect); } void JSquareObj::access(int n, JLinkObj& link, const JDataType& data) { INT(data) = value;} boolean JSquareObj::inputAllowed(int n) { return !inputSet(n).last();} boolean JSquareObj::outputAllowed(int n) { return true;} void JSquareObj::engine(int n, JLinkObj& link) { int nv; link.access(JIntegerData(nv)); nv *= nv; if (value != nv) { value = nv; broadcast(0); } }
A simple example on a module wihic calculate X^2 as an output when integer event going in.
virtual void writeContent(class JOutputStream& os); virtual void readContent(class JDictionary& dict);
They are persistency-related methods
virtual const char* className() const; virtual JObject* clone() const; JSquareObj();
They are basic methods. Register() is declared in JObject
virtual void draw(JGraphics g, int x, int y, int w, int h);
This is display-related method
virtual void access(int n, JLinkObj& link, const JDataType& data); ... ... virtual void engine(int n, JLinkObj& link);
They are event-broadcast-related methods
virtual boolean inputAllowed(int n); virtual boolean outputAllowed(int n);
They are information-related methods
int value;
This is persistent data
char* theJSquareObj = JSquareObj().Register();
It will call the Register method and register the module into NeatTools system. So later on, NeatTools could duplicate or recreate object instance by using this registered copy. If you fail to define this line, your module will become invisible to NeatTools system.
JModuleObj::writeContent(os);
Call base-class writeContent() first
putInteger(os, "value", value);
Write a table of persistent variables, which comprise the state of this instance of the module
JModuleObj::readContent(dict);
Call base-class readContent() first
value = getInteger(dict, "value");
Read a table of persistent variables, which comprise the state of this instance of the module.
const char* JSquareObj::className() const { return "JSquareObj";} JObject* JSquareObj::clone() const { return new JSquareObj(*this);}
Programmer also need to override the className() and clone() methods. In NeatTools, character string return by className() method to identify different objects. Please make sure not to define different class with the same name return by className(). The clone() method will new and return a object instance. If you fail to define these methods, NeatTools could not generate the module you designed.
JSquareObj::JSquareObj() { igm = 1; value = 0;}
The constructor is an option. But usually you will initialize your data member or set the default input or output port number. igm and ogm are the variable represent the current input and output port number. In this example, it set igm to 1 and the default value of ogm (defined in JModuleObj) is 1 also. So here, this module has only one input and one output port. If you try to change the number of input or output port respond to property or input event, there are some complex issues on layout refresh problem. Please reference the source code in NeatTools Module develop kit and find out how to implement this feature.
Information Methods in Module NeatTools need more information on each input and output port. For example the data type, the attached edge, the tag, etc. The following methods will need to be override: (If you have some module which has common behavior, you can use one of the class as base class and use it to derive other class. This way, we could save time and space.)
JString inputTag(int n), JString outputTag(int n)
NeatTools use these methods to display the tag or description for a particular input or output port. The default behavior in Module is following:
JString JModuleObj::inputTag(int n) { char* tag[] = { "input", "enable(logical)"}; return tag[n]; } JString JModuleObj::outputTag(int n) { return "output";}
So when user move to the port area, NeatTools system will display the tag by using the return value of this method.
int inputType(int n), int outputType(int n)
Each input or output port could associate with different data type. Here we can return different data type identify for each different port. Or just return one data type identify for every port. The default behavior is following:
int JModuleObj::inputType(int n) { return JIntegerData::id;} int JModuleObj::outputType(int n) { return JIntegerData::id;}
Here, it define every input and output port are all integer type. If you want to use other data type, please see the header files in NEAT directory. If you want to set different data type for each different port, you can write something like this:
int JTestObj::inputType(int n) { switch (n) { case 0: return JIntegerData::id; case 1: return JStringData::id; case 2: return JWaveData::id; } return JIntegerData::id; }
void JSquareObj::access(int n, JLinkObj& link, const JDataType& data) { INT(data) = value;}
INT is a macro defined in JIntegerData.h to type cast JIntegerData into int&. So we can use it and assign new value. Programmer also could use data.assign(JInteger(value)). JInteger is a wrapper class for int data (Like the Integer class in Java). But the first implementation is faster. Basic Methods in Module Almost every object class in NeatTools are subclass of JObject directly or indirectly. There are some method which always need to be take care of or override to ensure the basic object behavior works correctly.
boolean JSquareObj::inputAllowed(int n) { return !inputSet(n).last();} boolean JSquareObj::outputAllowed(int n) { return true;}
This two methods define the accessibility of a input or output port. When it return true, NeatTools could add new connection to it. When it return false, NeatTools will indicate this port already been occupied.The default behavior is following:
boolean JModuleObj::inputAllowed(int n) { return false;} boolean JModuleObj::outputAllowed(int n) { return false;}
So if you derive your module directly from JModuleObj, you will have to override these methods to ensure your input or output port could accept new connections. If you want input port accept only one connection, you can do something like line 45 and 46:
boolean JSquareObj::inputAllowed(int n) { return !inputSet(n).last();}
The inputSet(n) will return the link list of the input port number n. So if link is empty, its last() method will return null. So inputAllowed() will return true and NeatTools could accept new connection for this port. Otherwise, NeatTools will not accept new connection. Programmer could apply the outputSet(n) to the outputAllowed() method.
int inputFace(int n), int outputFace(int n)
These two method decide the attach face that a connection would connect to. For inputFace(n), you could return either LEFT or TOP. For outputFace(n), you could return either RIGHT or BOTTOM. The default behavior is following:
int JModuleObj::inputFace(int n) { return (!n)? LEFT : TOP;} int JModuleObj::outputFace(int n) { return RIGHT;}
It means inputFace(0) will return LEFT and inputFace(1) will return TOP. and outputFace() always return RIGHT. So connection to the first input port will attach on left edge, but the second input port will attach on top edge.
JFRect inputArea(int n), JFRect outputArea(int n)
These two methods decide the area that a port will accept connections. When user's mouse move within the area defined by JFRect (A floating point rectangle with x, y, width, height data member), NeatTools will show the inverse rectangle to indicate the port area. Module itself is derived from JFRect also. So itself has the x, y, width, and height data member which define the current position and dimension of the module. By using module's width and height value you can define inputArea or outputArea like this:
JFRect JModuleObj::inputArea(int n) { if (!n) return JFRect(0, height/4, width/6, height/2); return JFRect(width/4, 0, width/2, height/6); } JFRect JModuleObj::outputArea(int n) { return JFRect(width*5/6, height/4, width/6, height/2);}
But, it is too much trouble for most module with several input port and some of the module need to change the number of input or output port dynamically. So there is some utility method which will calculate the rectangle automatically. The default behavior is following:
JFRect JModuleObj::inputArea(int n) { if (!n) return leftArea(n, 0, 1); return topArea(n, 1, 1); } JFRect JModuleObj::outputArea(int n) { return rightArea(n, 0, 1);}
The leftArea, topArea, rightArea, and bottomArea method are defined in JModuleObj.
JFRect topArea(int n, int start, int N, double from = 0.25, double delta = 0.5, int factor = 6); JFRect bottomArea(int n, int start, int N, double from = 0.25, double delta = 0.5, int factor = 6); JFRect leftArea(int n, int start, int N, double from = 0.25, double delta = 0.5, int factor = 6); JFRect rightArea(int n, int start, int N, double from = 0.25, double delta = 0.5, int factor = 6);
The n stand for the port number. start means start from which port number. N means for how many port. from stand for the relative position that area start. delta stand for the relative distance that N port will occupy. factor stand for the divide factor of area. If factor is 6, it means area will have height/6 or width/6 dimension. So leftArea(n, 0, 3) means start from port 0 to port 2 (total 3 ports) will occupy area JFRect(0, height/4, width/6, height/2) and divide it into 3 portions from top to down are port0, port1, and port2.
JFPoint inputPoint(int n, class JLinkObj& link), JFPoint outputPoint(int n, class JLinkObj& link)
These two method is pretty much like the inputArea and outputArea. But the only different is it will return the final attachment point of the connection to a particular port. You will see the link become one of the parameter. It is because, for those port could accept more than one connection, each connection will going to have different attachment point event they are belong to the same port. But using this parameter, we could decide its order and calculate the attachment point when necessary. Again, I wrote the following utility methods. The usage is exactly the same to area utility methods. Only you have to put in the extra link parameter.
JFPoint topPoint(int n, class JLinkObj& link, int start, int N, double from = 0.25, double delta = 0.5); JFPoint bottomPoint(int n, class JLinkObj& link, int start, int N, double from = 0.25, double delta = 0.5); JFPoint leftPoint(int n, class JLinkObj& link, int start, int N, double from = 0.25, double delta = 0.5); JFPoint rightPoint(int n, class JLinkObj& link, int start, int N, double from = 0.25, double delta = 0.5);
Here, we our focus on engine method.
int nv;
In this method, we declare a temporary integer variable nv.
link.access(JIntegerData(nv));
the link.access(JIntegerData(nv)) will first create JIntegerData (a derived class of JDataType) instance and use it as a media to transfer integer value into nv. Every data type are derived class of JDataType. I will discuss the data type polymorphism later. Because of polymorphism, we can use the same access method to transfer all kind of different data type.
nv *= nv;
The input value will assign on variable nv. So we calculate the square of nv.
if (value != nv) { value = nv; broadcast(0); }
If nv and value are different, we update the value and call broadcast(0). Here we only have one output port, so the only valid broadcast number would be zero. When process broadcast method, the destination module's engine method will get invoked and eventually it will call source module's access method.
Display is a very important issue in NeatTools. There are only a few method that related to display. That's talk about paint, update, and repaint method:
void paint(JGraphics g, double dx, double dy, JRegion& rgn, double scale); void update(JGraphics g, double dx, double dy, JRegion& rgn, double scale); void repaint();
NeatTools will call paint method where there is a need to refresh the graphics contents of module. For example, when user do resize, scroll, scale, etc. operations on the NeatTools windows could cause a module's paint method get invoked. If module itself decide refresh itself to respond to its internal state's change (like LED receive a true event and decide to turn itself on), it could call the repaint() method. Eventually, NeatTools could invoke module's update method. (Depend on the situation, cause module could covered by other module or out of desktop's scope.) Usually, when receive paint, module will draw everything include frame and contents. But when received update, module could only draw the contents. To save space, usually, in paint method we will only draw frame and call the update to draw the contents. But basically, programmer could whatever he want depend on the situations. The following is an example on LED object:
void JLEDObj::paint(JGraphics g, double dx, double dy, JRegion& rgn, double scale) { JRect rect = getIExtent(dx, dy, scale); g.setJColor(moduleColor); if ((rect.width > depth2) && (rect.height > depth2)) g.draw3DJRect(rect, depth); update(g, dx, dy, rgn, scale); } void JLEDObj::update(JGraphics g, double dx, double dy, JRegion& rgn, double scale) { JRect rect = getIExtent(dx, dy, scale); if (value == 0) g.setJColor(bkgnd); else g.setJColor(color); if ((rect.width > depth2) && (rect.height > depth2)) { g.fillJRect(rect.shrink(depth, depth)); } else g.fillJRect(rect); }
The JGraphics object is the graphics context that module could make their graphics operations or setup parameters. It works like a media between program and actual devices. In this case, the JGraphics is always an area on screen, if user make some operation by calling JGraphics' methods, it will reflect the change to screen. Here, in paint, first we use getIExtent to get the current module extent in integer screen unit. Cause NeatTools could display module in any scale. So, module will not know its current size on screen until its paint or update method get invoked. After this statement, the rect will store the current module size in pixel unit. Then, depend on the value itself, if value is false, we set the current color to background color, otherwise we set it to foreground color. If current size is large enough to draw the frame, we call g.draw3DJRect to draw the 3d frame. Then we call update to draw the contents. In update, we follow the same process and later on fill the LED module with the proper color by using g.fillJRect methods.
void JLEDObj::engine(int n, JLinkObj& link) { if (!n) { int iv; link.access(JIntegerData(iv)); setValue(iv); } else processColor(n, link); } boolean JLEDObj::setValue(int _value) { if (_value != value) { value = _value; broadcast(0); repaint(); return true; } return false; }
So, when will LED decide to update itself and make visual feedback to user? Let's look at the engine method. If n is equal to zero (the input port on the left edge, in this case), it will use link.access to assign the integer value into iv variable. And call setValue(iv). In setValue method, we will check if the value need change, if it does, we update the value first and call broadcast(0) and repaint() to refresh its display. Later on, NeatTools will invoke it's update method to fill the color when necessary. The default behavior of display methods in JViewObj (JViewObj is the base class of all objects that could display itself on screen) are:
void JViewObj::paint(JGraphics g, double dx, double dy, JRegion& rgn, double scale) {} void JViewObj::update(JGraphics g, double dx, double dy, JRegion& rgn, double scale) { paint(g, dx, dy, rgn, scale);}
Here the paint draw nothing and update call paint directly. So derived class could just override paint for their simple display need. The default behavior of display methods in JModuleObj are:
void JModuleObj::draw(JGraphics g, int x, int y, int w, int h) { JRect rect(x, y, w, h); drawText(g, JString("Mod"), rect); } void JModuleObj::paint(JGraphics g, double dx, double dy, JRegion& rgn, double scale) { g.setJColor(moduleColor); JRect rect = getIExtent(dx, dy, scale); g.fill3DRect(rect.x, rect.y, rect.width, rect.height, 2); g.setJColor(moduleColor.darker()); draw(g, rect.x+3, rect.y+3, rect.width-8, rect.height-8); g.setJColor(moduleColor.brighter()); draw(g, rect.x+5, rect.y+5, rect.width-8, rect.height-8); g.setJColor(JColor::black); draw(g, rect.x+4, rect.y+4, rect.width-8, rect.height-8); }
When system need module's display, it will call paint. In paint, we use color and offset to create the 3D look and feel. So, if user only override draw method and draw some simple text or shapes, it will have the 3D look without repeat the process three times.
One important thing that NeatTools system does is provide user the capacity to layout modules and make connections between modules. But the other task that NeatTools does is equally important - module persistency. That means, NeatTools provide a way to let module store its current state and connections into secondary storage space like hard disk. And later on, retrieve the information and reconstruct every module, restore their state and rebuild their connections. So, how NeatTools do that? NeatTools rely on two methods in module to accomplish this requirement.
protected: virtual void writeContent(class JOutputStream& os); virtual void readContent(class JDictionary& dict);
The implementation in LED looks like this:
void JLEDObj::writeContent(JOutputStream& os) { JModuleObj::writeContent(os); putObject(os, "color", color); putObject(os, "bkgnd", bkgnd); putInteger(os, "value", value); } void JLEDObj::readContent(JDictionary& dict) { JModuleObj::readContent(dict); JObject *obj = getObject(dict, "color"); if (obj) color = *(JColor*)obj; obj = getObject(dict, "bkgnd"); if (obj) bkgnd = *(JColor*)obj; value = getInteger(dict, "value"); }
writeContent method will be invoked when NeatTools want a module to write its current states into output stream. Programmer has to call base class's writeContect method first. Cause every module will only handle the data member that declare in its object scope. Some times a data member is just for temporary usage, we could decide not to write their current state. readContent is the opposite to writeContent. When we try to put a variable, we assign a string name to identify the variable. Later on, the readContent provide user a dictionary object which contain all the available variables. Programmer could use string name as a key to retrive the variables. All the putX and getX method are defined in JObject class. The only tricky thing is that, getObject could return null. In that case, we could check for that situation and assign object only when the return object is not null.
The property related methods are:
JArray getProperties(); boolean updateProperty(JProperty& prop);
When user right click on a module, NeatTools will call the getProperties method first to get all the properties that belong to this module. Later on, if user decide to change one of the property, after property have been changed and user pressed the OK button, NeatTools will invoke updateProperty with a property parameter. The JProperty is the base class for every property. Currently, NeatTools has JIntegerProperty, JIntegerListProperty, JStringProperty, and JFileProperty. All you need is include the header files and look at the header file itself or copy the usage from other module's source code. The default behavior is following:
JArray JModuleObj::getProperties() { JArray properties; properties.append(JColorProperty("moduleColor", moduleColor)); return properties; } boolean JModuleObj::updateProperty(JProperty& prop) { if (prop.getName() == JString("moduleColor")) { moduleColor = ((JColorProperty*)&prop)->color; repaintView(*this); return true; } return false; }
All module has a moduleColor property that define the base color of module. In getProperties, we initialize a properties array and append a JColorProperty instance with name of "moduleColor". When NeatTools call updateProperty, we have to check the property's getName method and make sure we are handling the right property. Then we proceed the needed operations. If we process the property, we should return true. So NeatTools knows this property has been processed. Otherwise, we return false. In case we derived from other module, for example, JLEDObj derived from JModuleObj. The implementation is a little bit different:
JArray JLEDObj::getProperties() { JArray properties = JModuleObj::getProperties(); properties.append(JIntegerProperty("value", value, -limit-1, limit)); properties.append(JColorProperty("color", color)); properties.append(JColorProperty("bkgnd", bkgnd)); return properties; } boolean JLEDObj::updateProperty(JProperty& prop) { if (JModuleObj::updateProperty(prop)) return true; if (prop.getName() == JString("value")) { setValue(((JIntegerProperty*)&prop)->value); return true; } else if (prop.getName() == JString("color")) { color = ((JColorProperty*)&prop)->color; repaintView(*this); return true; } else if (prop.getName() == JString("bkgnd")) { bkgnd = ((JColorProperty*)&prop)->color; repaintView(*this); return true; } return false; }
When we derive class from other module class, in getProperties method, we usually call base class' getProperties first and get its property array. Then, we append properties that belong to the derived class. In updateProperty method, we call the base class' updateProperty method first, if it return true, it means the property belong to the base class and the property is already been processed. In this case, we return true also. Otherwise, we proceed to check the property and use the new property value to setup module's internal state or operations needed.
The polymorph data type is a special data type that could connection to any data type. NeatTools query the data type by invoke inputType or outputType. When programmer dealing with polymorth data type, he will use the inputSet and outputSet to get the connected module's inputType or outputType to recursively get the current data type. But care should be taken on the cyclic situation. Basically, we use counter for each port to make sure the cyclic situation does not happen. When access method been call, we redirect it to the access of the connected module. Cyclic could happen, so programmer need to do some extra code to avoid that. Their are several module use polymorph data type like JMultiplexer and JDeMultiplexer. Please reference their source code for more information.
In some situations, programmer will need to handle or process something without hanging the NeatTools system. For example, in JSocketObj, after the socket and server socket are connected, how we know a socket package is arraived? We can use non-blocking socket to test if package is ready for read. But how long should we check the socket input? If we use the blocked socket to read package, it could happen that no package is coming in and NeatTools got stuck by your module. In this case, if we dedicate a separate thread loop to execute the socket read, even the thread is block by the socket read procedure, the NeatTools still can proceed without stopped by modules. How we use thread in module? Lets look at the code from JTimeObj module.
JTimeObj::~JTimeObj() { close();} void JTimeObj::run() { valid = true; updateTime(); while (valid) { while (!isTimeChanged() && valid) JThread::sleep(10); updateTime(); if (!valid) break; JThread::sleep(900); broadcast(0); } } void JTimeObj::reset() { valid = false;} void JTimeObj::startup() { thread = JThread(this); thread.start(); } void JTimeObj::close() { if (!valid) return; valid = false; thread.waitFor(); }
When NeatTools prepare a new instance of module, it will first call the startup method. So, this method could be a good place to start a new thread. Here, we first initialize a new thread and assign it into thread variable. After that, we call thread.start method. If everything went well, a thread will start to running begin by invoke run method. Usually, we will have a loop inside run method. If we exit the run method, the thread stopped and removed by system. If the module's instance been remove by system, it will call the ~JTimeObj and eventually call the close method. In close method, we mark the valid to false and wait for thread to exit. Do not delete thread directly. Use waitFor method instead to avoid the memory leaking problem. Now, there is one problem left, the valid is true to indicate their is a thread running inside JTimeObj module. But it could happen that user duplicate an active module. After duplicate operation, the new JTimeObj instance has true value in valid variable but it actually does not has a thread running. So, the reset method come into action. When user try to duplicate module, NeatTools will try to invoke the reset method on the new module. So if we set valid to false in reset method, everything will going to be perfect.
Because NeatTools allow module use their own threads, the concurrency problems could happen. Programmer should keep it in mind that it's engine and access method could be called by other modules at the same time. For example, lets look at the following situation in engine method:
void JTNG4Obj::engine(int n, JLinkObj& link) { switch (n) { case IN_COM: { JBlock _data; link.access(JBytesData(_data)); csb.lock(); buf+=_data; csb.unlock(); break; } ... } }
In JTNG4Obj module, there is a JBlock variable buf in it. Because, the engine method could be invoke by more than one thread at the same time, it could happen that they execute the buf+=_data statement almost at the same time. What could happen? Information lost could happen. Or worse, NeatTools could crash. Here, there is a instance of JCriticalSection class to guard the statements from access by more than one thread. The lock method will allow only one thread enter. And the unlock will turn off the critical section lock. To determine where you should use JCriticalSection to guard your critical section statements need experience and knowledge of your module design.