lib.ogl classes big problem. Help me!

Hi people,
I write a simple example for the problem. Put the code into a .py file and run it. Open the file menu and click to shape label (the MDIChild frame appear). Now, open the task manager (my OS is windows xp) and in a "Proccess" tab, add a "GDI Object" column. You will see the number of gdi objects create for the test application. When you close the MDIChild frame, this number not decreasing and in a successive open of MDIChildFrame, the application saturates the memory.

best regards,
Stefano Casagrande.

#!/usr/bin/env python
#Boa:App:BoaApp

import wx
import wx.lib.ogl as ogl
import random

[wxID_MDICHILDFRAME1] = [wx.NewId() for _init_ctrls in range(1)]

class MDIChildGDIFrame(wx.MDIChildFrame):
    def _init_ctrls(self, prnt):
        wx.MDIChildFrame.__init__(self, style=wx.DEFAULT_FRAME_STYLE, name='', parent=prnt, title='MDIChildFrame1', pos=wx.Point(-1, -1), id=wxID_MDICHILDFRAME1, size=wx.Size(-1, -1))

        ogl.OGLInitialize()

        self.Object_panel = ObjectArea(self, self.GetSize())

        self.CreateStatusBar()

    def __init__(self, parent):
        self._init_ctrls(parent)

class ObjectArea(ogl.ShapeCanvas):
    def __init__(self, parent, size=wx.DefaultSize):
        ogl.ShapeCanvas.__init__(self, parent=parent, id=wx.NewId(), size=size, style=wx.SUNKEN_BORDER)

        maxWidth = 2000
        maxHeight = 2000
        self.SetScrollbars(20, 20, maxWidth/20, maxHeight/20)
        self.SetBackgroundColour(wx.WHITE)

        self.diagram = ogl.Diagram()
        self.SetDiagram(self.diagram)
        self.diagram.SetCanvas(self)
        self.diagram.SetSnapToGrid(False)
        for i in range(500):
            x, y = random.randint(0, 2000), random.randint(0, 2000)
            self.MyAddShape(
                CompositeShape(self),
                x, y, wx.BLACK_PEN, wx.RED_BRUSH, "Composite"
                )

    def MyAddShape(self, shape, x, y, pen, brush, text):
        # Composites have to be moved for all children to get in place
        if isinstance(shape, ogl.CompositeShape):
            dc = wx.ClientDC(self)
            self.PrepareDC(dc)
            shape.Move(dc, x, y)
        else:
            shape.SetDraggable(True, True)
        shape.SetCanvas(self)
        shape.SetX(x)
        shape.SetY(y)
        if pen: shape.SetPen(pen)
        if brush: shape.SetBrush(brush)
        if text:
            for line in text.split('\n'):
                shape.AddText(line)
        #shape.SetShadowMode(ogl.SHADOW_RIGHT)
        self.diagram.AddShape(shape)
        shape.Show(True)

        evthandler = MyEvtHandler(self.GetParent())
        evthandler.SetShape(shape)
        evthandler.SetPreviousHandler(shape.GetEventHandler())
        shape.SetEventHandler(evthandler)

        return shape

class CompositeShape(ogl.CompositeShape):
    def __init__(self, canvas):
        ogl.CompositeShape.__init__(self)

        self.SetCanvas(canvas)

        constraining_shape = ogl.RectangleShape(120, 100)
        constrained_shape1 = ogl.CircleShape(50)
        constrained_shape2 = ogl.RectangleShape(80, 20)

        constraining_shape.SetBrush(wx.BLUE_BRUSH)
        constrained_shape2.SetBrush(wx.RED_BRUSH)

        self.AddChild(constraining_shape)
        self.AddChild(constrained_shape1)
        self.AddChild(constrained_shape2)

        constraint = ogl.Constraint(ogl.CONSTRAINT_MIDALIGNED_BOTTOM, constraining_shape, [constrained_shape1, constrained_shape2])
        self.AddConstraint(constraint)
        self.Recompute()

        # If we don't do this, the shapes will be able to move on their
        # own, instead of moving the composite
        constraining_shape.SetDraggable(False)
        constrained_shape1.SetDraggable(False)
        constrained_shape2.SetDraggable(False)

        # If we don't do this the shape will take all left-clicks for itself
        constraining_shape.SetSensitivityFilter(0)

class MyEvtHandler(ogl.ShapeEvtHandler):
    def __init__(self, frame):
        ogl.ShapeEvtHandler.__init__(self)
        self.statbarFrame = frame

    def UpdateStatusBar(self, shape):
        x, y = shape.GetX(), shape.GetY()
        width, height = shape.GetBoundingBoxMax()
        self.statbarFrame.SetStatusText("Pos: (%d, %d) Size: (%d, %d)" %
                                        (x, y, width, height))

    def OnLeftClick(self, x, y, keys=0, attachment=0):
        shape = self.GetShape()
        canvas = shape.GetCanvas()
        dc = wx.ClientDC(canvas)
        canvas.PrepareDC(dc)

        if shape.Selected():
            shape.Select(False, dc)
            canvas.Redraw(dc)
        else:
            redraw = False
            shapeList = canvas.GetDiagram().GetShapeList()
            toUnselect =

            for s in shapeList:
                if s.Selected():
                    # If we unselect it now then some of the objects in
                    # shapeList will become invalid (the control points are
                    # shapes too!) and bad things will happen...
                    toUnselect.append(s)

            shape.Select(True, dc)

            if toUnselect:
                for s in toUnselect:
                    s.Select(False, dc)

                canvas.Redraw(dc)

        self.UpdateStatusBar(shape)

    def OnEndDragLeft(self, x, y, keys=0, attachment=0):
        shape = self.GetShape()
        ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)

        if not shape.Selected():
            self.OnLeftClick(x, y, keys, attachment)

        self.UpdateStatusBar(shape)

    def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
        ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
        self.UpdateStatusBar(self.GetShape())

    def OnMovePost(self, dc, x, y, oldX, oldY, display):
        ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display)
        self.UpdateStatusBar(self.GetShape())

    def OnRightClick(self, *dontcare):
        pass

