rubyboy 1.3.1 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +18 -1
- data/exe/rubyboy +23 -7
- data/exe/rubyboy-bench +43 -0
- data/lib/bench.rb +6 -7
- data/lib/rubyboy/emulator.rb +80 -0
- data/lib/rubyboy/version.rb +1 -1
- data/lib/rubyboy.rb +1 -83
- data/resource/screenshots/pokemon.png +0 -0
- data/resource/screenshots/puyopuyo.png +0 -0
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d92d323c7cf2886b1808eb6e8e2aabb786f5f4a4c66069e2788924ffa7ce72e
|
4
|
+
data.tar.gz: e0d5c4e1f1039f54283a5cd307c8fabe29f9dc019516501f9075560c16612af4
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6960fa54f9150fafd79388eb9dafc9f787188a7ac1b097885ce3c8cbfe59ea7fbac84f3b8d3008f36c818ae3925b8965fbefaf8793d9211b37e8eb214dbbf9ca
|
7
|
+
data.tar.gz: 814ee8ce6c307828dde433847eabbb6d681c37d7bf424b6a0e90731c5bc3869060bc6baeaa79f15dfe5364532f070e02f97d4fb99844f115e8d341cc678ee0c4
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [1.3.2] - 2024-05-04
|
4
|
+
|
5
|
+
- Revert "Enable YJIT when initialize"
|
6
|
+
- Add rubyboy-bench command
|
7
|
+
- Refactor Console class to use Emulator class
|
8
|
+
- Add option parsing and update default ROM path
|
9
|
+
|
3
10
|
## [1.3.1] - 2024-03-17
|
4
11
|
|
5
12
|
- Enable YJIT when initialize
|
data/README.md
CHANGED
@@ -6,6 +6,12 @@
|
|
6
6
|
|
7
7
|
A Game Boy emulator written in Ruby
|
8
8
|
|
9
|
+
## Screenshots
|
10
|
+
<div align="center">
|
11
|
+
<img src="/resource/screenshots/pokemon.png" width="400px"/>
|
12
|
+
<img src="/resource/screenshots/puyopuyo.png" width="400px"/>
|
13
|
+
</div>
|
14
|
+
|
9
15
|
## Requirements
|
10
16
|
[SDL2](https://wiki.libsdl.org/SDL2/Installation)
|
11
17
|
|
@@ -26,7 +32,18 @@ If bundler is not being used to manage dependencies, install the gem by executin
|
|
26
32
|
|
27
33
|
## Usage
|
28
34
|
|
29
|
-
$ rubyboy <rom_path>
|
35
|
+
$ RUBYOPT=--yjit rubyboy <rom_path>
|
36
|
+
|
37
|
+
| Key | Button |
|
38
|
+
| :---: | :----: |
|
39
|
+
| `W` | ↑ |
|
40
|
+
| `A` | ← |
|
41
|
+
| `S` | ↓ |
|
42
|
+
| `D` | → |
|
43
|
+
| `J` | A |
|
44
|
+
| `K` | B |
|
45
|
+
| `U` | Select |
|
46
|
+
| `I` | Start |
|
30
47
|
|
31
48
|
## Contributing
|
32
49
|
|
data/exe/rubyboy
CHANGED
@@ -2,14 +2,30 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
require 'rubyboy'
|
5
|
-
require '
|
5
|
+
require 'optparse'
|
6
6
|
|
7
|
-
|
7
|
+
begin
|
8
|
+
OptionParser.new do |opts|
|
9
|
+
opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} ROM_PATH"
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
opts.on('-h', '--help', 'Displays this help') do
|
12
|
+
puts opts
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
end.parse!
|
16
|
+
rescue OptionParser::ParseError => e
|
17
|
+
puts e.message
|
18
|
+
puts "Usage: #{File.basename($PROGRAM_NAME)} ROM_PATH"
|
19
|
+
exit 1
|
20
|
+
end
|
21
|
+
|
22
|
+
rom_path = ARGV.shift || 'lib/roms/tobu.gb'
|
23
|
+
|
24
|
+
puts "Ruby: #{RUBY_VERSION}"
|
25
|
+
if defined?(RubyVM::YJIT)
|
26
|
+
puts "YJIT: #{RubyVM::YJIT.enabled?}"
|
13
27
|
else
|
14
|
-
|
28
|
+
puts 'YJIT is not available in this environment.'
|
15
29
|
end
|
30
|
+
|
31
|
+
Rubyboy::Emulator.new(rom_path).start
|
data/exe/rubyboy-bench
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'rubyboy'
|
5
|
+
require 'bench'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
begin
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: #{File.basename($PROGRAM_NAME)} [options]"
|
12
|
+
|
13
|
+
opts.on('--count COUNT', Integer, 'Number of counts for the benchmark') do |count|
|
14
|
+
options[:count] = count
|
15
|
+
end
|
16
|
+
|
17
|
+
opts.on('--frames FRAMES', Integer, 'Number of frames to process') do |frames|
|
18
|
+
options[:frames] = frames
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on('--rom-path ROM_PATH', String, 'Path to the ROM file') do |rom_path|
|
22
|
+
options[:rom_path] = rom_path
|
23
|
+
end
|
24
|
+
|
25
|
+
opts.on('-h', '--help', 'Displays this help') do
|
26
|
+
puts opts
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
end.parse!
|
30
|
+
rescue OptionParser::ParseError => e
|
31
|
+
puts e.message
|
32
|
+
puts "Usage: #{File.basename($PROGRAM_NAME)} --count NUM --frames NUM --rom-path FILEPATH"
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
|
36
|
+
puts "Ruby: #{RUBY_VERSION}"
|
37
|
+
if defined?(RubyVM::YJIT)
|
38
|
+
puts "YJIT: #{RubyVM::YJIT.enabled?}"
|
39
|
+
else
|
40
|
+
puts 'YJIT is not available in this environment.'
|
41
|
+
end
|
42
|
+
|
43
|
+
Rubyboy::Bench.new.bench(**options)
|
data/lib/bench.rb
CHANGED
@@ -7,20 +7,19 @@ module Rubyboy
|
|
7
7
|
class Bench
|
8
8
|
def stackprof
|
9
9
|
StackProf.run(mode: :cpu, out: 'stackprof-cpu-myapp.dump', raw: true) do
|
10
|
-
Rubyboy::
|
10
|
+
Rubyboy::Emulator.new('lib/roms/tobu.gb').bench
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def bench
|
15
|
-
bench_cnt = 3
|
14
|
+
def bench(count: 3, frames: 1500, rom_path: 'lib/roms/tobu.gb')
|
16
15
|
time_sum = 0
|
17
|
-
|
18
|
-
time = Rubyboy::
|
16
|
+
count.times do |i|
|
17
|
+
time = Rubyboy::Emulator.new(rom_path).bench(frames)
|
19
18
|
time_sum += time
|
20
|
-
puts "#{i + 1}: #{time} sec"
|
19
|
+
puts "#{i + 1}: #{time / 1_000_000_000.0} sec"
|
21
20
|
end
|
22
21
|
|
23
|
-
puts "FPS: #{
|
22
|
+
puts "FPS: #{frames * count * 1_000_000_000.0 / time_sum}"
|
24
23
|
end
|
25
24
|
end
|
26
25
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubyboy
|
4
|
+
class Emulator
|
5
|
+
CPU_CLOCK_HZ = 4_194_304
|
6
|
+
CYCLE_NANOSEC = 1_000_000_000 / CPU_CLOCK_HZ
|
7
|
+
|
8
|
+
def initialize(rom_path)
|
9
|
+
rom_data = File.open(rom_path, 'r') { _1.read.bytes }
|
10
|
+
rom = Rom.new(rom_data)
|
11
|
+
ram = Ram.new
|
12
|
+
mbc = Cartridge::Factory.create(rom, ram)
|
13
|
+
interrupt = Interrupt.new
|
14
|
+
@ppu = Ppu.new(interrupt)
|
15
|
+
@timer = Timer.new(interrupt)
|
16
|
+
@joypad = Joypad.new(interrupt)
|
17
|
+
@apu = Apu.new
|
18
|
+
@bus = Bus.new(@ppu, rom, ram, mbc, @timer, interrupt, @joypad, @apu)
|
19
|
+
@cpu = Cpu.new(@bus, interrupt)
|
20
|
+
@lcd = Lcd.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def start
|
24
|
+
SDL.InitSubSystem(SDL::INIT_KEYBOARD)
|
25
|
+
|
26
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
27
|
+
elapsed_machine_time = 0
|
28
|
+
catch(:exit_loop) do
|
29
|
+
loop do
|
30
|
+
elapsed_real_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - start_time
|
31
|
+
while elapsed_real_time > elapsed_machine_time
|
32
|
+
cycles = @cpu.exec
|
33
|
+
@timer.step(cycles)
|
34
|
+
@apu.step(cycles)
|
35
|
+
if @ppu.step(cycles)
|
36
|
+
@lcd.draw(@ppu.buffer)
|
37
|
+
key_input_check
|
38
|
+
throw :exit_loop if @lcd.window_should_close?
|
39
|
+
end
|
40
|
+
|
41
|
+
elapsed_machine_time += cycles * CYCLE_NANOSEC
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@lcd.close_window
|
46
|
+
rescue StandardError => e
|
47
|
+
puts e.to_s[0, 100]
|
48
|
+
raise e
|
49
|
+
end
|
50
|
+
|
51
|
+
def bench(frames)
|
52
|
+
@lcd.close_window
|
53
|
+
frame_count = 0
|
54
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
55
|
+
while frame_count < frames
|
56
|
+
cycles = @cpu.exec
|
57
|
+
@timer.step(cycles)
|
58
|
+
if @ppu.step(cycles)
|
59
|
+
key_input_check
|
60
|
+
frame_count += 1
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - start_time
|
65
|
+
end
|
66
|
+
|
67
|
+
private
|
68
|
+
|
69
|
+
def key_input_check
|
70
|
+
SDL.PumpEvents
|
71
|
+
keyboard = SDL.GetKeyboardState(nil)
|
72
|
+
keyboard_state = keyboard.read_array_of_uint8(229)
|
73
|
+
|
74
|
+
direction = (keyboard_state[SDL::SDL_SCANCODE_D]) | (keyboard_state[SDL::SDL_SCANCODE_A] << 1) | (keyboard_state[SDL::SDL_SCANCODE_W] << 2) | (keyboard_state[SDL::SDL_SCANCODE_S] << 3)
|
75
|
+
action = (keyboard_state[SDL::SDL_SCANCODE_K]) | (keyboard_state[SDL::SDL_SCANCODE_J] << 1) | (keyboard_state[SDL::SDL_SCANCODE_U] << 2) | (keyboard_state[SDL::SDL_SCANCODE_I] << 3)
|
76
|
+
@joypad.direction_button(15 - direction)
|
77
|
+
@joypad.action_button(15 - action)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
data/lib/rubyboy/version.rb
CHANGED
data/lib/rubyboy.rb
CHANGED
@@ -4,6 +4,7 @@ require 'rubyboy/sdl'
|
|
4
4
|
require_relative 'rubyboy/apu'
|
5
5
|
require_relative 'rubyboy/bus'
|
6
6
|
require_relative 'rubyboy/cpu'
|
7
|
+
require_relative 'rubyboy/emulator'
|
7
8
|
require_relative 'rubyboy/ppu'
|
8
9
|
require_relative 'rubyboy/rom'
|
9
10
|
require_relative 'rubyboy/ram'
|
@@ -12,86 +13,3 @@ require_relative 'rubyboy/lcd'
|
|
12
13
|
require_relative 'rubyboy/joypad'
|
13
14
|
require_relative 'rubyboy/interrupt'
|
14
15
|
require_relative 'rubyboy/cartridge/factory'
|
15
|
-
|
16
|
-
module Rubyboy
|
17
|
-
class Console
|
18
|
-
CPU_CLOCK_HZ = 4_194_304
|
19
|
-
CYCLE_NANOSEC = 1_000_000_000 / CPU_CLOCK_HZ
|
20
|
-
|
21
|
-
def initialize(rom_path)
|
22
|
-
rom_data = File.open(rom_path, 'r') { _1.read.bytes }
|
23
|
-
rom = Rom.new(rom_data)
|
24
|
-
ram = Ram.new
|
25
|
-
mbc = Cartridge::Factory.create(rom, ram)
|
26
|
-
interrupt = Interrupt.new
|
27
|
-
@ppu = Ppu.new(interrupt)
|
28
|
-
@timer = Timer.new(interrupt)
|
29
|
-
@joypad = Joypad.new(interrupt)
|
30
|
-
@apu = Apu.new
|
31
|
-
@bus = Bus.new(@ppu, rom, ram, mbc, @timer, interrupt, @joypad, @apu)
|
32
|
-
@cpu = Cpu.new(@bus, interrupt)
|
33
|
-
@lcd = Lcd.new
|
34
|
-
|
35
|
-
RubyVM::YJIT.enable
|
36
|
-
|
37
|
-
puts "Ruby: #{RUBY_VERSION}"
|
38
|
-
puts "YJIT: #{RubyVM::YJIT.enabled?}"
|
39
|
-
end
|
40
|
-
|
41
|
-
def start
|
42
|
-
SDL.InitSubSystem(SDL::INIT_KEYBOARD)
|
43
|
-
|
44
|
-
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
|
45
|
-
elapsed_machine_time = 0
|
46
|
-
catch(:exit_loop) do
|
47
|
-
loop do
|
48
|
-
elapsed_real_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - start_time
|
49
|
-
while elapsed_real_time > elapsed_machine_time
|
50
|
-
cycles = @cpu.exec
|
51
|
-
@timer.step(cycles)
|
52
|
-
@apu.step(cycles)
|
53
|
-
if @ppu.step(cycles)
|
54
|
-
@lcd.draw(@ppu.buffer)
|
55
|
-
key_input_check
|
56
|
-
throw :exit_loop if @lcd.window_should_close?
|
57
|
-
end
|
58
|
-
|
59
|
-
elapsed_machine_time += cycles * CYCLE_NANOSEC
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
@lcd.close_window
|
64
|
-
rescue StandardError => e
|
65
|
-
puts e.to_s[0, 100]
|
66
|
-
raise e
|
67
|
-
end
|
68
|
-
|
69
|
-
def bench
|
70
|
-
cnt = 0
|
71
|
-
start_time = Time.now
|
72
|
-
while cnt < 1500
|
73
|
-
cycles = @cpu.exec
|
74
|
-
@timer.step(cycles)
|
75
|
-
if @ppu.step(cycles)
|
76
|
-
key_input_check
|
77
|
-
cnt += 1
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
Time.now - start_time
|
82
|
-
end
|
83
|
-
|
84
|
-
private
|
85
|
-
|
86
|
-
def key_input_check
|
87
|
-
SDL.PumpEvents
|
88
|
-
keyboard = SDL.GetKeyboardState(nil)
|
89
|
-
keyboard_state = keyboard.read_array_of_uint8(229)
|
90
|
-
|
91
|
-
direction = (keyboard_state[SDL::SDL_SCANCODE_D]) | (keyboard_state[SDL::SDL_SCANCODE_A] << 1) | (keyboard_state[SDL::SDL_SCANCODE_W] << 2) | (keyboard_state[SDL::SDL_SCANCODE_S] << 3)
|
92
|
-
action = (keyboard_state[SDL::SDL_SCANCODE_K]) | (keyboard_state[SDL::SDL_SCANCODE_J] << 1) | (keyboard_state[SDL::SDL_SCANCODE_U] << 2) | (keyboard_state[SDL::SDL_SCANCODE_I] << 3)
|
93
|
-
@joypad.direction_button(15 - direction)
|
94
|
-
@joypad.action_button(15 - action)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
Binary file
|
Binary file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubyboy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- sacckey
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-05-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ffi
|
@@ -34,6 +34,7 @@ description:
|
|
34
34
|
email:
|
35
35
|
executables:
|
36
36
|
- rubyboy
|
37
|
+
- rubyboy-bench
|
37
38
|
extensions: []
|
38
39
|
extra_rdoc_files: []
|
39
40
|
files:
|
@@ -45,6 +46,7 @@ files:
|
|
45
46
|
- README.md
|
46
47
|
- Rakefile
|
47
48
|
- exe/rubyboy
|
49
|
+
- exe/rubyboy-bench
|
48
50
|
- lib/bench.rb
|
49
51
|
- lib/opcodes.json
|
50
52
|
- lib/roms/bgbtest.gb
|
@@ -78,6 +80,7 @@ files:
|
|
78
80
|
- lib/rubyboy/cartridge/mbc1.rb
|
79
81
|
- lib/rubyboy/cartridge/nombc.rb
|
80
82
|
- lib/rubyboy/cpu.rb
|
83
|
+
- lib/rubyboy/emulator.rb
|
81
84
|
- lib/rubyboy/interrupt.rb
|
82
85
|
- lib/rubyboy/joypad.rb
|
83
86
|
- lib/rubyboy/lcd.rb
|
@@ -93,6 +96,8 @@ files:
|
|
93
96
|
- lib/rubyboy/version.rb
|
94
97
|
- resource/logo/logo.png
|
95
98
|
- resource/logo/logo.svg
|
99
|
+
- resource/screenshots/pokemon.png
|
100
|
+
- resource/screenshots/puyopuyo.png
|
96
101
|
- sig/rubyboy.rbs
|
97
102
|
homepage: https://github.com/sacckey/rubyboy
|
98
103
|
licenses:
|