Ruby 使用 Cairo 的 Gtk3 内存泄漏
Ruby Gtk3 memory leaks using Cairo
我正在尝试在 Linux 的 Ruby Gtk3 中制作一个简单的框图编辑器原型。在我看来,我对 Gtk/Cairo 的使用不够充分,或者我遇到了内存泄漏。
症状如下:当我创建一个图形对象(矩形)并移动它时,我的内存使用量迅速增加,如下所示。
问题是Gtk3::DrawingArea.queue_draw.
的使用
我错过了什么?
我的版本如下:
- ruby 2.4.0p0(2016-12-24 修订版 57164)[x86_64-linux]
- gtk3 (3.2.7)
require 'gtk3'
BLUE = [0.1,0.0,0.7]
class Rectangle
attr_accessor :x,:y,:w,:h
attr_accessor :color
def initialize x,y,w,h
@x,@y,@w,@h= x,y,w,h
@color=BLUE
end
def draw ctx
ctx.set_source_rgb *color
ctx.rectangle x,y,w,h
ctx.fill
end
end
class Drawer
def initialize
init_gui
@grobs=[]
@on_rect=nil
end
def init_gui
builder = Gtk::Builder.new
builder.add_from_file('drawing.glade')
@window = builder['applicationwindow2']
@window.signal_connect('destroy'){Gtk.main_quit}
@drawingArea = builder['drawingarea']
init_event_handlers
@window.present
end
def init_event_handlers
@drawingArea.signal_connect "draw" do
ctx=@drawingArea.window.create_cairo_context
redraw
end
@drawingArea.signal_connect("button-press-event") do |widget, event|
puts "mouse pressed"
if @on_rect
@moving=true
else
@grobs << Rectangle.new(event.x,event.y,50,50)
redraw
end
end
@drawingArea.signal_connect("motion-notify-event") do |widget, event|
puts "moving rect" if @moving
if @moving
@on_rect.x=event.x+@dx
@on_rect.y=event.y+@dy
@drawingArea.queue_draw
else
if @on_rect=on_rect?(event)
@dx,@dy=@on_rect.x-event.x,@on_rect.y-event.y
w,h=@on_rect.w,@on_rect.h
@drawingArea.queue_draw
end
end
end
@drawingArea.signal_connect("button-release-event") do |widget, event|
puts "mouse released"
@moving=false
@on_rect=nil
redraw
end
def on_rect? event
for rect in @grobs
if event.x>rect.x && event.x < rect.x+rect.w
if event.y>rect.y && event.y < rect.y+rect.h
return rect
end
end
end
nil
end
end
def redraw
ctx=@drawingArea.window.create_cairo_context
@grobs.each{|grob| grob.draw(ctx)}
end
end #class
Drawer.new
Gtk.main
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkApplicationWindow" id="applicationwindow2">
<property name="name">app_window</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">drawing_2</property>
<property name="has_resize_grip">True</property>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkMenuBar" id="menubar1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="menuitem1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_File</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem1">
<property name="label">gtk-new</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem2">
<property name="label">gtk-open</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem3">
<property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem4">
<property name="label">gtk-save-as</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem5">
<property name="label">gtk-quit</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Edit</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem6">
<property name="label">gtk-cut</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem7">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem8">
<property name="label">gtk-paste</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem9">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_View</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Help</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem10">
<property name="label">gtk-about</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="drawingarea">
<property name="name">drawing_area</property>
<property name="width_request">1000</property>
<property name="height_request">600</property>
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="margin_left">9</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<signal name="drag-begin" handler="drag" swapped="no"/>
<signal name="drag-motion" handler="drag" swapped="no"/>
<signal name="scroll-event" handler="scroll" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">7</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
我不知道 Ruby 内存管理,但我建议您使用 Gtk 提供的 Cairo 上下文,而不是创建您自己的上下文:
--- test.rb.orig 2018-07-28 08:04:24.958448613 +0200
+++ test.rb 2018-07-28 08:05:11.889968403 +0200
@@ -38,9 +38,8 @@ class Drawer
def init_event_handlers
- @drawingArea.signal_connect "draw" do
- ctx=@drawingArea.window.create_cairo_context
- redraw
+ @drawingArea.signal_connect "draw" do |widget, ctx|
+ @grobs.each{|grob| grob.draw(ctx)}
end
@drawingArea.signal_connect("button-press-event") do |widget, event|
我已将此问题报告给维护者 (kou)。线程是 here. This is really a bug (unnecessary object copies). Thanks to him, it is now fixed. The bug fix is here。此修复需要 cairo >=1.15.14.
我正在尝试在 Linux 的 Ruby Gtk3 中制作一个简单的框图编辑器原型。在我看来,我对 Gtk/Cairo 的使用不够充分,或者我遇到了内存泄漏。
症状如下:当我创建一个图形对象(矩形)并移动它时,我的内存使用量迅速增加,如下所示。
问题是Gtk3::DrawingArea.queue_draw.
我错过了什么?
我的版本如下:
- ruby 2.4.0p0(2016-12-24 修订版 57164)[x86_64-linux]
- gtk3 (3.2.7)
require 'gtk3'
BLUE = [0.1,0.0,0.7]
class Rectangle
attr_accessor :x,:y,:w,:h
attr_accessor :color
def initialize x,y,w,h
@x,@y,@w,@h= x,y,w,h
@color=BLUE
end
def draw ctx
ctx.set_source_rgb *color
ctx.rectangle x,y,w,h
ctx.fill
end
end
class Drawer
def initialize
init_gui
@grobs=[]
@on_rect=nil
end
def init_gui
builder = Gtk::Builder.new
builder.add_from_file('drawing.glade')
@window = builder['applicationwindow2']
@window.signal_connect('destroy'){Gtk.main_quit}
@drawingArea = builder['drawingarea']
init_event_handlers
@window.present
end
def init_event_handlers
@drawingArea.signal_connect "draw" do
ctx=@drawingArea.window.create_cairo_context
redraw
end
@drawingArea.signal_connect("button-press-event") do |widget, event|
puts "mouse pressed"
if @on_rect
@moving=true
else
@grobs << Rectangle.new(event.x,event.y,50,50)
redraw
end
end
@drawingArea.signal_connect("motion-notify-event") do |widget, event|
puts "moving rect" if @moving
if @moving
@on_rect.x=event.x+@dx
@on_rect.y=event.y+@dy
@drawingArea.queue_draw
else
if @on_rect=on_rect?(event)
@dx,@dy=@on_rect.x-event.x,@on_rect.y-event.y
w,h=@on_rect.w,@on_rect.h
@drawingArea.queue_draw
end
end
end
@drawingArea.signal_connect("button-release-event") do |widget, event|
puts "mouse released"
@moving=false
@on_rect=nil
redraw
end
def on_rect? event
for rect in @grobs
if event.x>rect.x && event.x < rect.x+rect.w
if event.y>rect.y && event.y < rect.y+rect.h
return rect
end
end
end
nil
end
end
def redraw
ctx=@drawingArea.window.create_cairo_context
@grobs.each{|grob| grob.draw(ctx)}
end
end #class
Drawer.new
Gtk.main
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.18.3 -->
<interface>
<requires lib="gtk+" version="3.12"/>
<object class="GtkApplicationWindow" id="applicationwindow2">
<property name="name">app_window</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes">drawing_2</property>
<property name="has_resize_grip">True</property>
<child>
<object class="GtkBox" id="box1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkMenuBar" id="menubar1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkMenuItem" id="menuitem1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_File</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu1">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem1">
<property name="label">gtk-new</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem2">
<property name="label">gtk-open</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem3">
<property name="label">gtk-save</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem4">
<property name="label">gtk-save-as</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkSeparatorMenuItem" id="separatormenuitem1">
<property name="visible">True</property>
<property name="can_focus">False</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem5">
<property name="label">gtk-quit</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Edit</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu2">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem6">
<property name="label">gtk-cut</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem7">
<property name="label">gtk-copy</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem8">
<property name="label">gtk-paste</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem9">
<property name="label">gtk-delete</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_View</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="menuitem4">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="label" translatable="yes">_Help</property>
<property name="use_underline">True</property>
<child type="submenu">
<object class="GtkMenu" id="menu3">
<property name="visible">True</property>
<property name="can_focus">False</property>
<child>
<object class="GtkImageMenuItem" id="imagemenuitem10">
<property name="label">gtk-about</property>
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="use_underline">True</property>
<property name="use_stock">True</property>
</object>
</child>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkDrawingArea" id="drawingarea">
<property name="name">drawing_area</property>
<property name="width_request">1000</property>
<property name="height_request">600</property>
<property name="visible">True</property>
<property name="app_paintable">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
<property name="margin_left">9</property>
<property name="margin_right">10</property>
<property name="margin_top">10</property>
<property name="margin_bottom">10</property>
<signal name="drag-begin" handler="drag" swapped="no"/>
<signal name="drag-motion" handler="drag" swapped="no"/>
<signal name="scroll-event" handler="scroll" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="padding">7</property>
<property name="pack_type">end</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
</interface>
我不知道 Ruby 内存管理,但我建议您使用 Gtk 提供的 Cairo 上下文,而不是创建您自己的上下文:
--- test.rb.orig 2018-07-28 08:04:24.958448613 +0200
+++ test.rb 2018-07-28 08:05:11.889968403 +0200
@@ -38,9 +38,8 @@ class Drawer
def init_event_handlers
- @drawingArea.signal_connect "draw" do
- ctx=@drawingArea.window.create_cairo_context
- redraw
+ @drawingArea.signal_connect "draw" do |widget, ctx|
+ @grobs.each{|grob| grob.draw(ctx)}
end
@drawingArea.signal_connect("button-press-event") do |widget, event|
我已将此问题报告给维护者 (kou)。线程是 here. This is really a bug (unnecessary object copies). Thanks to him, it is now fixed. The bug fix is here。此修复需要 cairo >=1.15.14.