kojix2 / LibUI

A portable GUI library for Ruby
MIT License
203 stars 10 forks source link

Fiddle::Pointer and Garbage-Collection Protection #23

Open rubyFeedback opened 3 years ago

rubyFeedback commented 3 years ago

Hey there kojix2,

First off - thank you for the example(s) provided for coloured text.

It works fine on my system. The API is a bit complicated, assumingly due to upstream libui and how it handles strings (attributed/unattributed), but the end result works - I got the green and red colours.

I also realized that we can already display images as well, even if this requires embedding into some table - but I realized that I can just display a single example as-such and it works too, so this is kind of "displaying an image in libui / ruby-libui".

Anyway.

I started a project that is similar to gtk_paradise (for ruby-gtk) in that it tries to enhance the official bindings for libui, which you make available via the ruby bindings to LibUI here. I thus did so for libui, or ruby-libui that you provide, as well.

See here:

https://rubygems.org/gems/libui_paradise

Please do not be too critical of the code - it is one giant pile of hack upon hack upon hack upon hack. But it was fun to modify Fiddle::Pointer - that even works! It feels like oldschool evil.rb (if anyone remembers it ... shapechanging objects ... the button {} idea I had back then when evil.rb was around. No idea what happened to it after ruby 1.8.7 though ...)

I also managed to create tons of segfaults already via ruby-libui. :D

It is a bit difficult to know why something segfaulted, so I try to make only small changes, see what breaks and what does not break, and continue. In pure ruby we get better results and informations, e. g. which particular line caused the issue. We can also use pp caller() - I am not sure if something like this is available for libui, but this is an aside.

This brings me to the issue request here, though.

I looked at your examples and in some of them you add code that protects against garbage collection segfaults (I think).

For instance, in the file basic_draw_text.rb you have this code part including the comment explaining why it is done:

