Visitors in Action

Visitors are a general mechanism allowing clients (plug-ins) to traverse the system’s (modo’s) internal state. The client essentially defines a single action function to be performed over and over as the system walks some internal database. At each step the client can query the system about the current state of the enumeration, and abort the enumeration if it wants to. Visitors can also be used to cause a client action to happen in a specific state.

Although this is related to the [http://en.wikipedia.org/wiki/Visitor_pattern Visitor pattern], it’s a very minimal implementation. Although the simplicity makes it easy to reuse for multiple purposes, it does take a little practice to understand how to use it.

Enumeration Methods

Interfaces provide enumeration methods to allow a client to traverse the contents of some container or database, especially when access by count and index are inefficient or otherwise undesirable. A typical example are the mesh accessors. These objects are allocated specifically for investigating the features of mesh elements: points, polygons, edges and maps. Methods on the object allow the client to select elements by index or ID and then to query the object for the element’s properties.

In addition there are also enumeration methods. These take a mark mode, a visitor, and a monitor.

1
2
3
4
5
6
         [[LXxMETHOD (index)|LXxMETHOD]](  [[LxResult (index)|LxResult]],
 Enumerate) (
         [[LXtObjectID (index)|LXtObjectID]]              self,
         LXtMarkMode              mode,
         [[LXtObjectID (index)|LXtObjectID]]              visitor,
         [[LXtObjectID (index)|LXtObjectID]]              monitor);

The mark mode identifies a subset of elements to traverse, and the Monitor Interface gives feedback about the overall progress. The visitor object is used to allow the client to act on each element during the traversal. Even when Count/ByIndex methods are available, an enumeration call will generally be more efficient and should be preferred, all things being equal.

Visitor Evaluation

The Visitor::Evaluate() method is called for each element, and the visitor is expected to request attributes of the current element that it wants to query. Let’s say we want to make a list of point positions in a mesh. The general outline of this process would be to:

  • allocate a point accessor

  • create a visitor

  • enumerate the points:
    • for each point:
      • query point position

      • add to list

Abstract Visitor

Fortunately there are some wrappers that make this process relatively easy. Many of the user classes define alternate methods that take ‘’abstract visitors’’. For example all the mesh accessors have alternate methods defined like this:

1
2
3
4
5
         [[LxResult (index)|LxResult]]
 Enum (
         CLxImpl_AbstractVisitor *visitor,
         LXtMarkMode              mode = [[LXiMARK_ANY (index)|LXiMARK_ANY]],
         [[ILxUnknownID (index)|ILxUnknownID]]             mon  = 0)

These C++ methods take CLxImpl_AbstractVisitor instead of generic objects along with other conveniences like optional arguments. The ‘’abstract visitor’’ class requires a simple C++ method without any COM-related rigamarole. Here’s all the code required to build a point position list using the user method.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 class CPointList : public CLxImpl_AbstractVisitor
 {
     public:
        CLxUser_Point            point;
        std::vector<LXtFVector>  list;

                LxResult
        Evaluate ()
        {
                LXtFVector       pos;

                point.Pos (pos);
                list.push_back (pos);
                return LXe_OK;
        }
 };

        static void
 GetPointList (
        CLxUser_Mesh            &mesh,
        CPointList              &plist)
 {
        plist.point.fromMesh (mesh);
        plist.point.Enum (plist);
 }

There are several important things to notice about this example. First the visitor is just a C++ class deriving from ‘’’CLxImpl_AbstractVisitor’’’. The CLxImpl_ prefix normally means that the class requires a COM wrapper, but in this case that’s just an incorrect use of the naming conventions. In fact the Evaluate() method derived from the superclass is sufficient.

Second, the point accessor is part of the member data of the visitor. This allows the visitor to query the state of the accessor during evaluation, which is critical for reading out the state of the current element as the mesh is traversed.

Finally, Evaluate() returns ‘’LXe_OK’’. If it returned anything else the enumeration would have aborted and the Enum() method would have returned that result rather than OK.

One Visitor

There may be interfaces that don’t have nice user methods. What if something really requires a real visitor object and has no C++ alternate? That’s also easy – we can use the CLxInst_OneVisitor class to create a singleton. For example the previous code can be rewritten to use the raw Enumerate() method rather than the Enum() user wrapper.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
 class CPointList
 {
     public:
        CLxUser_Point            point;
        std::vector<LXtFVector>  list;

                LxResult
        Evaluate ()
        {
                LXtFVector       pos;

                point.Pos (pos);
                list.push_back (pos);
                return LXe_OK;
        }
 };

        static std::vector<LXtFVector>
 GetPointList (
        CLxUser_Mesh            &mesh)
 {
        CLxInst_OneVisitor<CPointList> one;

        one.point.fromMesh (mesh);
        one.point.Enumerate (plist, LXiMARK_ANY, 0);
        return one.loc.list;
 }

What’s basically happening here is that the ‘’One Visitor’’ template class is doing the hard work of creating a single, one-use COM object for the purpose of enumeration. Admittedly some of the other tradeoffs are different too; it’s a different way of doing effectively the same thing.

Spawned Visitor

1
 ... TBD ...