Tuesday, February 16, 2021

Glimmer DSL for SWT Mandelbrot Fractal and Hello, Cursor! Samples

UPDATED WITH CODE: 2021-02-19


It includes two new samples:
  • Mandelbrot Fractal: this is a very well known Computer Science/Math algorithm that renders a fractal at various zoom levels, demonstrating the repetitive nature of fractals. During my bachelor of Computer Science at McGill University (Montreal, QC, Canada), I remember spending very long and tiring nights at the computer lab to implement an Assembly language renderer of the Mandelbrot Fractal. Much has changed since then. We have multi-core processors today, let alone the wonderful Ruby programming language, so I wrote this with Glimmer DSL for SWT by taking advantage of the multi-threaded JRuby and saturating all CPU cores to finish calculating Mandelbrot points in less than 10 seconds on 4 CPU cores. It takes advantage of the idea of Thread Pools, resulting in very terse multi-threaded code, which is implemented using concurrent-ruby, a multi-threading library included in Glimmer. The sample supports unlimited zooming, pre-calculated in the background with the window title bar notifying you once higher zooms are available. It also allows panning with scrollbars or mouse dragging. Enjoy!
  • Hello, Cursor!: introduces the various mouse cursors available out of the box in SWT.


Mandelbrot Fractal Sample



Hello, Cursor! Sample

Below is the multi-threaded model code and Glimmer GUI DSL code of the Mandelbrot Fractal initial version (without zooming). The way I parallelized processing is by realizing that the calculation of each Mandelbrot Fractal point (happening in a loop) is independent of all other points, and thus I could distribute and process in separate threads. Of course, I don't want more than 4 threads (or 8 hyperthreads), which depends on the number of CPU cores (I have 4) to avoid thread competition for CPUs slowing down the calculation. As such, I relied on a Thread Pool constrained by as many processors as available. See below how simple the code to achieve that is with Ruby and the concurrent-ruby gem. Otherwise, the Glimmer DSL for SWT GUI simply paints an image as a form of image buffering (to avoid Mandelbrot pixel repaints on GUI repaints) and then puts it inside a canvas to display.

# From: https://github.com/AndyObtiva/glimmer-dsl-swt/blob/master/samples/elaborate/mandelbrot_fractal.rb
require 'glimmer-dsl-swt'
require 'complex'
require 'concurrent-ruby'
class Mandelbrot
attr_accessor :max_iterations
def initialize(max_iterations)
@max_iterations = max_iterations
end
def calculate_all(x_array, y_array)
thread_pool = Concurrent::FixedThreadPool.new(Concurrent.processor_count)
width = x_array.size
height = y_array.size
pixel_rows_array = Concurrent::Array.new(height)
height.times do |y|
pixel_rows_array[y] ||= Concurrent::Array.new(width)
width.times do |x|
thread_pool.post do
pixel_rows_array[y][x] = calculate(x_array[x], y_array[y]).last
end
end
end
thread_pool.shutdown
thread_pool.wait_for_termination
pixel_rows_array
end
# Mandelbrot point calculation implementation
# Courtesy of open-source code at:
# https://github.com/gotbadger/ruby-mandelbrot
def calculate(x,y)
base_case = [Complex(x,y), 0]
Array.new(max_iterations, base_case).inject(base_case) do |prev ,base|
z, itr = prev
c, _ = base
val = z*z + c
itr += 1 unless val.abs < 2
[val, itr]
end
end
end
class MandelbrotFractal
include Glimmer::UI::CustomShell
before_body {
@colors = [[0, 0, 0]] + 40.times.map { |i| [255 - i*5, 255 - i*5, 55 + i*5] }
@colors = @colors.map {|color_data| rgb(*color_data).swt_color}
mandelbrot = Mandelbrot.new(@colors.size - 1)
@y_array = (1.0).step(-1,-0.0030).to_a
@x_array = (-2.0).step(0.5,0.0030).to_a
@height = @y_array.size
@width = @x_array.size
@pixel_rows_array = mandelbrot.calculate_all(@x_array, @y_array)
@image = Image.new(display.swt_display, @width, @height)
image_gc = org.eclipse.swt.graphics.GC.new(@image)
@height.times { |y|
@width.times { |x|
new_foreground = @colors[@pixel_rows_array[y][x]]
image_gc.foreground = @current_foreground = new_foreground unless new_foreground == @current_foreground
image_gc.draw_point x, y
}
}
}
body {
shell {
text 'Mandelbrot Fractal'
minimum_size @width, @height + 12
image @image
canvas {
image(@image, 0, 0)
}
}
}
end
MandelbrotFractal.launch

One final note is that obviously from a Software Engineering point of view, Ruby wouldn't be the fastest language at calculating the Mandelbrot Fractal points given it is dynamically typed and interpreted. So, think of this as just a Glimmer GUI learning exercise taking advantage of the fact that modern computers ship with so many cores that even Ruby has a fighting a chance, albeit not the fastest, for this type of problem. That said, for serious applications, if you offload the calculation to another statically typed language like Java while integrating with JRuby for the Glimmer GUI, this sample should run much faster.

Happy Glimmering!

You may learn more about how the Mandelbrot Fractal sample got a progress bar and menus in this following blog post.


No comments: