Skip to content

Add notifyRemoteUpdate and notifyLocalChange functions in serializable#53

Merged
jafyvilla merged 1 commit intoHBPVIS:masterfrom
jafyvilla:notify_changes
Apr 27, 2016
Merged

Add notifyRemoteUpdate and notifyLocalChange functions in serializable#53
jafyvilla merged 1 commit intoHBPVIS:masterfrom
jafyvilla:notify_changes

Conversation

@jafyvilla
Copy link
Copy Markdown
Contributor

Separated semantics for local changes (Collage::Distributable use case)
and remote updates (ZeroEQ events).
Use pimpl in serializable.

@jafyvilla
Copy link
Copy Markdown
Contributor Author

jafyvilla commented Apr 19, 2016

  • Two different semantics for local changes and remote updates. notifyLocalChange is meant for Collage::Distributable objects (previously done by a virtual ZeroBuf::notifyChanging), where as notifyRemoteUpdate should be called after network changes, for example.
  • After this change the existing virtual ZeroBuf::notifyChanging() should be removed, as the base class (this) already will provide a way to set a callback function (setLocalChangeFunction).
  • co::distributable then would do something like setLocalChangeFunction( [](){ _dirty = true; })
  • If, for any of the functions (localChange/remoteUpdate/requested) we need more than one observer, then this approach is not valid anymore, as we can only bind one function per callback. In that case, if/when the time comes, we can consider the usage of signals (e.g. adding a hard boost dependency), but always wrapping the connection of the signal inside the setFooFunction, so the API doesn't get broken.

/** @internal used by ZeroEQ to invoke updated function */
/** @internal used by ZeroEQ to invoke 'localChange' function */
SERVUS_API void notifyLocalChange() const;
/** @internal used by ZeroEQ to invoke 'remoteUpdate' function */
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very good, I was also suggesting in the other(?) PR that notifyRemoteUpdate() and notifyRequested() can be made private and called internally by the public to/fromJSON/Binary().

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible, yes and agreed.

* Set a new function called after the object has been updated remotely
* (via a subscriber, a http server, loading from file...).
*/
SERVUS_API void setRemoteUpdateFunction( const ChangeFunc& func );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about setUpdatedFunction and setReceivedFunction?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm bad with naming, but I thought that specifying the source of the change/update in the name of the method (local or remote) helps to understand the semantics. Maybe not...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could work as well. In fact, the distinction between 'remote' and 'local' does not make sense for file (de)serialization (provided by Lunchbox), and 'received' would also be inaccurate in this case. It is hard to find good names, but what about:
setSerializeCallback(), setDeserializedCallback(), setChangedCallback()?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with Raphael's propositions, also because I can't come up with better ones :)
For more API safety, the three functions should be different types. Today they have the same signature, but for instance the changed callback could hint which member has changed. This change won't break then the other two functions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether it it's set- or add- callbacks, a remove/unset is needed as well. For symmetry, for temporary listeners, etc.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, a mechanism to unset is needed. To keep the API simple is it possible to get away with setting a "null" function instead of having an explicit unset? (This only works in the case of a single callback of course.)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree here. As a library user, 'set' implies a single instance and 'add' multiple. Having 'set' maintain multiple is a hack for the debatable reason of not changing the API (but the semantics!)

@eile
Copy link
Copy Markdown
Contributor

eile commented Apr 20, 2016

Good with me.

For the record: This means we will likely break the API when we need multiple observers, because setFoo has not the same semantics as addFoo in an API, i.e., the current set API cannot be used for multi-observers.

@jafyvilla
Copy link
Copy Markdown
Contributor Author

We can try to avoid that by already renaming it to addFoo, even if it's just a set for now; or leaving it as setFoo and, when we need multiple, specify in the documentation that it supports setting more than one function.

