some notes on SwClient and SwModify

classic Classic list List threaded Threaded
5 messages Options
Bjoern Michaelsen Bjoern Michaelsen
Reply | Threaded
Open this post in threaded view
|

some notes on SwClient and SwModify

Hi,

as you might have noted I did some changes deep in the bowels of LibreOffice
Writer, namely the SwClient/SwModify classes that almost all Writer objects
derive from. So what do these do? They are a rather unfortunate attempt at
implementing the observer pattern. Namely, the original intend was to allow all
SwClients to watch a SwModify for events:

- You could call pModify->ModifyBroadcast(...) on a SwModify, which ...
- ... hands the arguments to all SwClients on this SwClient and calls the
  event handler pClient->Modify(...) on them. The pClient deregistering from
  the pModify in this event handler should be handled gracefully.

This sounds not too bad so far, however it has been tainted by some design
decisions, namely:
- The clients are kept in a intrusive double linked list, thus each client can
  only observe at most _one_ SwModify (and creating likely unexpected behaviour
  in "dreaded diamond" classes).
- The "message" is a rather unwieldy pair of SfxPoolItem pointers.
- pModify->ModifyBroadcast(...) was extended to allow sending the message to
  only a specific "type" (as in tools/rtti.hxx type), thus breaking the
  possible dependency inversion by the observer pattern
- There are some weird side-effects triggered by 'magic bools' in SwModify.
- If a SwModify is dying it re-registers all SwClients registered at it on the
  object it itself is registered in as a client.

This has lead to some hacks around this design, making things even more
"interesting":
- Often, instead of passing the clumsy two-pointer message to
  ModifyBroadcast(), instead the SwModify directly iterates over clients and
  does downcasting to interesting classes and triggers code on those,
  creating really tight coupling.
- Because of the "client can only listen to one modify" limitation, hacks are
  needed for objects that need to observe more event sources. This is SwDepend.
- Because of the "client can only listen to one modify", many SwClients are
  assumed only registered to a specific SwModify: They then use the
  GetRegisteredIn() function to find that SwModify and downcast it to whatever
  they expect it to be.

So, what did my recent changes do? In fact not, as much as should be done, but
its a start:
- add unittests for these
- allow inlining most of this code
- killed the untyped SwClientIter, the slightly better (because stronger typed)
  SwIterator<> template class should be used instead
- added a template specialization for SwIterator<SwClient,...>, which should
  make it as fast as the old SwClientIter for iterating over (~untyped) SwClients
- mba already added the option to send a more generic SfxHint& as a message to
  clients, with the ultimate goal to unify this SwClient/SwModify mess with the
  SfxBroadcaster/SfxListener implementation, to have at least one (weird)
  observer pattern less in our codebase. These changes make SfxHints the
  default message -- the old SfxPoolItem*-pairs are tunneled through these.
- made the SwIterator, SwDepend and LegacyModifyHint classes final
- killed some accessor functions, that were unused and should rather be kept
  private

IMHO what needs to happen to further untangle this mess:
- Pass final classes derived from SfxHint as messages, instead of the clumsy
  old SfxPoolItem*-pair where appropriate.
- While SwIterator<> gives some more typesafety than the old SwClientIter, it
  still creates quite a mess dependency-wise. Also replace these with passing a
  SfxHint& that is just handled properly by the receiving SwClients.
- Often something like SwIterator<Foo,...>(this).First() is used with
  confidence that there is only exactly one registree of a certain type.
  Instead of iterating through a possibly longer list of clients, consider having
  a point reserved for this specific client at the modify.
- Ultimately: Use a SfxBroadcaster/SfxListener API only in Writer, instead of
  yet-another-message-passing. And yes, SfxBroadcaster etc. might be
  wrapped/merged down itself around something like boost::signals/whatever.
- Ultimately: Slowly soften the implied "everything in writer need to derive
  from SwClient" situation.
- Ultimately: Once this SwIterator<> mess is gone, we also have one barrier
  less for getting rid of the hackish tools/rtti.hxx stuff.

Given that:

 grp SwIterator sw/|wc -l

shows 220 cases of SwIterator being used (58 of which seemingly only interested
in just using .First() on it once), this is still a bit of work though ...

FWIW, the changes done so far killed some ~150 LOC and were measured to have no
negative performance impact (actually 0.1% faster according to callgrind).

If there is passionate objection to any of the above, we might discuss it on
the next ESC call.

Best,

Bjoern
_______________________________________________
LibreOffice mailing list
[hidden email]
http://lists.freedesktop.org/mailman/listinfo/libreoffice
Miklos Vajna-4 Miklos Vajna-4
Reply | Threaded
Open this post in threaded view
|

Re: some notes on SwClient and SwModify

Hi Bjoern,

First -- thanks for the summary, I got only part of this "big picture"
from your commit messages. :)

One part of your changes is that now SwModify::GetDepends() returns a
const sw::WriterListener*, while it returned a const SwClient* in the
past. We had code that casted away the const-ness using a C style cast:

while( 0 != ( pDep = (SwClient*)aCallMod.GetDepends()) )

(sw/source/core/doc/docfmt.cxx:657)

I'm not sure why loplugin:cstylecast did not catch these earlier, but
now it does:

