Search code examples
rubydrawingcpu-usageruby-1.9.3libgosu

High CPU Usage in Gosu Drawing Program with Ruby


I have been tinkering with making a drawing application in Gosu using Ruby, but am running into some high CPU usage. I know one possible cause.

First, I am simply adding a primitive to an hash every time the user clicks the mouse button (or if they hold down the mouse button, every time the screen refreshes, which I think happen around 60 times per second). Each time the screen redraws I have the program loop through the hash and redraw all the primitives. If someone holds down their mouse button it will just keep overlapping primitives on top of each other and in other situations the primitives just layer. I can see how that would eventually eat up a bunch of memory in the hash.

I feel that the best solution would be to use some sort of lower level function that simply colors pixels on the screen and doesn't have to store them in an hash. I'm not sure if Gosu supports that. Could someone either help me with this in Gosu or suggest a different method and/or another program to use? Thank you!

(Below is the complete code for my program.)

require 'Gosu'
require 'socket'

class GameWindow < Gosu::Window
  def initialize

    super 1280, 960, false
    self.caption = "Drawz Server"

    @background_image = Gosu::Image.new(self, "media/Space.png", true)


    @player = Player.new(self)
    # Platform is the blank image for the epicycle to be drawn on.
    @earth_image = Gosu::Image.new(self, "media/earth.bmp", true)
    @cursor_image = Gosu::Image.new(self, "media/cursor.bmp", true)

    puts "Please enter port..."
    port = gets.strip!
    puts "Server is running."

    # Initialized Instance variables for all methods.
    @square_drawing_number = 0
    @client_coordinates_array = []
    @client_coordinates_array_array = []
    @client_quad_hash = {}
    server = TCPServer.open("10.64.8.2", port)
    @client = server.accept

    manage_client_data = Thread.new do
            x = 0
            loop do
                @client_coordinates_array[x] = @client.gets
                x += 1
            end
        end

    @x = 0
    @quad_hash = {}
    # Start up value of things in draw method, because draw is called before update. Hence I put the start up values here so that when draw is called it has location of things in
    # its arguments.
    @cx = 0 
    @cy = 0
  end



class QuadCoords
    attr_reader :x1, :y1, :x2, :y2, :x3, :y3, :x4, :y4

    def initialize(x1, y1, x2, y2, x3, y3, x4, y4)
        @x1 = x1
        @y1 = y1
        @x2 = x2
        @y2 = y2
        @x3 = x3
        @y3 = y3
        @x4 = x4
        @y4 = y4
    end
end

    # Mean't to change location (x and y) and such of things in draw method.

    def update
        @cx = mouse_x
        @cy = mouse_y
        @x += 1
        # Adds square locations to hash which is then accessed by draw function to retrieve and draw squares.
        if button_down? Gosu::MsLeft
            @x += 1
            quad_coords_obj = QuadCoords.new(@cx - 5, @cy - 5, @cx, @cy - 5, @cx - 5, @cy, @cx, @cy)
            @quad_hash["quad#{@x}"] = quad_coords_obj
            # Sends location of squares to client for it to draw.
            @client.puts "#{quad_coords_obj.x1}---#{quad_coords_obj.y1}---#{quad_coords_obj.x2}---#{quad_coords_obj.y2}---#{quad_coords_obj.x3}---#{quad_coords_obj.y3}---#{quad_coords_obj.x4}---#{quad_coords_obj.y4}"
        end

        if @client_coordinates_array.length > @square_drawing_number
            new_squares_to_add = @client_coordinates_array.length - @square_drawing_number
            @client_coordinates_array[-1 * new_squares_to_add..-1].each do |value|
                @client_coordinates_array_array << value.split(/\---/)
            end
            x = 1
            new_squares_to_add.times do
                @x += 1
                @client_quad_coords_obj = QuadCoords.new(@client_coordinates_array_array[-1 * x][0].to_i,
                @client_coordinates_array_array[-1 * x][1].to_i,
                @client_coordinates_array_array[-1 * x][2].to_i,
                @client_coordinates_array_array[-1 * x][3].to_i,
                @client_coordinates_array_array[-1 * x][4].to_i,
                @client_coordinates_array_array[-1 * x][5].to_i, 
                @client_coordinates_array_array[-1 * x][6].to_i, 
                @client_coordinates_array_array[-1 * x][7].to_i)
                @client_quad_hash["quad#{@x}"] = @client_quad_coords_obj
                x += 1
                @square_drawing_number += 1
            end
        end


    end


  # Draw is called before update.
  def draw

    @background_image.draw(0,0,0)
    @earth_image.draw(295,215,1)
    # x1,y1 = Upper Left Hand Corner; x2,y2 = Lower Left Hand Corner; x3,y3 = Upper Right Hand Corner; x4,y4 = Lower Right Hand Corner
    @quad_hash.each_value do |value|
        draw_quad(value.x1, value.y1, 0xff00ffff, value.x2, value.y2, 0xff00ffff, value.x3, value.y3, 0xff00ffff, value.x4, value.y4, 0xff00ffff, z = 0, mode = :default)
    end

    @client_quad_hash.each_value do |value|
        draw_quad(value.x1, value.y1, 0xff00ff00, value.x2, value.y2, 0xff00ff00, value.x3, value.y3, 0xff00ff00, value.x4, value.y4, 0xff00ff00, z = 0, mode = :default)
    end

    @cursor_image.draw(@cx, @cy, 1)

  end

  def button_down(id)
    if id == Gosu::KbEscape
      close
    end
  end
end



class Player
    attr_accessor :x, :y, :update_called
  def initialize(window)
    @image = Gosu::Image.new(window, "media/Sun.bmp", false)
  end
end



window = GameWindow.new
window.show

Solution

  • You can use TexPlay for drawing over Gosu::Image and then render this image. http://banisterfiend.wordpress.com/2008/08/23/texplay-an-image-manipulation-tool-for-ruby-and-gosu/ for example:

    image1.paint {
      circle 20,20,10, :color => :red
      rect 40,40,50,50, :color => :green
      pixel 60,60, :color => :blue
    }
    

    There are alternatives: RMagick (that's more powerful, but much more complex to install\distribute) and Ashton (it has methods of rendering to OpenGL texture, so it's faster, but requires some understanding of OpenGL).