vcl/README.lifecycle |  181 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 181 insertions(+)

New commits:
commit 5d163771735375bf675ed6801c6459ac4ae0b6c3
Author: Michael Meeks <[email protected]>
Date:   Fri Mar 20 11:32:43 2015 +0000

    vclptr: document the architecture, sample debugging, FAQ etc.
    
    At least a start of some documentation on VCL lifecycle.
    
    Change-Id: I6180841b2488155dd716f0d972c208b96b96a364

diff --git a/vcl/README.lifecycle b/vcl/README.lifecycle
new file mode 100644
index 0000000..201e212
--- /dev/null
+++ b/vcl/README.lifecycle
@@ -0,0 +1,181 @@
+** Understanding transitional VCL lifecycle **
+
+---------- How it used to look ----------
+
+       All VCL classes were explicitly lifecycle managed; so you would
+do:
+       Dialog aDialog(...);   // old - on stack allocation
+       aDialog.Execute(...);
+or:
+       Dialog *pDialog = new Dialog(...);  // old - manual heap allocation
+       pDialog->Execute(...);
+       delete pDialog;
+or:
+       boost::shared_ptr<Dialog> xDialog(new pDialog()); // old
+       xDialog->Execute(...);
+       // depending who shared the ptr this would be freed sometime
+
+       In several cases this lead to rather unpleasant code, when
+various shared_ptr wrappers were used, the lifecycle was far less than
+obvious. Where controls were wrapped by other ref-counted classes -
+such as UNO interfaces, which were also used by native Window
+pointers, the lifecycle became extremely opaque. In addition VCL had
+significant issues with re-enterancy and event emission - adding
+various means such as DogTags to try to detect destruction of a window
+between calls:
+
+       ImplDelData aDogTag( this );    // 'orrible old code
+       Show( true, SHOW_NOACTIVATE );
+       if( !aDogTag.IsDead() )         // did 'this' go invalid yet ?
+               Update();
+
+       Unfortunately use of such protection is/was ad-hoc, and far
+from uniform, despite the prevelance of such potential problems.
+
+       When a lifecycle problem was hit, typically it would take the
+form of accessing memory that had been freed, and contained garbage due
+to lingering pointers to freed objects.
+
+
+---------- Where we are now: ----------
+
+       To fix this situation we now have a VclPtr - which is a smart
+       reference-counting pointer (include/vcl/vclptr.hxx) which is
+       designed to look and behave -very- much like a normal pointer
+       to reduce code-thrash. VclPtr is used to wrap all OutputDevice
+       derived classes thus:
+
+       VclPtr<Dialog> pDialog( new Dialog( ... ) );
+       // gotcha - this is not a good idea ...
+
+       However - while the VclPtr reference count controls the
+       lifecycle of the Dialog object, it is necessary to be able to
+       break reference count cycles. These are extremely common in
+       widget hierarchies as each widget holds (smart) pointers to
+       its parents and also its children.
+
+       Thus - all previous 'delete' calls are replaced with 'dispose'
+       method calls:
+
+** What is dispose ?
+
+       Dispose is defined to be a method that releases all references
+       that an object holds - thus allowing their underlying
+       resources to be released. However - in this specific case it
+       also releases all backing graphical resources. In practical
+       terms, all destructor functionality has been moved into
+       'dispose' methods, in order to provide a minimal initial
+       behavioral change.
+
+** ScopedVclPtr - making disposes easier
+
+       While replacing existing code with new, it can be a bit
+       tiresome to have to manually add 'disposeAndClear()'
+       calls to VclPtr<> instances.
+
+       Luckily it is easy to avoid that with a ScopedVclPtr which
+       does this for you when it goes out of scope.
+
+** How does my familiar code change ?
+
+       Lets tweak the exemplary code above to fit the new model:
+
+-      Dialog aDialog(...);
+-      aDialog.Execute(...);
++      ScopedVclPtr<Dialog> pDialog(new Dialog(...));
++      pDialog->Execute(...); // VclPtr behaves much like a pointer
+
+or:
+-      Dialog *pDialog = new Dialog(...);
++      VclPtr<Dialog> pDialog(newDialog(...));
+       pDialog->Execute(...);
+-      delete pDialog;
++      pDialog.disposeAndClear(); // done manually - replaces a delete
+or:
+-      boost::shared_ptr<Dialog> xDialog(new pDialog());
++      ScopedVclPtr<Dialog> xDialog(new Dialog(...));
+       xDialog->Execute(...);
++      // depending how shared_ptr was shared perhaps
++      // someone else gets a VclPtr to xDialog
+or:
+-      VirtualDevice aDev;
++      ScopedVclPtr<VirtualDevice> pDev(new VirtualDevice());
+
+** Why are these 'disposeOnce' calls in destructors ?
+
+       This is an interim measure while we are migrating, such that
+       it is possible to delete an object conventionally and ensure
+       that its dispose method gets called. In the 'end' we would
+       instead assert that a Window has been disposed in it's
+       destructor, and elide these calls.
+
+       As the object's vtable is altered as we go down the
+       destruction process, and we want to call the correct dispose
+       methods we need this disposeOnce(); call for the interim in
+       every destructor. This is enforced by a clang plugin.
+
+       The plus side of disposeOnce is that the mechanics behind it
+       ensure that a dispose() method is only called a single time,
+       simplifying their implementation.
+
+
+---------- Who owns & disposes what ? ----------
+
+** referencing / ownership inheritance / hierarchy.
+
+** VclBuilder
+       + and it's magic dispose method.
+
+
+---------- What remains to be done ? ----------
+
+       * Expand the VclPtr pattern to many other less
+         than safe VCL types.
+
+       * create factory functions for VclPtr<> types and privatize
+         their constructors.
+
+       * Pass 'const VclPtr<> &' instead of pointers everywhere
+
+       * Cleanup common existing methods such that they continue to
+         work post-dispose.
+
+       * Dispose functions shoudl be audited to:
+               + not leave dangling pointsr
+               + shrink them - some work should incrementally
+                 migrate back to destructors.
+
+---------- FAQ / debugging hints ----------
+
+** Compile with dbgutil
+
+       This is by far the best way to turn on debugging and
+       assertions that help you find problems. In particular
+       there are a few that are really helpful:
+
+       vcl/source/window/window.cxx (Window::dispose)
+               "Window ( N4sfx27sidebar20SidebarDockingWindowE (Properties))
+                         ^^^ class name                 window title ^^^
+                with live children destroyed:  N4sfx27sidebar6TabBarE ()
+                N4sfx27sidebar4DeckE () 10FixedImage ()"
+
+       You can de-mangle these names if you can't read them thus:
+
+       $ c++filt -t N4sfx27sidebar20SidebarDockingWindowE
+       sfx2::sidebar::SidebarDockingWindow
+
+       In the above case - it is clear that the children have not been
+       disposed before their parents. As an aside, having a dispose chain
+       separate from destructors allows us to emit real type names for
+       parents here.
+
+       To fix this, we will need to get the dispose ordering right,
+       occasionally in the conversion we re-ordered destruction, or
+       omitted a disposeAndClear() in a ::dispose() method.
+
+       => If you see this, check the order of disposeAndClear() in
+          the sfx2::Sidebar::SidebarDockingWindow::dispose() method
+
+       => also worth git grepping for 'new sfx::sidebar::TabBar' to
+          see where those children were added.
+
_______________________________________________
Libreoffice-commits mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/libreoffice-commits

Reply via email to