[wxID_FRAME1] = [wx.NewId() for _init_ctrls in range(1)]

class Frame1(wx.MDIParentFrame):
    def _init_ctrls(self, prnt):
        wx.MDIParentFrame.__init__(self, style=wx.DEFAULT_FRAME_STYLE | wx.VSCROLL | wx.HSCROLL | wx.MAXIMIZE, name='', parent=prnt, title='Frame1', pos=wx.Point(-1, -1), id=wxID_FRAME1, size=wx.Size(-1, -1))

    def __init__(self, parent):
        self._init_ctrls(parent)

        #crea Menu Function
        self.SentryMenuFunction = wx.Menu()
        self.ID_Menu_Events = wx.NewId()
        self.SentryMenuFunction.Append(self.ID_Menu_Events, "Shape")

        #crea Menu bar
        self.SentryMenuBar = wx.MenuBar()
        self.SentryMenuBar.Append(self.SentryMenuFunction, "&Function")
        self.SetMenuBar(self.SentryMenuBar)

        self.Bind(wx.EVT_MENU, self.OnShape, id=self.ID_Menu_Events)

  self.Maximize()

    def OnShape(self, event):
        cf = MDIChildGDIFrame(self)
        cf.Show(True)
        event.Skip()

class BoaApp(wx.App):
    def OnInit(self):
        wx.InitAllImageHandlers()
        self.main = Frame1(None)
        self.main.Show()
        self.SetTopWindow(self.main)
        return True

def main():
    application = BoaApp(0)
    application.MainLoop()

if __name__ == '__main__':
    main()

···

stefcasa@inwind.it wrote:
> I have a problem with lib.ogl classes: in my project there are many ogl objects instance
> (ogl.BitmapShape and ogl.EllipseShape) inside a ogl.ShapeCanvas and the ogl.ShapeCanvas is
> inside MDIChildFrame. The number of ogl objects depend from many factors and this number
> change during time. When I close the MDIChildFrame, the gdi objects is not released from memory
> (I see that in task manager) and in a successive MDIChildFrame open the application creates new gdi objects.
> In a successive open, the application saturates the memory and I must Kill it.
> How I can make in order to resolve the problem of the memory release of the gdi objects?

Are you able to narrow it down any further? Perhaps to figure out what
it is that is not being released?

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

stefcasa@inwind.it wrote:

I have a problem with lib.ogl classes: in my project there are many ogl objects instance (ogl.BitmapShape and ogl.EllipseShape) inside a ogl.ShapeCanvas and the ogl.ShapeCanvas is inside MDIChildFrame. The number of ogl objects depend from many factors and this number change during time. When I close the MDIChildFrame, the gdi objects is not released from memory (I see that in task manager) and in a successive MDIChildFrame open the application creates new gdi objects. In a successive open, the application saturates the memory and I must Kill it. How I can make in order to resolve the problem of the memory release of the gdi objects?

Are you able to narrow it down any further? Perhaps to figure out what it is that is not being released?

Hi people,
I write a simple example for the problem. Put the code into a .py file and run it. Open the file menu and click to shape label (the MDIChild frame appear). Now, open the task manager (my OS is windows xp) and in a "Proccess" tab, add a "GDI Object" column. You will see the number of gdi objects create for the test application. When you close the MDIChild frame, this number not decreasing and in a successive open of MDIChildFrame, the application saturates the memory.

Because of the way that OGL objects keep references to each other, and the way that Python reference counting works, you end up with a lot of reference cycles that don't get cleaned up very well. Since it was ported from C++ code where you don't have to worry about forcing object destruction when circular references are present this is a natural side effect of the port. It would be nice if somebody wanted to go over OGL and modify most of those references to be weak refs instead, but in the mean time you need to do some cleanup yourself.

If you give your ObjectArea class a method to be called from the frame's EVT_CLOSE handler that calls self.diagram.DeleteAllShapes()
then you'll be all set. Well, almost. DeleteAllShapes doesn't actually call shape.Delete like it should, and also there is an extra set of references in composite shapes so they won't get cleaned up properly either. I've attached a patch that you can use to update your copy of the ogl package so DeleteAllShapes will work.

ogl-breakcycles.patch (5.01 KB)

···

stefcasa@inwind.it wrote:

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Robin Dunn <robin@alldunn.com> writes:

>
> Because of the way that OGL objects keep references to each other, and
> the way that Python reference counting works, you end up with a lot of
> reference cycles that don't get cleaned up very well. Since it was
> ported from C++ code where you don't have to worry about forcing object
> destruction when circular references are present this is a natural side
> effect of the port. It would be nice if somebody wanted to go over OGL
> and modify most of those references to be weak refs instead, but in the
> mean time you need to do some cleanup yourself.
>
I remember fixing a huge resource leak when I did the port, and that single
one took me hours to track down. I also remember thinking that weakrefs wasn't
a solution, but right now I can't remember why I would have thought that, so
Robin is probably right.

If I have time this weekend, I might look into this a bit.

···

--
  Pierre Hjälm