rubyboy 1.4.2 → 1.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8f272f80eda6bc8a180619d01183ddeecc4b8f288b1e182983bee8b4ddaec51c
4
- data.tar.gz: c05b9f8e41193fb6c21c928e6bd4b2c4dc1de8d2f5264203cbe9d36be1fc9ede
3
+ metadata.gz: 22c8e1f0094ec086f0e7130e6c79623d0e9385c718f0724ca2773c101ff3b976
4
+ data.tar.gz: 49adde27b593ef7bf8bf3f874160922ed7994388960db71f5757b2cbf033735f
5
5
  SHA512:
6
- metadata.gz: 5d33f763fc53d97c0cbc572ceb85f8f5eae609f1dde6fdade9da8a736461cb7e43ffa7131506a748cee86b046e6e2adc930a8d3877a1f15693d21c8f4b8b2e58
7
- data.tar.gz: 55e3f162ba70d59c89ad1a2399c20011dc51af7fb22afeb121317ef94bee0c745492fa10c073dc545bb85d1b443af2c52295a93711ffc73124890f1b9cf69e3e
6
+ metadata.gz: 48abbc79e8dde1e691f3f76a2736ad909b29af549c3c3b6a99eedd16a5b832f8b84bfb810ed4115483e116f5acbd50e1b07ec333e61778dc82433293f36a8401
7
+ data.tar.gz: dcf874037cd1d133687e3a99c5aeb36d97cd8a8f356514bb97b40eec57184b3bed7bd003538925fce18875d13d8bd37e1f50ffdd646568b5fbc351925f710a5a
data/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
- ## [Unreleased]
1
+ ## [1.5.1] - 2025-02-16
2
+
3
+ - Optimize Cartridge::Mbc1 and Bus
4
+
5
+ ## [1.5.0] - 2025-02-09
6
+
7
+ - Add EmulatorHeadless and use it for bench
8
+ - Bump ruby.wasm version to 2.7.1
2
9
 
3
10
  ## [1.4.2] - 2025-01-11
4
11
 
data/docs/worker.js CHANGED
@@ -1,4 +1,4 @@
1
- import { DefaultRubyVM } from 'https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.0/dist/browser/+esm';
1
+ import { DefaultRubyVM } from 'https://cdn.jsdelivr.net/npm/@ruby/wasm-wasi@2.7.1/dist/browser/+esm';
2
2
  import { File } from 'https://cdn.jsdelivr.net/npm/@bjorn3/browser_wasi_shim@0.3.0/+esm';
3
3
 
4
4
  const DIRECTION_KEY_MASKS = {
data/exe/rubyboy-bench CHANGED
@@ -40,4 +40,4 @@ else
40
40
  puts 'YJIT is not available in this environment.'
41
41
  end
42
42
 
43
- Rubyboy::Bench.new.bench(**options)
43
+ Rubyboy::Bench.new.run(**options)
data/exe/rubyboy-wasm CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  case ARGV[0]
5
5
  when 'build'
6
- command = %w[build --ruby-version 3.3 -o ./docs/ruby-js.wasm]
6
+ command = %w[build --ruby-version 3.4 -o ./docs/ruby-js.wasm]
7
7
  when 'pack'
8
8
  command = %w[pack ./docs/ruby-js.wasm --dir ./lib::/lib -o ./docs/rubyboy.wasm]
9
9
  else
data/lib/bench.rb CHANGED
@@ -1,22 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'stackprof'
4
- require_relative 'rubyboy'
3
+ require_relative 'rubyboy/emulator_headless'
5
4
 
6
5
  module Rubyboy
7
6
  class Bench
8
- def stackprof
9
- StackProf.run(mode: :cpu, out: 'stackprof-cpu-myapp.dump', raw: true) do
10
- Rubyboy::Emulator.new('lib/roms/tobu.gb').bench
11
- end
12
- end
13
-
14
- def bench(count: 3, frames: 1500, rom_path: 'lib/roms/tobu.gb')
7
+ def run(count: 3, frames: 1500, rom_path: 'lib/roms/tobu.gb')
15
8
  time_sum = 0
9
+
16
10
  count.times do |i|
17
- time = Rubyboy::Emulator.new(rom_path).bench(frames)
18
- time_sum += time
11
+ emulator = Rubyboy::EmulatorHeadless.new(rom_path)
12
+ frame_count = 0
13
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond)
14
+ while frame_count < frames
15
+ emulator.step
16
+ frame_count += 1
17
+ end
18
+ time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :nanosecond) - start_time
19
19
  puts "#{i + 1}: #{time / 1_000_000_000.0} sec"
