Tutorial: Building an Rogue Wave Server Application > Designing the Server Object Model > Defining Derived Members > Re-evaluation
 
Re-evaluation
The input capacity of a given node is the sum of the capacities of its incoming lines. Likewise, its output capacity is the sum of the capacities of its outgoing lines. In your application, you want the input and output capacities of a node to be automatically recomputed when the capacity of a line connected to the node is updated. To do so, you are going to define the inputCapacity and outputCapacity derived data members in the class Node, using the IlsDerived class template. You will then associate these data members with the computeInput and computeOutput evaluation functions.
The IlsDerived class template takes the following three arguments:
*The name of the class to which the data member belongs: HolderType.
*The type of the derived data member: DerivedType.
*A user-defined class that contains a static function with the following signature:
static DerivedType Evaluate(HolderType&);
This function, which is called an evaluator, must return the derived member value. It simply calls the member functions computeInput and computeOutput and returns their value.
1. In the file network.h, modify the class Node as follows:
class Node: public IlsObject
{
public:
   Node(IlsIdentifier name, int x=1, int y=0);
   int computeInput();
   int computeOutput();
   IlsEntry<IlsIdentifier> name;
   IlsEntry<int> x;
   IlsEntry<int> y;
   ...
   //Input capacity
   class InputEvaluator {
   public:
      static int Evaluate(Node& node) {
         return node.computeInput();
      }
   };
   IlsDerived<Node,int,InputEvaluator> inputCapacity;
   //Input capacity
   class OutputEvaluator {
   public:
      static int Evaluate(Node& node) {
         return node.computeOutput();
      }
   };
   IlsDerived<Node,int,OutputEvaluator> outputCapacity;
};
2. In the file network.cpp, modify the constructor of the class Node as follows:
Node::Node(IlsIdentifier nm, int px, int py):
   name(*this,nm),
   x(*this,px),
   y(*this,py),
   inputCapacity(*this),
   outputCapacity(*this),
   domain(*this),
   inputLines(*this),
   outputLines(*this)
{}
3. In file network.cpp, you must now define the computeInput and computeOutput functions for the class Node, like this:
int
Node::computeInput()
{
   cout<<"computing the input capacity of a node"<<endl;
   IlsInvertedRelationList<Node,Line>::Iterator i(inputLines);
   int result=0;
   LineP aLine;
   while (i>>aLine) result+=aLine->capacity;
   return result;
}
 
