changeset 232:f10a35efebc0

Memory leak fix for pychan Thanks to phoku for doing much of this patch
author cheesesucker@33b003aa-7bff-0310-803a-e67f0ece8222
date Mon, 23 Mar 2009 02:06:14 +0000
parents c62ed457e954
children 2959ed343fde
files engine/extensions/pychan/events.py
diffstat 1 files changed, 38 insertions(+), 14 deletions(-) [+]
line wrap: on
line diff
--- a/engine/extensions/pychan/events.py	Sat Mar 21 23:26:09 2009 +0000
+++ b/engine/extensions/pychan/events.py	Mon Mar 23 02:06:14 2009 +0000
@@ -45,6 +45,7 @@
 from internal import get_manager
 import tools
 import traceback
+import weakref
 
 EVENTS = [
 	"mouseEntered",
@@ -111,7 +112,7 @@
 			return
 		if self.debug: print "Attach:",self
 		self.doAttach(widget.real_widget)
-		self.widget = widget
+		self.widget_ref = weakref.ref(widget)
 		self.is_attached = True
 
 	def detach(self):
@@ -122,8 +123,6 @@
 		if not self.is_attached:
 			return
 		if self.debug: print "Detach:",self
-		self.doDetach(self.widget.real_widget)
-		self.widget = None
 		self.is_attached = False
 
 	def _redirectEvent(self,name,event):
@@ -152,14 +151,14 @@
 
 class _ActionEventListener(EventListenerBase,guichan.ActionListener):
 	def __init__(self):super(_ActionEventListener,self).__init__()
-	def doAttach(self,real_widget):	real_widget.addActionListener(self)
+	def doAttach(self,real_widget): real_widget.addActionListener(self)
 	def doDetach(self,real_widget): real_widget.removeActionListener(self)
 
 	def action(self,e): self._redirectEvent("action",e)
 
 class _MouseEventListener(EventListenerBase,guichan.MouseListener):
 	def __init__(self):super(_MouseEventListener,self).__init__()
-	def doAttach(self,real_widget):	real_widget.addMouseListener(self)
+	def doAttach(self,real_widget): real_widget.addMouseListener(self)
 	def doDetach(self,real_widget): real_widget.removeMouseListener(self)
 
 	def mouseEntered(self,e): self._redirectEvent("mouseEntered",e)
@@ -174,7 +173,7 @@
 
 class _KeyEventListener(EventListenerBase,guichan.KeyListener):
 	def __init__(self):super(_KeyEventListener,self).__init__()
-	def doAttach(self,real_widget):	real_widget.addKeyListener(self)
+	def doAttach(self,real_widget): real_widget.addKeyListener(self)
 	def doDetach(self,real_widget): real_widget.removeKeyListener(self)
 
 	def keyPressed(self,e): self._redirectEvent("keyPressed",e)
@@ -203,7 +202,8 @@
 	"""
 	def __init__(self,widget):
 		super(EventMapper,self).__init__()
-		self.widget = widget
+		self.widget_ref = weakref.ref(widget)
+		self.callbacks = {}
 		self.listener = {
 			KEY_EVENT    : _KeyEventListener(),
 			ACTION_EVENT : _ActionEventListener(),
@@ -213,8 +213,16 @@
 		self.debug = get_manager().debug
 
 	def __repr__(self):
-		return "EventMapper(%s)" % repr(self.widget)
+		return "EventMapper(%s)" % repr(self.widget_ref())
 
+	def attach(self):
+		for listener in self.listener.values():
+			listener.attach()
+
+	def detach(self):
+		for listener in self.listener.values():
+			listener.detach()
+			
 
 	def capture(self,event_name,callback,group_name):
 		if event_name not in EVENTS:
@@ -224,7 +232,7 @@
 			if self.isCaptured(event_name,group_name):
 				self.removeEvent(event_name,group_name)
 			elif self.debug:
-				print CALLBACK_NONE_MESSAGE % str(self.widget)
+				print CALLBACK_NONE_MESSAGE % str(self.widget_ref())
 			return
 		self.addEvent(event_name,callback,group_name)
 
@@ -245,17 +253,35 @@
 	def removeEvent(self,event_name,group_name):
 		listener = self.getListener(event_name)
 		del listener.events[event_name][group_name]
+		
 		if not listener.events[event_name]:
 			del listener.events[event_name]
 		if not listener.events:
 			listener.detach()
+			
+		del self.callbacks[group_name][event_name]
+		if len(self.callbacks[group_name]) <= 0:
+			del self.callbacks[group_name]
 
 	def addEvent(self,event_name,callback,group_name):
 		if not callable(callback):
 			raise RuntimeError("An event callback must be either a callable or None - not %s" % repr(callback))
+		# The closure self needs to keep a weak ref.
+		# Otherwise the GC has problems.
+		self_ref = weakref.ref(self)
 
+		# Set up callback dictionary. This should fix some GC issues
+		if not self.callbacks.has_key(group_name):
+			self.callbacks[group_name] = {}
+			
+		if not self.callbacks[group_name].has_key(event_name):
+			self.callbacks[group_name][event_name] = {}
+			
+		self.callbacks[group_name][event_name] = callback
+			
+		
 		def captured_f(event):
-			tools.applyOnlySuitable(callback,event=event,widget=self.widget)
+			tools.applyOnlySuitable(self_ref().callbacks[group_name][event_name],event=event,widget=self_ref().widget_ref())
 
 		listener = self.getListener(event_name)
 
@@ -263,7 +289,7 @@
 			listener.events[event_name] = {group_name : captured_f}
 		else:
 			listener.events[event_name][group_name] = captured_f
-		listener.attach(self.widget)
+		listener.attach(self.widget_ref())
 
 
 def splitEventDescriptor(name):
@@ -277,6 +303,4 @@
 		raise exceptions.RuntimeError("Unknown event name: " + name)
 	if len(L) == 2:
 		L = L[0],L[1],"default"
-	return L
-
-
+	return L
\ No newline at end of file