20
+
21
+ time_sum += time
20
22
  end
21
23
 
22
24
  puts "FPS: #{frames * count * 1_000_000_000.0 / time_sum}"
data/lib/rubyboy/bus.rb CHANGED
@@ -11,74 +11,93 @@ module Rubyboy
11
11
  @apu = apu
12
12
  @interrupt = interrupt
13
13
  @timer = timer
14
+ end
15
+
16
+ def read_byte(addr)
17
+ case addr >> 12
18
+ when 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb
19
+ return @mbc.read_byte(addr)
20
+ when 0x8, 0x9
21
+ return @ppu.read_byte(addr)
22
+ when 0xc
23
+ return @ram.wram1[addr - 0xc000]
24
+ when 0xd
25
+ return @ram.wram2[addr - 0xd000]
26
+ when 0xf
27
+ case addr >> 8
28
+ when 0xfe
29
+ return @ppu.read_byte(addr) if addr <= 0xfe9f
30
+ when 0xff
31
+ last_byte = addr & 0xFF
14
32
 
15
- @read_methods = Array.new(0x10000)
16
- @write_methods = Array.new(0x10000)
33
+ case last_byte
34
+ when 0x00
35
+ return @joypad.read_byte(addr)
36
+ when 0x04, 0x05, 0x06, 0x07
37
+ return @timer.read_byte(addr)
38
+ when 0x0f
39
+ return @interrupt.read_byte(addr)
40
+ when 0x46
41
+ return @ppu.read_byte(addr)
42
+ when 0xff
43
+ return @interrupt.read_byte(addr)
44
+ end
17
45
 
18
- set_methods
19
- end
46
+ return @apu.read_byte(addr) if last_byte <= 0x26 && last_byte >= 0x10
20
47
 
