[Ns-developers] finalizing the ns-3 object model

Mathieu Lacage mathieu.lacage at sophia.inria.fr
Mon Jan 14 00:26:20 PST 2008


hi tom,

I think that you have captured the gist of the emails we exchanged
before so, I won't get into the technical details. Instead, I will
comment on some of the goals you outline below and their impact on the
overall structure.

On Sun, 2008-01-13 at 23:01 -0800, Tom Henderson wrote:

> 3) topology/script reuse
>    i) write general topology or scenario code and allow it to be run
>       populated with different types of objects, such as swapping out
>       TCP variants
> 
> 4) simulation configuration
>    i) expose existing parameter configurations
>    ii) allow users to create new default variables for new classes
>    iii) hook into command line arguments
>    iv) support scope control, both in terms of simulation time
> (pre-simulation, object construction, object inheritance) and the
> objects that have access to them

The two "features" you describe in 3) and 4) above are really about the
same thing: the goal is to allow someone who writes a script to easily
control some of the parameters of a simulation: for example, what kind
of TCP do you want ? What kind of Wifi rate control algorithm do you
want ? What is the value of the RTS_CTS_Threshold in your wifi
simulations ?

>From the perspective of the user, all these parameters are really the
same kind of thing. However, from an implementation perspective, they
can be very different: choosing the type of rate control ends being
about choosing which type of object to create when a WifiNetDevice
object is created while choosing the value of the RTS_CTS_Threshold is
really about setting a simple integer in a WifiNetDevice data structure.
The former is one of the things which is the most complex because the
user is delegating the creation of the real C++ object down to the
bowels of the WifiNetDevice but the user also wants:

  - the ability to control the arguments to give to the object to create

  - the ability to make the inner bowels of the system to create new
kinds of objects which were never built into the core model. For
example, you want to be able to specify a new kind of rate control
algorithm without having to patch the core model.

The latter two requirements lead you to what we call a ClassId and the
ComponentManager in our current codebase : we delegate the object
creation to a generic factory. This implementation has _some_ problems
but the main problem is that it is not really possible to specify object
construction arguments in a generic way so, it is not really possible to
make the inner bowels of the system be extendable to create new types of
objects with new construction arguments. We can fix this with the
Parameter work but the resulting system is going to be a bit hairy to
use.

I think that all these efforts to build this complex generic factory
thing are somewhat misleaded. The simplest way to solve this problem is
really to delegate the object creation to the user and allow the user to
specify an object instance to use. For example, if the user wants to
specify a rate control algorithm with the Wifi models, he could do this:

Ptr<WifiNetDevice> dev = ...;
Ptr<WifiRateControl rate = ...;
dev->SetRateControl (rate);

this gives you all the flexbility you want. Of course, it means that the
user has to do lots of things to create a fully-working WifiNetDevice
object and that if he forgets some of the steps of the object
construction process (like, setting the rate control algorithm object to
use), he is going to end up with a broken WifiNetDevice which crashes
pretty quickly. I think that this is an acceptable thing.

So, what I am getting at is that I think that the ClassId stuff and all
the concepts around it are way too complicated to use and explain, do
not really work and that we should replace them instead by delegating
object creation to the user.

Now, of course, you are going to say that you really want to provide a
facility to make it easy for users to set/change some parameters anyway
and I agree that this is a desirable feature. I think I disagree that we
need to build it into every model: if you want a higher-level control
over which objects are created during a simulation, this should be built
on top of the low-level features I just outlined.

For example, if you have a BuildAdhoc function:

void BuildAdhoc (Ptr<WifiChannel> channel, Ptr<Node> node, std::string
rateType, Parameters parameters)
{
  Ptr<WifiNetDevice> device = ...;
  ObjectFactory factory = ObjectFactory (rateType);
  Ptr<WifiRateControl> rate = factory->Create
(parameters)->QueryInterface<WifiRateControl> ();
  device->SetRate (rate);
}

It should be trivial to hook the "rate type" string from a command-line
parser and to convey that directly to this BuildAdhoc function. Yes, if
you do all of this, you are going to lose _some_ of the automaticity of
the current way of doing things but the result is just vastly easier to
comprehend I think.

Another key issue to understand here is that, the example above is
indeed very constrained: if you want to specify another extra
controllable object type in this BuildAdhoc function, you will have to
add extra arguments. Yes, the interesting thing to note here is that
this BuildAdhoc function is most likely going to end up being very
targeted at a specific kind of user/simulation script. For the same
reason that the ns-2 node-config function is simply almost impossible to
use outside of the limited range of cases where it works, I don't
believe you can build a generic Do function which takes as an argument a
complete description of what you want to do as a data structure.

Ultimately, I think that the big difference is probably that you would
like a sort of data-driven style of interaction with the simulation
scripts while I think that a function-style interaction is what we
should try to get. The main reason why I would like to avoid a
data-driven style is that data-driven APIs are good only when the
structure of the input data is well-defined, constant, and never
changes, and what changes is the type of process you apply to them. They
are thus really nice with data-flow-style applications (like,
pixel-crunching, or numerical resolution of large similar equations) but
here, what we have, is something where the control flow of a simulation
script is going to change a lot.

Now, there are some counter-examples to what I just said. One of the
best counter-example is GUI systems like QT or GTK which both provide
generic rather complex function-style APIs and XML-based configuration
files to build complex GUIs. I think that, indeed, what they do in this
respect is interesting and could be used as an inspiration for building
a generic scenario configuration file format but what is more
interesting is what they don't do: they don't allow you to build an
application exclusively from an XML configuration file. They allow you
to use the XML configuration file to build a large part of the GUI and
define some key elements in the GUI and, then, you have to write a real
application which reads in the configuration file and applies control
flow to the key elements of the GUI.

For a simulation, the above concept of an XML/text file would probably
be even easier to use if the user used only a predefined set of traffic
generation/trace/analysis features. If he wanted to specify new kinds of
online analysis of traffic generations, he would need to write an
application, read the configuration file in, and do the extra stuff.

Anyhow, The goal of this email was merely to point out that I think that
we are misleaded when we try to solve the features 3) and 4) you
described by using our DefaultValue system and, more specifically, by
using the ClassIdDefaultValue class. I think that we should instead
focus on getting rid of the ClassIdDefaultValue class and its uses in
the low-level API. 

So, without further mumblings, here is an outline of a plan we could
follow:
  1) get rid of ClassIdDefaultValue
  2) implement the Parameter proposal, get rid of the remaining
DefaultValue things, except for a few potential (rare < 5) exceptions
  3) implement a generic mid-layer topology API which uses Parameter to
convey parameters from the user to the objects being instantiated. 
  4) Then, for specific kinds of scenarios, we could build specific
helper functions/classes which contain a lot of common topology code and
which provide hooks to control which types of object to create.

Of course, 4) is going to be hard to implement until we have experience
in what kind of scenarios users want to run so, if it were me, I would
punt it. What is missing from this email is a description of 3). I think
I will leave this to another email. Maybe Joe would want to chime in
with a summary of that part ?

*sigh* another lengthy email,
Mathieu


More information about the Ns-developers mailing list