Replacing finalizers with destructors for gintro seems to work in principle, at least I got cairo_anim.nim running with a few hacks.
Problem is similar as with finalizers -- for callback vars owned by gtk we do not want a finalizer or destroy call, while for ordinary use we want the call. For finalizers I made the var global to prevent finalizer call. But for destroy global pragma makes no difference. What I have is basically
type
Context00* = object
Context* = object of RootObj
impl*: ptr Context00
proc `=destroy`*(s: var Context) =
echo "=destroy called"
if s.impl != nil:
cairo_destroy(s.impl)
s.impl = nil
proc `=sink`*(s: var Context; x: Context) =
echo "=sink called"
if s.impl != nil and s.impl != x.impl:
cairo_destroy(s.impl)
s.impl = nil
s.impl = x.impl
and the macro generated callback proc
proc connect_for_signal_cdecl_draw1(self: ptr Widget00; cr: ptr cairo.Context00; data: pointer): gboolean {.cdecl.} =
let h: pointer = g_object_get_qdata(self, Quark)
var cr1: cairo.Context
cr1.impl = cr
drawingAreaDrawCb(cast[DrawingArea](h), cr1).ord.gboolean
(Here drawingAreaDrawCb() is the user provided high level callback proc.)
Unfortunately at the end of connect proc =destroy is called for cr1, resulting in freeing cr which is owned by gtk.
I tried sink and lent modifier for cr1, but that does not work, at least not in macro. So there remain two solutions:
1: cr1.impl = nil at end of connect proc, so destroy is called but does nothing.
2: Use ContextXX* = object of Context for type of cr1, that prevents call of =destroy currently, but I am not sure if that is guaranteed.
Shouldn't context stay a ref object?
Are you familiar with GTK and cairo?
Well ref object without GC may be fine, but I think that is still work in progress for Araq, and I have to learn that then.
But destructors work already, as they are used for seqs and strings, and it is my feeling that value objects with destructors should work for the proxy objects fine. One problem is when we get objects back from gtk, for example gtk_get_child(gtk_table). For that we get a ref object currently, but its a plain widget, so we have to cast it to whatever it really is, maybe a button. It not nice currently. I think I can solve that with a generic get(), like getChildButton which gives us the Button widget or raises an exception, or maybe returns false when widget is not a Button.
But well, that explanations may make not much sense to you.
The concrete problem with ref objects and GC is caro drawing as in
https://github.com/StefanSalewski/gintro/blob/master/examples/drawingarea.nim
proc paint(this: PDA) =
if this.surf != nil:
cairosurfaceDestroy(this.surf.impl) # we may have to fix that later for automatically destroy
this.surf = this.darea.window.createSimilarSurface(Content.color,
this.darea.allocatedWidth, this.darea.allocatedHeight)
let cr = newContext(this.surf)
Here we use temporary surface and context -- handling that with GC is problematic of course, as the proxy object is tiny, but the cairo surface is large, and that proc is called 60 times a second. So the GC may delay freeing for a long time, which may give us some 100 megabytes of temporary allocated mem for surfaces. Well we may do a GC_fullcollect for each proc call, but that is not what the GC is designed for.
I had the same issue with Ruby in http://ssalewski.de/PetEd.html.en resulting in crashes.
I think value proxy objects with destructors should catch that well. And if we use them for cairo, we can use them for all gtk and can avoid the GC fully. I would guess that is what gtkmm does also.
For copy, isNil, == you are right of course, but I think we can define that well.
Are you familiar with GTK and cairo?
No I'm not. I am quite familiar with C/C++ FFI, ref and ptr though in high performance context where allocation is very expensive (GPUs memory allocation) and the GC is a bottleneck.
In your case, if you use ptr or template for aliasing instead of let/var you will not have a =destroy call at the end of the function.
In your case, if you use ptr or template for aliasing instead of let/var you will not ...
Yes, but I can not see how this really helps.
gintro attemps to provide a simple gtk api similar to maybe Python or Ruby, so the user will be able to just allocate a surface and a cairo context, and then do the painting. (Buffered painting on custom surface in this case, plain painting in the draw callback is already working fine.) This is
proc paint(this: PDA) =
this.surf = this.darea.window.createSimilarSurface(Content.color,
this.darea.allocatedWidth, this.darea.allocatedHeight)
let cr = newContext(this.surf)
cr.lineTo(myX, myY)
And my suggestions in my initial post works both fine...
Setting impl to nil in the macro generated code avoids a cairo_destroy() for sure. And the trick "type ContextXX = object of Context; var cr: ContextXX" avoids the =destroy call too, but I am not sure if Araq may change that later. I had the feeling that lent or sink annotations for local variables may also make a difference, but I may not really understand them, have not yet seen a working example code.
Thanks for your reply.
I think I already tried something like
var cr1: sink cairo.Context
but it does not compiled -- the reason may be that I do not yet understand sink for local vars good enough.
But I just read your blog post about new runtime and owned refs more carefully, and I got the impression that owned refs may be the perfect solution: We get deterministic deallocation and keep the benefits of ref objects. And nearly no code change will be necessary, only adding a few owned annotations. (I assume that finalizers continue to work as they do now.)
PS:
For my comment from yesterday that getChild() or getParent() would not work too nice currently, that should be wrong indeed. Gintro uses the gobject toggle_ref mechanism, so getChild() gives us indeed a Widget, but it is the same Nim object what we put into the container, so we can do Nim's of operator to test what it really was, maybe a label or a button. So there is really an advantage when we use ref objects for the proxy objects.
But: Will gc_ref() work at all with the new runtime? Currently gc_ref() is used to keep the proxy object alive when a widget is put into a container, so that we can recover the original Nim object from the gtk container. This may not work without GC. So maybe we can use newruntime with owned refs only for the cairo objects like surface and cairo_context, but that is OK, as Cairo is what is critical for memory management. Widgets are in no way critical.