21
- def set_methods
22
- 0x10000.times do |addr|
23
- case addr
24
- when 0x0000..0x7fff
25
- @read_methods[addr] = -> { @mbc.read_byte(addr) }
26
- @write_methods[addr] = ->(value) { @mbc.write_byte(addr, value) }
27
- when 0x8000..0x9fff
28
- @read_methods[addr] = -> { @ppu.read_byte(addr) }
29
- @write_methods[addr] = ->(value) { @ppu.write_byte(addr, value) }
30
- when 0xa000..0xbfff
31
- @read_methods[addr] = -> { @mbc.read_byte(addr) }
32
- @write_methods[addr] = ->(value) { @mbc.write_byte(addr, value) }
33
- when 0xc000..0xcfff
34
- @read_methods[addr] = -> { @ram.wram1[addr - 0xc000] }
35
- @write_methods[addr] = ->(value) { @ram.wram1[addr - 0xc000] = value }
36
- when 0xd000..0xdfff
37
- @read_methods[addr] = -> { @ram.wram2[addr - 0xd000] }
38
- @write_methods[addr] = ->(value) { @ram.wram2[addr - 0xd000] = value }
39
- when 0xfe00..0xfe9f
40
- @read_methods[addr] = -> { @ppu.read_byte(addr) }
41
- @write_methods[addr] = ->(value) { @ppu.write_byte(addr, value) }
42
- when 0xff00
43
- @read_methods[addr] = -> { @joypad.read_byte(addr) }
44
- @write_methods[addr] = ->(value) { @joypad.write_byte(addr, value) }
45
- when 0xff04..0xff07
46
- @read_methods[addr] = -> { @timer.read_byte(addr) }
47
- @write_methods[addr] = ->(value) { @timer.write_byte(addr, value) }
48
- when 0xff0f
49
- @read_methods[addr] = -> { @interrupt.read_byte(addr) }
50
- @write_methods[addr] = ->(value) { @interrupt.write_byte(addr, value) }
51
- when 0xff10..0xff26
52
- @read_methods[addr] = -> { @apu.read_byte(addr) }
53
- @write_methods[addr] = ->(value) { @apu.write_byte(addr, value) }
54
- when 0xff30..0xff3f
55
- @read_methods[addr] = -> { @apu.read_byte(addr) }
56
- @write_methods[addr] = ->(value) { @apu.write_byte(addr, value) }
57
- when 0xff46
58
- @read_methods[addr] = -> { @ppu.read_byte(addr) }
59
- @write_methods[addr] = ->(value) { 0xa0.times { |i| write_byte(0xfe00 + i, read_byte((value << 8) + i)) } }
60
- when 0xff40..0xff4b
61
- @read_methods[addr] = -> { @ppu.read_byte(addr) }
62
- @write_methods[addr] = ->(value) { @ppu.write_byte(addr, value) }
63
- when 0xff80..0xfffe
64
- @read_methods[addr] = -> { @ram.hram[addr - 0xff80] }
65
- @write_methods[addr] = ->(value) { @ram.hram[addr - 0xff80] = value }
66
- when 0xffff
67
- @read_methods[addr] = -> { @interrupt.read_byte(addr) }
68
- @write_methods[addr] = ->(value) { @interrupt.write_byte(addr, value) }
69
- else
70
- @read_methods[addr] = -> { 0xff }
71
- @write_methods[addr] = ->(_value) {}
48
+ return @apu.read_byte(addr) if last_byte <= 0x3f && last_byte >= 0x30
49
+
50
+ return @ppu.read_byte(addr) if last_byte <= 0x4b && last_byte >= 0x40
51
+
52
+ return @ram.hram[addr - 0xff80] if last_byte <= 0xfe && last_byte >= 0x80
72
53
  end
73
54
  end
74
- end
75
55
 
76
- def read_byte(addr)
77
- @read_methods[addr].call
56
+ 0xff
78
57
  end
79
58
 
80
59
  def write_byte(addr, value)
81
- @write_methods[addr].call(value)
60
+ case addr >> 12
61
+ when 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0xa, 0xb
62
+ return @mbc.write_byte(addr, value)
63
+ when 0x8, 0x9
64
+ return @ppu.write_byte(addr, value)
65
+ when 0xc
66
+ return @ram.wram1[addr - 0xc000] = value
67
+ when 0xd
68
+ return @ram.wram2[addr - 0xd000] = value
69
+ when 0xf
70
+ case addr >> 8
71
+ when 0xfe
72
+ return @ppu.write_byte(addr, value) if addr <= 0xfe9f
73
+ when 0xff
74
+ last_byte = addr & 0xFF
75
+
76
+ case last_byte
77
+ when 0x00
78
+ return @joypad.write_byte(addr, value)
79
+ when 0x04, 0x05, 0x06, 0x07
80
+ return @timer.write_byte(addr, value)
81
+ when 0x0f
82
+ return @interrupt.write_byte(addr, value)
83
+ when 0x46
84
+ 0xa0.times { |i| write_byte(0xfe00 + i, read_byte((value << 8) + i)) }
85
+ return
86
+ when 0xff
87
+ return @interrupt.write_byte(addr, value)
88
+ end
89
+
90
+ return @apu.write_byte(addr, value) if last_byte <= 0x26 && last_byte >= 0x10
91
+
92
+ return @apu.write_byte(addr, value) if last_byte <= 0x3f && last_byte >= 0x30
93
+
94
+ return @ppu.write_byte(addr, value) if last_byte <= 0x4b && last_byte >= 0x40
95
+
96
+ return @ram.hram[addr - 0xff80] = value if last_byte <= 0xfe && last_byte >= 0x80
97
+ end
98
+ end
99
+
100
+ nil
82
101
  end