/**
* Set a new function called when a request has been received.
*
* Invoked before the object is published.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

published -> serialized
published is very specific to ZeroEQ

@jafyvilla jafyvilla force-pushed the notify_changes branch 2 times, most recently from e7c5346 to 96b4143 Compare April 20, 2016 11:53
@jafyvilla
Copy link
Copy Markdown
Contributor Author

Updated

@jafyvilla
Copy link
Copy Markdown
Contributor Author

jafyvilla commented Apr 20, 2016

Adapted other projects to this change:
Eyescale/Lunchbox#258
Eyescale/Collage#183
HBPVIS/ZeroBuf#48
HBPVIS/ZeroEQ#150
BlueBrain/Livre#282

But don't merge any of them yet

ChangeFunc _updated;
ChangeFunc _requested;
SERVUS_API void _notifyDeserialized() const;
SERVUS_API void _notifySerialize() const;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we have a pimpl now they don't need to appear in the private section

@eile
Copy link
Copy Markdown
Contributor

eile commented Apr 20, 2016

C&P since the other one is not default visible: I disagree here. As a library user, 'set' implies a single instance and 'add' multiple. Having 'set' maintain multiple is a hack for the debatable reason of not changing the API (but the semantics!)

void registerSerializeCallback( const Serializable::SerializeFunc& func )
{
serialize = func;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these needed? funcs are public...

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here is that when we need more than one observer and start using signals, we can keep the same interface for the connection of the signals/slots. That way the signals remain hidden in the implementation, and if we use boost, for example, the dependency doesn't leak through the header.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, a least for me it was just a code style comment :-)
instead of having these functions just _impl->serialize = func instead of _impl->registerChangedCallback( func )

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, got it. Sure.

@jafyvilla
Copy link
Copy Markdown
Contributor Author

I'm confused... in the latest patch I replaced the 'set' naming with 'register'

@eile
Copy link
Copy Markdown
Contributor

eile commented Apr 20, 2016

set/register

I had missed this patch. It feels slightly better, although we still would change semantics 'on the fly'.

@dnachbaur
Copy link
Copy Markdown
Contributor

To me, if I read the code object->registerCallback( func ) does not mean to me it touches any other registered callback.

@eile
Copy link
Copy Markdown
Contributor

eile commented Apr 20, 2016

Then we should throw today if there is already one registered.

@dnachbaur
Copy link
Copy Markdown
Contributor

True.

@jafyvilla jafyvilla force-pushed the notify_changes branch 2 times, most recently from 588e330 to af6c9c1 Compare April 20, 2016 16:14
{ return _fromBinary( data.ptr.get(), data.size ); }
bool fromBinary( const void* data, const size_t size )
{ return _fromBinary( data, size ); }
bool fromBinary( const Data& data );
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add SERVUS_API for non-inline functions

*
* Invoked before the object is published.
* @return the previously set function.
* Register a new function called when a request has been received.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the serializable is about to be serialized.

@jafyvilla jafyvilla force-pushed the notify_changes branch 3 times, most recently from 40dc31e to 273d9a8 Compare April 26, 2016 09:33
@jafyvilla
Copy link
Copy Markdown
Contributor Author

Retest this please

@jafyvilla
Copy link
Copy Markdown
Contributor Author

Everything seems to work again. Please review this together with:
Eyescale/Lunchbox#258
Eyescale/Collage#183
HBPVIS/ZeroBuf#48
HBPVIS/ZeroEQ#150
BlueBrain/Livre#282

and then I will start merging and updating sha1 sums.

{
if( _updated )
_updated();
_impl->deserialized = func;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw if current func exists still missing?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it throws, then it has to be documented and unit tested.
For the record, my preference still remains to support a single callback unless someone finds a really good use case for multiple callbacks, which I don't foresee is going to be needed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot about that, and I'm actually not sure about it. That means that if users want to replace the registered function, they can't unless they deregister it first (which at the moment is only possible by registering a void function). We don't have the use case though, so I can implement the throw until multiple callbacks are supported. I'll update the change including also the bool() type callbacks.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whichever way: consistency matters. If the API is designed for multi callback as it is now, at least it should throw if stuff is unimplemented so we won't change semantics when we implement them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if users want to replace the registered function, they can't unless they deregister it first

Which is exactly the semantics of the API. register adds a callback, if you call it twice you've got two callbacks. Right now we silently do something else, hence we will break semantics later.

Iff we want a single callback, the methods should be get/set and we'll break the API when we need multiples.

@rdumusc
Copy link
Copy Markdown

rdumusc commented Apr 26, 2016

+1 for this change and the dependent ones, after the get/set() (or throw) solution is implemented.

@jafyvilla jafyvilla force-pushed the notify_changes branch 3 times, most recently from 2dbbed4 to 19bc6dd Compare April 27, 2016 08:57
@jafyvilla
Copy link
Copy Markdown
Contributor Author

Updated with the throw mechanism

ChangeFunc _updated;
ChangeFunc _requested;
class Impl;
Impl* _impl;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to deactivate or implement the assignment operator and copy constructor, otherwise will lead to double free/missing free of _impl.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idem for move if C++11

@eile
Copy link
Copy Markdown
Contributor

eile commented Apr 27, 2016

+1 otherwise

@jafyvilla jafyvilla force-pushed the notify_changes branch 2 times, most recently from 2e68bd6 to 483c2d6 Compare April 27, 2016 10:00
Separated semantics for local changes (Collage::Distributable use case)
and remote updates (ZeroEQ events).
Use pimpl in serializable.
@jafyvilla
Copy link
Copy Markdown
Contributor Author

Retest this please

@jafyvilla jafyvilla merged commit 9fb8f08 into HBPVIS:master Apr 27, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants