Open Nathan-MV opened 5 months ago
mruby can handle ~50,000 Ruby method calls per frame at 60fps on low/mid range devices.
MRI can do around 4x that.
nb. Calling a block has the same overhead as calling a method.
So with:
array = [1] * 10000
This loop costs 10000 more method calls:
array.each do |el|
# do nothing
end
Than:
n = array.size
i = 0
while i < n
el = array[i]
i += 1
# do nothing
end
Instance variables are free to access, relative to calling a method.
As a result it's much more efficient to ask game objects to handle themselves:
e.g.
class Tile
attr_accessor %i(x y w h vx vy color)
def tick
if @vx != 0
@x += @vx
end
if @vy != 0
@y += @vy
end
DrawRectangle(@x, @y, @w, @h, @color)
end
end
def tick()
n = world.size
i = 0
while i < n
thing = world[i]
thing.tick
i += 1
end
end
This is the fastest way I know to update and draw many game objects in mRuby.
I bet the difference between LiteRGSS2, Taylor and SDL in your tests comes down to method call overhead in the implementation of their Ruby APIs.
Once they call into C, the difference is probably negligible in comparison!
I had tested SDL2 using mkxp-z and it gave 10k but using RGU which is using SDL3 it gives 50 fps with 20k both of them using Angle to Vulkan, as they are used to do the same thing, aka being a RGSS1/2/3 player for RPG Maker XP/VX/VXA i used the same benchmark for both (LiteRGSS is a little different (It was initially made for RGSS1(RPG Maker XP) but then it was designed solely for PokemonSDK and changed things too much on both ends) so it needs some heavy modifications and instead of being an executable it is a shared library so i put the ruby binary pokemonsdk uses with it in the download)
class SpriteGenerator
DEFAULT_SPRITE_COUNT = 20_000
SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480
SPRITE_SIZE = 32
def initialize
@sprite_count = DEFAULT_SPRITE_COUNT
@viewport = Viewport.new(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)
@bitmap = nil
end
def run
initialize_sprites
loop do
Graphics.update
end
rescue StandardError
puts "\nEND\n"
end
private
def initialize_sprites
puts "Sprite Count: #{@sprite_count}"
@sprites = @sprite_count.times.map do |i|
initialize_bitmap if (i % 100).zero?
initialize_sprite
end
end
def initialize_bitmap
@bitmap = Bitmap.new(320, 320)
@bitmap.fill_rect(0, 0, 320, 320, random_color)
end
def initialize_sprite
sprite = Sprite.new(@viewport)
sprite.bitmap = @bitmap
sprite.x = rand(SCREEN_WIDTH) - SPRITE_SIZE
sprite.y = rand(SCREEN_HEIGHT) - SPRITE_SIZE
set_sprite_src_rect(sprite)
sprite
end
def set_sprite_src_rect(sprite)
j = @sprite_count % 100
sprite.src_rect.set(32 * (j / 10), 32 * (j % 10), SPRITE_SIZE, SPRITE_SIZE)
end
def random_color
Color.new(rand(256), rand(256), rand(256), 32)
end
end
SpriteGenerator.new.run
I've been busy with the refactor at the moment but performance is something I've kept a little bit of an eye on. I'm probably going to have a quick pass over the code base once I've finished the refactor to see what I can do about performance.
With the refactor there is a lot less ruby -> ruby -> c++, it's now more ruby -> c++, I'm hoping this will help mitigate a little bit of the performance as I too feel it could be better.
Ray said he tends to ignore code that could be more performatic for code that are simpler to understand for the sake of his students
It appears that creating your own Vector2 class in Ruby increases benchmark performance by 2x https://github.com/vaiorabbit/raylib-bindings/blob/main/examples/textures_bunnymark_optimized.rb
I wonder what's going on here as i doubt it's entirely Ruby's fault, LiteRGSS2 (LiteCGSS is the backend with SFML2 and LiteRGSS2 are the Ruby bindings) can draw around 50k at a stable 60 fps (or is SFML2 just faster than Raylib/SDL2 and i didn't know? xD (SDL2 seems to draw 10k at a stable 60)) https://gitlab.com/NuriYuri/litecgss https://gitlab.com/pokemonsdk/litergss2
Benchmark: https://www.mediafire.com/file/zi7d3qwc0nk93dd/litergss2_-_bench.7z/file