83
102
 
84
103
  def read_word(addr)
@@ -10,65 +10,47 @@ module Rubyboy
10
10
  @ram_bank = 0
11
11
  @ram_enable = false
12
12
  @ram_banking_mode = false
13
-
14
- @read_methods = Array.new(0xc000)
15
- @write_methods = Array.new(0xc000)
16
-
17
- set_methods
18
13
  end
19
14
 
20
- def set_methods
21
- 0xc000.times do |addr|
22
- @read_methods[addr] =
23
- case addr
24
- when 0x0000..0x3fff then -> { @rom.data[addr] }
25
- when 0x4000..0x7fff then -> { @rom.data[addr + (@rom_bank - 1) * 0x4000] }
26
- when 0xa000..0xbfff
27
- lambda do
28
- if @ram_enable
29
- if @ram_banking_mode
30
- @ram.eram[addr - 0xa000 + @ram_bank * 0x800]
31
- else
32
- @ram.eram[addr - 0xa000]
33
- end
34
- else
35
- 0xff
36
- end
37
- end
38
- end
39
- end
40
-
41
- 0xc000.times do |addr|
42
- @write_methods[addr] =
43
- case addr
44
- when 0x0000..0x1fff then ->(value) { @ram_enable = value & 0x0f == 0x0a }
45
- when 0x2000..0x3fff
46
- lambda do |value|
47
- @rom_bank = value & 0x1f
48
- @rom_bank = 1 if @rom_bank == 0
49
- end
50
- when 0x4000..0x5fff then ->(value) { @ram_bank = value & 0x03 }
51
- when 0x6000..0x7fff then ->(value) { @ram_banking_mode = value & 0x01 == 0x01 }
52
- when 0xa000..0xbfff
53
- lambda do |value|
54
- if @ram_enable
55
- if @ram_banking_mode
56
- @ram.eram[addr - 0xa000 + @ram_bank * 0x800] = value
57
- else
58
- @ram.eram[addr - 0xa000] = value
59
- end
60
- end
61
- end
15
+ def read_byte(addr)
16
+ case (addr >> 12)
17
+ when 0x0, 0x1, 0x2, 0x3
18
+ @rom.data[addr]
19
+ when 0x4, 0x5, 0x6, 0x7
20
+ @rom.data[addr + ((@rom_bank - 1) << 14)]
21
+ when 0xa, 0xb
22
+ if @ram_enable
23
+ if @ram_banking_mode
24
+ @ram.eram[addr - 0xa000 + (@ram_bank << 11)]
25
+ else
26
+ @ram.eram[addr - 0xa000]
62
27
  end
28
+ else
29
+ 0xff
30
+ end
63
31
  end
64
32
  end
65
33
 
66
- def read_byte(addr)
67
- @read_methods[addr].call
68
- end
69
-
70
34
  def write_byte(addr, value)
71
- @write_methods[addr].call(value)
35
+ case addr >> 12
36
+ when 0x0, 0x1
37
+ @ram_enable = value & 0x0f == 0x0a
38
+ when 0x2, 0x3
39
+ @rom_bank = value & 0x1f
40
+ @rom_bank = 1 if @rom_bank == 0
41
+ when 0x4, 0x5
42
+ @ram_bank = value & 0x03
43
+ when 0x6, 0x7
44
+ @ram_banking_mode = value & 0x01 == 0x01
45
+ when 0xa, 0xb
46
+ if @ram_enable
47
+ if @ram_banking_mode
48
+ @ram.eram[addr - 0xa000 + (@ram_bank << 11)] = value
49
+ else
50
+ @ram.eram[addr - 0xa000] = value
51
+ end
52
+ end
53
+ end
72
54
  end
73
55
  end