/home/vmiklos/git/libreoffice/master-clang/sw/source/core/doc/docfmt.cxx:657:26: error: c-style cast, type=5, from='const sw::WriterListener *', to='SwClient *', recommendedFix=static_cast [loplugin:cstylecast]
    while( 0 != ( pDep = (SwClient*)aCallMod.GetDepends()) )
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.

Additional problem is that SwClient now inherits privately from
::sw::WriterListener, so not even a static_cast + const_cast will make
the compiler happy.

Could do const_cast + reinterpret_cast, but it doesn't sound like the
right fix. What is the correct fix here? ;-)

Thanks,

Miklos

_______________________________________________
LibreOffice mailing list
[hidden email]
http://lists.freedesktop.org/mailman/listinfo/libreoffice

signature.asc (188 bytes) Download Attachment
Bjoern Michaelsen Bjoern Michaelsen
Reply | Threaded
Open this post in threaded view
|

Re: some notes on SwClient and SwModify

Hi Miklos

On Mon, Mar 23, 2015 at 09:54:45AM +0100, Miklos Vajna wrote:

> /home/vmiklos/git/libreoffice/master-clang/sw/source/core/doc/docfmt.cxx:657:26: error: c-style cast, type=5, from='const sw::WriterListener *', to='SwClient *', recommendedFix=static_cast [loplugin:cstylecast]
>     while( 0 != ( pDep = (SwClient*)aCallMod.GetDepends()) )
>                          ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 1 error generated.
>
> Additional problem is that SwClient now inherits privately from
> ::sw::WriterListener, so not even a static_cast + const_cast will make
> the compiler happy.
>
> Could do const_cast + reinterpret_cast, but it doesn't sound like the
> right fix. What is the correct fix here? ;-)

Doing a:

 SwIterator<SwClient,SwModify> aIter(aCallMod);
 for(SwClient* pClient = aIter.First(); pClient; pClient=aIter.Next())
     aCallMod.Remove(pClient);

should work: SwIterator is required to handle removal of Client gracefully (and
there is a unittest for that now). Also, when doing more than a plain
"Remove()", something like a SwIterator<SwFrm,SwModify> etc. takes care of most
of the casting for you internally.

So for now, SwIterator<> should help getting rid of the cast errors: IMHO
GetDepends() is an implementation detail that shouldnt be public anyway.

Best,

Bjoern
_______________________________________________
LibreOffice mailing list
[hidden email]
http://lists.freedesktop.org/mailman/listinfo/libreoffice
Bjoern Michaelsen Bjoern Michaelsen
Reply | Threaded
Open this post in threaded view
|

Re: some notes on SwClient and SwModify

Hi,

On Mon, Mar 23, 2015 at 10:27:29AM +0100, Bjoern Michaelsen wrote:
> On Mon, Mar 23, 2015 at 09:54:45AM +0100, Miklos Vajna wrote:
> > /home/vmiklos/git/libreoffice/master-clang/sw/source/core/doc/docfmt.cxx:657:26: error: c-style cast, type=5, from='const sw::WriterListener *', to='SwClient *', recommendedFix=static_cast [loplugin:cstylecast]
> >     while( 0 != ( pDep = (SwClient*)aCallMod.GetDepends()) )

just adding quickly: All of that btw seems to be a workaround for the weird API
discussed: One would assume you could call something like:

 aCallMod.CallSwClientNotify(aDeregisterFromMeNowHint);

and universally cause Clients to deregister. There is something almost like that
historically:

 SwPtrMsgPoolItem aDying(RES_OBJECT_DYING, aCallMod);
 aCallMod.ModifyBroadcast(nullptr, aDying);

... however, it has the nasty side effect of reregistering the clients of the
"dying" object at the SwModify the "dying" object was registered itself. Thats
really not expected behaviour. It seems though, that instead of creating a sane
"deregister" hint, that triggers some more obvious behaviour, people just
played around with the private parts of the SwModify/SwClient classes(*).

Best,

Bjoern

(*) This might have been reenforced by "if I touch calbck.hxx, I need to
recompile all of sw/, OMG dont want to do that." avoidance strategies back in
the days.
_______________________________________________
LibreOffice mailing list
[hidden email]
http://lists.freedesktop.org/mailman/listinfo/libreoffice
sberg sberg
Reply | Threaded
Open this post in threaded view
|

Re: some notes on SwClient and SwModify

In reply to this post by Miklos Vajna-4
On 03/23/2015 09:54 AM, Miklos Vajna wrote:
> I'm not sure why loplugin:cstylecast did not catch these earlier

Because it only warns about a select subset of C-style casts, not
including "CK_NoOp - A conversion which does not affect the type other
than (possibly) adding qualifiers."  Working to improve that.

> /home/vmiklos/git/libreoffice/master-clang/sw/source/core/doc/docfmt.cxx:657:26: error: c-style cast, type=5, from='const sw::WriterListener *', to='SwClient *', recommendedFix=static_cast [loplugin:cstylecast]
>      while( 0 != ( pDep = (SwClient*)aCallMod.GetDepends()) )
>                           ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 1 error generated.

Already reverted that commit with
<http://cgit.freedesktop.org/libreoffice/core/commit/?id=5fb7f222f51b93a9dfffa0fe211a5b3e0c83757e>
before seeing this mail thread.
_______________________________________________
LibreOffice mailing list
[hidden email]
http://lists.freedesktop.org/mailman/listinfo/libreoffice