Archived

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

Synchronising remote objects

Hi,

I have a C# editor that allows me to change objects in a C++ program using Ice.

Say I had a base class Shape and several subclasses like Box and Sphere.
When I update by giving a Sphere object in C# to the Engine Interface, it arrives on the C++ side as a Shape. I can see the virtual function table is correct and it does have the methods of a Sphere, but it lacks the Sphere data members.

["clr:property"]
class Shape
{
int X;
int Y;
int Z;
}

["clr:property"]
class Sphere extends Shape
{
float Radius;
}

interface Engine
{
void Update(Shape currentShape);
};


// C# client
void Update()
{
engineProxy.Update((Shape)propertyGrid.SelectedObject);
}

// C++ server
void EngineI::Update(const ::Thunder::ShapePtr& updatedShape, const ::Ice::Current&)

On the server, the Radius data member is not present in the object (updatedShape) that was received. Is this possible with Ice?

Is there a better way to synchronize remote objects?


Thanks
Johan

Comments

  • mes
    mes California
    Hi,

    It sounds as if your Sphere object is getting "sliced" to the Shape base class in the C++ server. This normally occurs for one of the following reasons:
    1. The C++ server was not linked with the generated code for the Sphere type.
    2. The C++ server does not have an object factory installed for the Sphere type.
    Object factories are mandatory for Slice classes that define operations; factories are not usually necessary if a Slice class has only data members but no operations.

    Take a look at the code in demo/Ice/value for an example of passing Ice objects by value.

    Regards,
    Mark
  • Synchronising remote objects

    Hi,

    Thanks for the information. When I do a dynamic cast on the server of a known derived type, everything works fine. Which is encouraging since I know the object was correctly serialized and deserialized. However, since usually I don't know what the derived type is, this is not an option. I would have liked an update method to update the Ice defined members of the most derived type.

    //
    void EngineI::Update(const ::Thunder::ShapePtr& updatedShape, const ::Ice::Current&)
    {
    ShapePtr shape = g_scene-GetShape(m_currentShape);
    // SpherePtr sphere = SpherePtr::dynamicCast(updated); //works correctly but I don't know that it is a sphere/box
    shape.update(updatedShape);
    }

    (Where ShapePtr can be a BoxPtr or SpherePtr.)

    Maybe I am completely on the wrong track? It seems strange that I even need to update my object manually. When the value of an object member changes on the client in a distributed system, how is that usually propagated to the server? I would have liked to access my shape via a ShapeProxy with no actual local variables, only properties. When I set a property value on the client, it automatically changes the value on the server. When I get a property, it retrieves the value from the server.

    Thanks
    Johan
  • mes
    mes California
    Hi,

    Ice doesn't provide any support for automatically synchronizing changes between local and remote Ice objects. Keep in mind that remote invocations are very expensive in comparison to a local method call, so you don't necessarily want every change to a local Ice object being propagated via a remote invocation.

    Regards,
    Mark
  • Synchronising remote objects

    Yes, you are probably right about the performance issue if updating whenever a member changes...

    If the granularity of updates is on the object level rather than the member level, I am still stuck with the problem of how to update all the members in my original object with the new values. Could I convert the updated object back to a bytestream and then read this into my original object.

    Something like this?

    //
    void EngineI::Update(const ::Thunder::ShapePtr& updated, const ::Ice::Current& __current)
    {
    BasicStream stream;
    updated->__write(&stream);
    ShapePtr shape = g_scene->GetShape(m_currentShape);
    shape->__read(&stream, true);
    }

    Unfortunately there is no easy way to setup a BasicStream?
    Is there a better way?

    Thanks
    Johan
  • I'm not entirely sure what you are trying to do. At any rate, do not use a BasicStream--that class is internal to the Ice run time.

    It sounds like you want to assign one class to another. You can do this by calling ice_clone(). See "Polymorphic Copying of Classes" in the Client-Side C++ Mapping chapter. For concrete classes, the method is generated for you. For abstract classes, you must override ice_clone() in your implementation.

    Cheers,

    Michi.
  • I am trying to use Ice to allow my WPF editor written in C# to easily communicate with my application, written in C++;

    The Update function is executed on the server whenever the editor changes a value on an object in a PropertyGrid. I would like to update the value of an existing object, without destroying it. My Shape object has an implementation ShapeI (C++) which contains private data members which are already initialized and needed for my application. The update function is called remotely by my editor (C#) which sends the updated values to my application. I only want to update the slice defined members but preserve the private members of my object. If I use ice_clone() I get a brand new object with the same slice values but without preserving my private members.

    Thanks
    Johan
  • If you just want to assign data members, you can do this with something like the following:
    void EngineI::Update(const ::Thunder::ShapePtr& updated, const ::Ice::Current& __current)
    {
        ShapePtr shape = g_scene->GetShape(m_currentShape);
        SpherePtr updatedSphere = SpherePtr::dynamicCast(updated);
        if(updatedSphere)
        {
            SpherePtr target = SpherePtr::dynamicCast(shape);
            assert(target);
            *target = *updatedSphere;
        }
        else
        {
            // Deal with other types derived from Shape here...
        }
    }
    

    Note that assignment necessarily involves downcasts. (Without the downcast, only the Shape members would be assigned, but not members in the derived part of a class derived from Shape.)

    A cleaner way to implement this would be to use a helper method that hides the downcast; for example, you could add an assign method to each concrete derived class that verifies that the source type matches the destination type before doing the assignment.

    You could also use the streaming interface to do what you want, but I would not recommend it: it's probably more trouble than it is worth for classes, and it certainly will be less efficient.

    Cheers,

    Michi.
  • I was hoping I could avoid the downcast. Thanks.
  • I believe that avoiding the downcast is impossible. For operator==, the compiler uses the static type of the target to determine which overload (if any) of operator== to call. The actual call is dispatched using the run-time type of the target (if operator== is virtual). However, for the correct semantics, this would require the base class to implement a virtual operator== for all its derived classes, which is extremely bad form.

    One way of solving the problem is to use the following pattern:
    class Base {
    public:
        virtual void assign(const Base& rhs) {
            _i = rhs._i;
        }
    
    private:
        int _i;
    };
    
    class Derived : public Base {
    public:
        virtual void assign(const Base& rhs) {
            const Derived *tmp = dynamic_cast<const Derived*>(&rhs);
            if(!tmp)
                throw "Source type does not match destination type";
            this->Base::assign(rhs);
            _j = tmp->_j;
        }
    
    private:
        int _j;
    };
    

    This makes sure that you don't accidentally assign a chair to a table or some such, and it makes sure that you cannot accidentally slice an object and assign only the base part.

    Cheers,

    Michi.