Ruby GUI progress: Leaking worlds, naming is hard
Tags: ruby, c, computerscience, mruby
Published: 2020-05-20 22:12:40

First and most importantly this project progressed enough that it now needs a catchy, searchable name. I have a couple candidates that are _fine_ though they don't feel _right_. There are Latin words _**pyrope**_ which is a kind of red or rust colored gem though inferior to a ruby gem; and _**punicus**_ which sounds better though it has multiple meanings. Along with the color bright red it may also relate to old Carthage and the Roman bloodlust for Carthaginian genocide which isn't what I want to evoke.

Now the less important stuff

(dev.to embedded gifs aren't functioning at the moment)

https://dev-to-uploads.s3.amazonaws.com/i/7afo799od7nrmgegw90v.gif

I've made a tremendous amount of progress in a few days it's hard to talk about where things are at. I'll detail the most interesting thing I came across the past few days, a few quick hits and then where I'm going next.

I'm using the Dear ImGUI library for creating the GUI. 

{% github https://github.com/ocornut/imgui %}

It's a tiny easily embedded interface library written in an [orthodox C++, or C+, style](https://gist.github.com/bkaradzic/2e39896bc7d8c34e042b). That is, almost entirely functions and structs without any of the C++ nightmares. _Im_ means _immediate mode_ GUI, in contrast to _retained mode_. The primary difference between the two is what controls the state of the interface. Most people are used to working with something that looks like a retained mode GUI. The user of a retained mode interface notifies an underlying system of changes. When notifying GUI system of changes the GUI library changes internal data structures and rebuilds lists of commands for drawing the interface. Working with the browser DOM is a great example of retained mode style. We tell the browser presentation engine when things change by modifying the DOM elements, not by telling the browser how to render a page directly.

Immediate mode style GUI does away with that state and hands the user of the API complete control of how to draw all the elements. Instead of notifying a system when state changes, with an immediate mode design the GUI code you write is instead executed linear sequence every frame. It's best shown with an example, here from the Dear ImGUI demonstration

```c++
if (ImGui::BeginMenuBar())
{
    if (ImGui::BeginMenu("Menu"))
    {
        ShowExampleMenuFile();
        ImGui::EndMenu();
    }
    if (ImGui::BeginMenu("Examples"))
    {
        ImGui::MenuItem("Main menu bar", NULL, &show_app_main_menu_bar);
        ImGui::MenuItem("Console", NULL, &show_app_console);
        ImGui::MenuItem("Log", NULL, &show_app_log);
        ImGui::MenuItem("Simple layout", NULL, &show_app_layout);
        ImGui::MenuItem("Property editor", NULL, &show_app_property_editor);
        ImGui::MenuItem("Long text display", NULL, &show_app_long_text);
        ImGui::MenuItem("Auto-resizing window", NULL, &show_app_auto_resize);
        ImGui::MenuItem("Constrained-resizing window", NULL, &show_app_constrained_resize);
        ImGui::MenuItem("Simple overlay", NULL, &show_app_simple_overlay);
        ImGui::MenuItem("Manipulating window titles", NULL, &show_app_window_titles);
        ImGui::MenuItem("Custom rendering", NULL, &show_app_custom_rendering);
        ImGui::MenuItem("Documents", NULL, &show_app_documents);
        ImGui::EndMenu();
    }
    if (ImGui::BeginMenu("Tools"))
    {
        ImGui::MenuItem("Metrics", NULL, &show_app_metrics);
        ImGui::MenuItem("Style Editor", NULL, &show_app_style_editor);
        ImGui::MenuItem("About Dear ImGui", NULL, &show_app_about);
        ImGui::EndMenu();
    }
    ImGui::EndMenuBar();
}
```

Reading this top to bottom you know exactly what's going on. Two points of interest. One, the `if()` calls around ImGUI functions generally mean 'if this element is visible'. If that element is completely obscured in some way it doesn't execute that code path. Amazingly simple! The other interesting point is Dear ImGUI elements usually take a boolean parameter. This is a reference to an address of memory representing if the element should be displayed or disabled. The interface for creating a new window is `ImGui::Begin("Ruby Eval", &ruby_eval_open)`. The second parameter becomes `false` when the user closes the window. We can re-open the window whenever we want by setting `ruby_eval_open` to true.

(This pattern works fantastically in C and comes back to bite us when we're in Ruby land. Discussions for another day)

Casey Muratori describes the differences and benefits of an immediate mode in great detail here

{% youtube Z1qyvQsjK5Y %}

Setting aside HN style nonsense on the computational efficiency or wastefulness of this pattern, the benefit to this is we have full control over what we want to draw when and the clear simplicity of it.

Back to making the GUI work with Ruby. It's a straightforward process making an mruby class in C in which calling the methods on the class in Ruby execute C code. For now I'm generally mapping the ImGUI functions to a GUI module one to one.

In a World...
===

The main program loop is in C. It looks something like this, abbreviated

```c++
while(!done) {
  process_window_messages();
  begin_new_frame();
  mrb_load_string(mrb, "World.render");
  render_frame();
}
```

The important bit here is `mrb_load_string`. It's essentially an `eval` into the mruby state. Surrounding the call processes incoming Windows messages, sets up the next frame, then renders the frame. Presently `World.render` is the entry point for each frame from the Ruby side. World is another simple construction. It maintains a list of all the open windows. When we call `World.render` it iterates over all of the open windows calling `render` on each.

```ruby
module World
  @open_windows = []

  def self.add_window window
    raise unless window.respond_to? :render

    @open_windows.push window unless @open_windows.include? window
  end

  def self.remove_window window
    @open_windows.delete window
  end

  def self.render
    # TODO see if current active window respond_to? menu_bar,
    # otherwise render this
    GUI.main_menu do
      GUI.menu("Tools") do
        GUI.menu_item "Class Browser", "Ctrl+I" do
          ClassBrowser.open
        end

        GUI.menu_item "Open Transcript", "Ctrl+T" do
          Transcript.open
        end

        GUI.menu_item "Open Stats", "" do
          Stats.open
        end
      end
    end

    @open_windows.each { |window| window.render }
  rescue => ex
    puts ex
  end
end
```

When you want to open a window, call `World.add_window thing`. Closing it is `World.remove_window thing`. Simple and straight forward. 

Implementing a `render` method is a simple set of ImGUI calls. For example, this is the full `Transcript`, a global console-like thing where you can print stuff to

```ruby
module Transcript
  @lines = []

  def self.add obj
    @lines.push obj.to_s
  end

  def self.clear
    @lines = []
  end

  def self.open
    World.add_window self
  end

  def self.close
    World.remove_window self
  end

  def self.render
    GUI.new_window 'Transcript' do
      @lines.each { |line| GUI.text line }
    end
  end
end
```

This all works fine and I was reasonably satisfied with it so far. Until one time I left the program open for several hours I noticed the program acquired multiple gigabytes of working memory. It usually allocates about 300kb total. Classic memory leak somewhere. Getting to the heart of why is a long saga detailed in this short Github exchange. 

{% github https://github.com/mruby/mruby/issues/5001 %} 

The short answer is this innocent looking C call

```c++
mrb_load_string(mrb, "World.render");
```

Which it turns out any C function named `mrb_load_*` creates an RProc object in the current mruby state. However, that RProc is not garbage collected at any point! For reasons I sort understand [this document](https://github.com/mruby/mruby/blob/master/doc/guides/gc-arena-howto.md) details the situation. However it feels surprising that the `mrb_load_*` function calls don't automatically protect against that situation.

Going back to the main loop, we call `mrb_load_string` every frame, yielding to the `World` object for rendering all the windows. That generates an `RProc` object in the VM which the garbage collector never collects. We do the at least 60 times a second, so we're leaking 60 RProc objects a second. A basic Ruby object is tiny, 36 bytes or so which is why this behavior was not noticeable.

I've since added basic GC stats into the interface so I can observe such things

https://dev-to-uploads.s3.amazonaws.com/i/jam8uak08klab6mwbvum.gif

Next up
===

As it stands currently this is primarily a C program driving the mruby state as shown by the main program loop is in C. I want to flip this around where the main program loop is in Ruby which calls back to C code when necessary. I want the C main function to look something like

```c++
int main() {
  setup_state();
  mrb_load_string(mrb, "GUI.start");
  cleanup();
  return 0;
}
```

And then the Ruby side looking something like

```ruby
class GUI
  def self.start
    while is_running do
      process_window_events
      begin_next_frame
      render
      present_frame
    end
  end
end
```

I think this will take a significant amount of work and I know it will pay off later. Reading through past mruby issues and recommendations by matz the general solution to most of the integration problems is 'push what you're doing into the mruby VM instead of C'.

I didn't think of moving the loop into ruby this way until I saw this demonstration of a madman using mruby for creating a Sega Dreamcast game. This combination of esoteric things, homebrew Dreamcast development and mruby, make me quite happy it exists.

{% github https://github.com/yujiyokoo/mrbtris-dreamcast %}

{% youtube ni-1x5Esa_o %}