handler.Draw         = handler_draw_event
# Assigning to local variables
# This is intended to protect Fiddle::Closure from garbage collection.
handler.MouseEvent   = (c1 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
handler.MouseCrossed = (c2 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
handler.DragBroken   = (c3 = Fiddle::Closure::BlockCaller.new(0, [0]) {})
handler.KeyEvent     = (c4 = Fiddle::Closure::BlockCaller.new(0, [0]) {})

I could comment out the last two lines, via '#', but when I also comment out the "handler.MouseCrossed = (c2 = Fiddle::Closure::BlockCaller.new(0, [0]) {})" part then the program segfaults/crashes at once. So I understand that the code you use there is necessary and mandatory to make the program work - otherwise it will crash.

But!

In my opinion this is not very elegant. It is some kind of boilerplate code.

Would it be possible that there could be a more elegant solution offered?

I can not recommend a particular solution as such, because I do not know the internals or how C really works (pointers are complicated for me), and I don't know enough about the ruby-to-C bridge. I know a little bit, but not enough to understand what is going on in the end.

What I am thinking is to have some kind of API, directly onto toplevel LibUI, that could be used to activate garbage protection or the like.

In other words: something that is a single line, that could replace ad-hoc code like the above. That way we could do something like:

UI.garbage_protection
UI.anti_segfaults

Or something like that. I don't propose those names, but just as an example, something that could be used that then allows us to write code that could break due to garbage collection kicking in otherwise. Just like the trick you do by assigning to variables, but just without having to do that in a given .rb file.

This is not the only instance where BlockCaller is used.

For example:

basic_table.rb:              blockcaller = Fiddle::Closure::BlockCaller.new(*args, &block)
basic_area.rb:               handler_draw_event   = Fiddle::Closure::BlockCaller.new(0, [1, 1, 1]) do |_, _, area_draw_params|
basic_table_image.rb:        blockcaller = Fiddle::Closure::BlockCaller.new(*args, &block)
histogram.rb:                handler.MouseEvent   = (c1 = Fiddle::Closure::BlockCaller.new(0, [0]) {})

So it is used in the more complicated examples.

From my point of view this is all "necessary, protective boilerplate code", but I think that when an end-user is making use of libui - which, by the way, works great on windows out-of-the-box - then the end-user should not have to be concerned with this, IMO, if it can be avoided. But as said, I don't fully understand it - I thought that assigning to just one variable should suffice, but the example of basic_draw_text.rb shows that this may require more local variables to exist.

API-wise I don't really know how to best implement this, and don't worry if that needs time to think through and design, e. g. end-of-the-year changes only. :)

I just wanted to mention this in regards to from an end-user perspective, when you think about user interface elements then having to think about fiddle-closures and segfaults is a bit distracting. I understand that ruby is ultimately just cool syntactic sugar over C, but ideally it would be great if we could think just about it from the "assumed" ruby side - the one matz mentioned a long time ago in the interview from 2003 ( https://www.artima.com/articles/the-philosophy-of-ruby ).

One of my favourite matz quotes is still this one he gave almost 20 years ago:

"Often people, especially computer engineers, focus on the machines. They think, "By doing this, the machine will run faster. By doing this, the machine will run more effectively. By doing this, the machine will something something something." They are focusing on machines. But in fact we need to focus on humans, on how humans care about doing programming or operating the application of the machines. We are the masters. They are the slaves."

Anyway, thanks for reading, please as always feel free to close/disregard/implement-as-you-see fit - this is mostly feedback anyway. \o/

PS: I really think many ruby folks are unaware of libui. Even the example of the RING programming language, they added bindings to libui in 2020. But when I looked at old changelogs then libui itself is already available since 2015 ... so we now have +6 years. Even if RING is still fairly young, I think people often don't quite know about alternatives. I had a look at gtk2-based applications on rubygems.org recently and was surprised to see how many used to exist, although they were created largely from 2009 to 2015 or so only.

At any rate this was just mostly information - sorry for creating so many issues. I think there are still many ruby users who never heard of libui before; I only found it indirectly, via ruby-gtk and looking at projects on github pages.

On that particular side note, I also accidentally found the Ring programming language, by searching tutorials for libui. :D

https://github.com/ring-lang/ring/tree/master/extensions/ringlibui

They also show some drawing-shape examples, which is quite nifty. Ultimately we could use that to build more GUI elements.

(Ring itself is not as elegant as ruby, though, and the total download size was in the range of ~400 MB or so, which is just insane ... but from a GUI-point of view I think Ring does bring in a fresh idea into working with computers.)

kojix2 commented 3 years ago

Well, I'm not good at English so it took me a long time to read a lot of English sentences.

And, of course, I don't like the code as shown below. In fact, almost no one likes this kind of code.

handler.MouseEvent   = (c1 = Fiddle::Closure::BlockCaller.new(0, [0]) {})

First of all, I think the name Fiddle::Closure::BlockCaller is not good.

You always need to be careful about garbage collection when assigning pointers to structures. I forgot about it and made a wrong bug report. The answer is worth a look.

Fiddle also has other problems related to this.

I use Fiddle in libui because it's the standard library and doesn't require any additional gem. In fact, I think Ruby-FFI is better in many ways.

This problem is not one, but a complex combination of issues. The things about garbage collection are beyond my understanding. So I agree that there is a problem. But I don't know how to solve it either.

kojix2 commented 3 years ago

Also, thank you for informing me about the libui_paradise you created !! I've uploaded it to gitlab so that I can refer to it without forgetting (as unofficial repository).

https://gitlab.com/kojix2/libui_paradise

(The license is MIT, so I think it's okay, but please let me know if it's not).

I've got all the samples working. I would like to use it as a reference for future development.

kojix2 commented 3 years ago

Drawing-shape examples are good to have. However, I have other high-priority work to do, so it may be difficult to work on this. I think it will take at least some time. Of course, pull requests are always welcome, and if you can add it to libui_paradise, I'll consider importing it into the libui reopository. And what is the biorobe project? This is very interesting.

Good luck.

kojix2 commented 3 years ago

@rubyFeedback

Would you like to make libui_paradise available as a repository?

I would like to check the source code of libui_paradise, but it takes a lot of time to unzip the gem file and observe the content. I would like to have a web interface to check the source code.

Furthermore, I have a problem when importing libui_paradise source code into LibUI. It becomes difficult to credit your name to the commit of the source code. Perhaps you don't care about this, but I do.

Besides Github, there are many other options, such as Gtilab, BitBucket, and Gitee, which is behind the Chinese iron firewall. Please consider them.

Good luck.