int
Node::computeOutput()
{
   cout<<"computing the output capacity of a node"<<endl;
   IlsInvertedRelationList<Node,Line>::Iterator i(outputLines);
   int result=0;
   LineP aLine;
   while (i>>aLine) result+=aLine->capacity;
   return result;
}
The while loop scans the lines and adds their capacities to result.
4. To display the input and output capacities of node n2, modify main.cpp and compile.
int
main()
{
   //...
   cout<<"printing " << n2->name << " input capacity" <<endl;
   cout<<n2->inputCapacity << endl;
   cout<<"printing " << n2->name << " output capacity" <<endl;
   cout<<n2->outputCapacity << endl;
   return 0;
}
The program prints:
printing Node 2 input capacity
computing the input capacity of a node
25
printing Node 2 output capacity
computing the output capacity of a node
5
Lazy Evaluation
The order in which the messages appear shows that the computation is performed only if the data member is accessed. This computation mode is called lazy evaluation.
5. Now, you are going to call the derived members twice to verify that the computation is done only when required. Modify the end of your main.cpp file as follows and compile.
int
main()
{
   ...
   cout<<"printing " << n2->name << " input capacity" <<endl;
   cout<<n2->inputCapacity << endl;
   cout<<n2->inputCapacity << endl;
   cout<<"printing " << n2->name << " output capacity" <<endl;
   cout<<n2->outputCapacity << endl;
   cout<<n2->outputCapacity << endl;
   return 0;
}
This time the program will print:
printing Node 2 input capacity
computing the input capacity of a node
25
25
printing Node 2 output capacity
computing the output capacity of a node
5
5
You can see that the capacities have not been computed twice. This is because the derived member caches the result. A derived member is recalculated only if necessary, that is, when one of its dependencies changes.
Active or Inactive Entries
6. Now, you are going to slightly modify the constructor of the class Line in the file network.cpp so that the derived data member becomes sensitive to the update of the data member capacity. Modify your file network.cpp as shown below.
Line::Line(IlsIdentifier n, int cap, NodeP i, NodeP o):
   name(*this,n),
   capacity(*this,cap,ILS_ACTIVE),
   input(*this,i,INPUT),
   output(*this,o,OUTPUT)
{
}
The argument ILS_ACTIVE sets the data member capacity as active. Any entry data member set as active causes the derived members depending on it to be recalculated. This argument is optional and, by default, entry data members are inactive. If you had left out this argument, the input and output capacities of a node would not have been recomputed upon modification of a line capacity.
Reevaluation on Demand
7. Now change main.cpp as shown below and compile.
int
main()
{
   ...
   cout<<"printing " << n2->name << " input capacity" <<endl;
   cout<<n2->inputCapacity << endl;
   l2->capacity = 21;
   cout<<n2->inputCapacity << endl;
   cout<<"printing " << n2->name << " output capacity" <<endl;
   cout<<n2->outputCapacity << endl;
   l2->capacity = 22;
   cout<<n2->outputCapacity << endl;
   return 0;
}
Now the program will print:
printing Node 2 input capacity
computing the input capacity of a node
25
computing the input capacity of a node
21
printing Node 2 output capacity
computing the output capacity of a node
5
5
A derived data member is sensitive to entry modifications. However, it is not recomputed when the entry is modified, but only when the derived member is accessed. Thus, in the example above, the input capacity is recomputed when the capacity of line l2 changes because l2 is a dependency of the input capacity. However, as the capacity of l2 is not a dependency of the output capacity, then changing the capacity of l2 does not cause the output capacity to be recomputed. This example shows that the derived member is recomputed only on demand and if a dependency has changed.
8. You are now going to calculate the total input capacity for a domain. Add the derived member, function and evaluator that will sum the input capacities for all nodes in a Domain by modifying the file network.h as follows.
class Domain: public IlsObject
{
public:
   IlsOwnsList<Domain,Line> lines;
   IlsOwnsList<Domain,Node> nodes;
   Domain();
   class CapacityEvaluator {
   public:
      static int Evaluate(Domain& domain) {
         return domain.computeCapacity();
      }
   };
   IlsDerived<Domain,int,CapacityEvaluator> totalInputCapacity;
   int computeCapacity();
};
9. Modify the constructor of Domain in the file network.cpp as follows.
Domain::Domain():
   nodes(*this),
   lines(*this),
   totalInputCapacity(*this)
{
}
10. Add the function computeCapacity to the file network.cpp as follows.
int
Domain::computeCapacity()
{
   IlsOwnsList<Domain,Node>::Iterator i(nodes);
   Node* aNode;
   int result = 0;
   while (i>>aNode) {
      result += aNode->computeInput();
   }
   return result;
}
11. Now modify the end of your main.cpp file as follows and compile.
...
cout << "Total input capacity for domain" << endl;
cout << d->totalInputCapacity << endl;
return 0;
}
Now the program will print:
...
Total input capacity for domain
computing the input capacity of a node
computing the input capacity of a node
computing the input capacity of a node
computing the input capacity of a node
67
Active or Inactive Relations
12. Once you have done this, you want to recompute the total input capacity whenever nodes are added to, or removed from, the domain. To do so, you must alter the constructor of the class Domain in the file network.cpp as shown below:
Domain::Domain():
   nodes(*this,IlsDefaultRelationId,ILS_ACTIVE),
   lines(*this),
   totalInputCapacity(*this)
{
}
Any relation set as active causes the derived members depending on it to be recalculated when it is updated. To make the relation nodes active, you must add the arguments IlsDefaultRelationId and ILS_ACTIVE to the constructor. By default, relations are not active.
The type IlsDefaultRelationId is the default value which is given to identify a relation, as was discussed in Inverting Relations.
13. Now modify the end of your main.cpp file as follows and compile.
int
main()
{
   ...
   cout << "Total input capacity for domain" << endl;
   cout << d->totalInputCapacity << endl;
   d->nodes>>n2;
   cout << "Total input capacity for domain" << endl;
   cout << d->totalInputCapacity << endl;
   return 0;
}
This line removes the node from the graph and then prints out again the new total input capacity.
The program prints:
...
Total input capacity for domain
computing the input capacity of a node
computing the input capacity of a node
computing the input capacity of a node
computing the input capacity of a node
67
Total input capacity for domain
computing the input capacity of a node
computing the input capacity of a node
computing the input capacity of a node
45
This shows that the recomputation of the total input capacity is sensitive to the removal of a node. Similarly, it would have been sensitive to the addition of a node. This means that a derived member is not only sensitive to entry updates but also to relation modifications.
Eagerness
A derived data member can be marked as eager or not. This distinction affects the way the derived member is recomputed. By default, a derived member is not eager and is recomputed only on demand. This recalculation mode is called lazy evaluation (see Lazy Evaluation).
A derived member marked as eager is an active value. It is recomputed automatically at an explicit call to IlsReevaluate or during each interaction cycle of the view server when it becomes out-of-date. See Introducing Dynamic View Services for more information about view servers.
You mark a derived data member as eager using the functions IlsDerived::incrEagerness and IlsDerived::decrEagerness.
Note: Eagerness is managed by a counter rather than a Boolean value.
14. To test the automatic re-evaluation of out-of-date derived members, modify the end of your main.cpp file as follows and compile.
...
d->totalInputCapacity.incrEagerness();
cout << "Total input capacity for domain" << endl;
cout << d->totalInputCapacity << endl;
d->nodes>>n2;
IlsReevaluate();
The program prints:
...
Total input capacity for domain
computing the input capacity of a node
computing the input capacity of a node
computing the input capacity of a node
computing the input capacity of a node
67
computing the input capacity of a node
computing the input capacity of a node
computing the input capacity of a node
Note: A derived member is sensitive only to updates of server data members —that is, relations, entries, or other derived members declared using the appropriate class templates supplied with the Rogue Wave Server library. If you modify a data member that is “unknown” to Rogue Wave Server, the derived member depending on it will not be recomputed.
Summary
In this section, you have learned how to define derived data members. You know that derived members are associated with an evaluation function. In other words, they cannot be modified directly. Derived members are re-evaluated if the components of the function they are associated with are updated. If a derived member is not marked as eager, it is recomputed on demand only.

Version 6.3
Copyright © 2018, Rogue Wave Software, Inc. All Rights Reserved.