Archived

This forum has been archived. Please start a new discussion on GitHub.

ObjectFactory for connecting to legacy classes?

Hi ICE-users and ZeroC staff.

I have previously developed a robot controller in Python, using an earlier developed Math3D library (add "deb Index of /debian binary/" to sources.list in Debian/Ubuntu or get python-math3d.tgz), likewise written in Python. Basically the Math3D library gives classes like Vector, Orientation/Rotation, Transform (homogeneous), Quaternion, and some interpolator classes. All necessary ingredients for a robot controller.

Now we are putting the robot controllers, as well as several other device controllers, on the laboratory LAN, with ICE as the middleware, to have the basic elements of a Holonic Manufacturing System. This is sweet as pie, and functionally it works well, thanks to a tremendous effort from my good friend and colleague Olivier-Roulet Dubonnet.

However, as I start programming the interfaces and implementations for the various controllers that relate to 3D maths, I wondered about the use of slice classes for the Math3D elements, and have the ICE runtime ship the data of the classes, and object factories to recreate the elements.

The typical, and abundant, example is for a client to either send or request a Transform object from a controller proxy. So, the transform, consisting of an rotation and a translation must be modeled for slice:

module math3dI {
sequence<double> Vector;
sequence<Vector> Orientation;
struct Transform {
Orientation orient;
Vector pos;
};
};

This works fine, but give some handling code on both the server and client side. The servers and clients uses real elements from my math3d module, e.g. math3d.Transform, but what the server must send and the client receives are functionless math3dI.Transforms. The server being requested to return a Transform must create a math3dI.Transform from the internally located or computed math3d.Transform, and then return the one from math3dI. Since the slice types are a little peckish about parameter types, this unfolds to something like (self._thome is a math3d.Transform):

return math3di.Transform(self._thome.orient._data.tolist(), self._thome.pos._data.tolist())

At the other end of the wire, the client receives the math3dI.Transform, of which is has no use, so it must create a math3d.Transform by giving the data found in the math3dI.Transform to the constructor; typically something like:

recvTrf = math3d.Transform(recvTrfI.orient, recvTrfI.pos)

As mentioned earlier, everything is peachy and it work well. However, the described "casting operations" between math3d and math3dI objects will be there EVERY time some of the 3D math-related stuff goes over the wire: Neither client, nor server, is ever interested in using the math3dI classes. This raises the thought of automating the casting, like using the ObjectFactory pattern. Is it possible, somehow, to hook the legacy math3d classes into the ICE runtime, such that when a math3d.Transform is sent, it is actually serialised to the 12 contained doubles, shipped over the wire, and unpacked into a math3d.Transform at the receiving side? This is basically what I do in my code, but the triviality and ubiquity of those pack/unpack operations would be nice to hide.

I do not really see how ObjectFactory can do this. Basically the create method only takes the type id as parameter, not the necessary data for creating the object. For the inverse operation, an ObjectPacker is needed, to translate the class data into something to send.

I feel that I have read documentation, searched the forums, and experimented somewhat with ICE, though still far from being an expert. So I really hope that I have missed out on some corner, that will solve my problems. If not, i.e. the features to support the pattern I sketched are non-existing, I would like to add requests for such features.

However, it might be that my thoughts are sinful against the grand catechism of theoretical software engineering, in which case I apologise, and will repent as soon as possible :-) (Note: This is NOT an expression of what I think of ZeroC staff, whom I consider very practical and reasonable.)

Best regards,
Morten Lind.

Comments

  • Hi Morten,

    if you weren't using Python, I'd suggest to use serializable objects. With those, you can take a language-native object and ship it directly over wire using Ice (at the cost of breaking language transparency). That would be the most direct match for what you need. However, we support serializable objects only for Java and C# at the moment.

    For Python, one approach that might work:

    Make a Python class that derives from the Slice class and register an object factory for your derived class with the run time. The derived class would have an accessor operation, something like getNative() that, when called, instantiates the native class and initializes it with the data members of the Slice base class. In other words, the factory would serve to create a hook for lazy initialization, and the actual initialization would be done by getNative().

    getNative() could also clear the Slice data members in the base once it has performed the initialization, so you don't carry two copies of the object state in each derived instance. (This may not be a problem if the objects are small.) This would streamline the receiving side a bit.

    For the sending side, you could do something similar: have a class that holds a member of the native type with an accessor that returns the Slice type. When you want to send the instance across a Slice interface, instead of passing your holder class to the Slice operation, you call holderClass.getSlice() or some such, which, at that point, would perform the inverse operation and turn the native class into its Slice representation for transmission.

    At least, this would place the code that performs the conversions between the native and the Slice representation into a well-defined place, instead of littering your code with it all over the place.

    Another option I can think of is to serialize the Python classes into a pickle and to write your Slice definitions in terms of a byte array. (In order to not lose static type-safety, you could make a Slice class that stores the byte array, but have a different Slice class for each native type, so the native types appear as different types at the Slice level.)

    Then use the same trick with a factory and an accessor to trigger the serialization and deserialization at the point where the instances cross a Slice interface boundary.

    One caveat: this is theoretical in the sense that I'm sure that the mechanism as such would work, but that I haven't actually written a system in this way in the past. So, there may be something I've simply overlooked that ends up making this approach about as messy as your current one. So, I'd prototype this on a small scale first before committing to it.

    Cheers,

    Michi.