74
56
  end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'apu'
4
+ require_relative 'bus'
5
+ require_relative 'cpu'
6
+ require_relative 'ppu'
7
+ require_relative 'rom'
8
+ require_relative 'ram'
9
+ require_relative 'timer'
10
+ require_relative 'joypad'
11
+ require_relative 'interrupt'
12
+ require_relative 'cartridge/factory'
13
+
14
+ module Rubyboy
15
+ class EmulatorHeadless
16
+ def initialize(rom_path)
17
+ rom_data = File.open(rom_path, 'r') { _1.read.bytes }
18
+ rom = Rom.new(rom_data)
19
+ ram = Ram.new
20
+ mbc = Cartridge::Factory.create(rom, ram)
21
+ interrupt = Interrupt.new
22
+ @ppu = Ppu.new(interrupt)
23
+ @timer = Timer.new(interrupt)
24
+ joypad = Joypad.new(interrupt)
25
+ @apu = Apu.new
26
+ bus = Bus.new(@ppu, rom, ram, mbc, @timer, interrupt, joypad, @apu)
27
+ @cpu = Cpu.new(bus, interrupt)
28
+ end
29
+
30
+ def step
31
+ loop do
32
+ cycles = @cpu.exec
33
+ @timer.step(cycles)
34
+ @apu.step(cycles)
35
+ break if @ppu.step(cycles)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubyboy
4
- VERSION = '1.4.2'
4
+ VERSION = '1.5.1'
5
5
  end
data/lib/stackprof.rb ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'stackprof'
4
+ require_relative 'rubyboy/emulator'
5
+
6
+ module Rubyboy
7
+ class Stackprof
8
+ def run
9
+ StackProf.run(mode: :cpu, out: 'stackprof-cpu-myapp.dump', raw: true) do
10
+ Rubyboy::Emulator.new('lib/roms/tobu.gb').bench
11
+ end
12
+ end
13
+ end
14
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyboy
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - sacckey
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-01-11 00:00:00.000000000 Z
10
+ date: 2025-02-16 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: ffi
@@ -30,8 +29,6 @@ dependencies:
30
29
  - - ">="
31
30
  - !ruby/object:Gem::Version
32
31
  version: 1.16.3
33
- description:
34
- email:
35
32
  executables:
36
33
  - rubyboy
37
34
  - rubyboy-bench
@@ -91,6 +88,7 @@ files:
91
88
  - lib/rubyboy/cartridge/nombc.rb
92
89
  - lib/rubyboy/cpu.rb
93
90
  - lib/rubyboy/emulator.rb
91
+ - lib/rubyboy/emulator_headless.rb
94
92
  - lib/rubyboy/emulator_wasm.rb
95
93
  - lib/rubyboy/interrupt.rb
96
94
  - lib/rubyboy/joypad.rb
@@ -105,6 +103,7 @@ files:
105
103
  - lib/rubyboy/sdl.rb
106
104
  - lib/rubyboy/timer.rb
107
105
  - lib/rubyboy/version.rb
106
+ - lib/stackprof.rb
108
107
  - resource/logo/logo.png
109
108
  - resource/logo/logo.svg
110
109
  - resource/logo/rubyboy.png
@@ -120,7 +119,6 @@ metadata:
120
119
  source_code_uri: https://github.com/sacckey/rubyboy
121
120
  changelog_uri: https://github.com/sacckey/rubyboy/blob/main/CHANGELOG.md
122
121
  rubygems_mfa_required: 'true'
123
- post_install_message:
124
122
  rdoc_options: []
125
123
  require_paths:
126
124
  - lib
@@ -135,8 +133,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
133
  - !ruby/object:Gem::Version
136
134
  version: '0'
137
135
  requirements: []
138
- rubygems_version: 3.5.3
139
- signing_key:
136
+ rubygems_version: 3.6.2
140
137
  specification_version: 4
141
138
  summary: A Game Boy emulator written in Ruby
142
139
  test_files: []