Class: RGame::Engine::DebugOverlay
- Inherits:
-
Object
- Object
- RGame::Engine::DebugOverlay
- Defined in:
- lib/rgame/engine/debug_overlay.rb
Overview
A hard-wired development overlay, toggled by F1 (see RGame::Game), reporting runtime health in the bottom-right corner: frame rate (FPS), the process' cumulative allocated-object count (OBJ), and the objects allocated since the last frame (Δ/f).
Δ/f is the one to watch, and it is a standing guard rather than a diagnostic for one past bug. A clean per-frame path holds it near zero; a steady nonzero number is a garbage collection being scheduled. The cost is invisible by every other measure — nothing looks wrong, nothing is slower, until a pause lands mid-frame — so without a number on screen it is not noticed at all.
Every layer can break it and each has its own way of doing so. Core can
allocate in a binding, the engine layer in a component's draw, and game
code in a scene that builds a string or an array per frame. RuboCop's
Game/NoInterpolationInHotPath and Game/NoNeedlessAllocation catch the
shapes they can see in this repo; they cannot see a game built on top,
and they cannot see an allocation that happens inside a method they think
is cheap. This can.
Pure: it reads only GC.stat and draws against the renderer interface, so it stays headless-testable. The crux is that it must not allocate while drawing — the values change every frame, so the usual "cache the string, rebuild on change" trick would allocate a String per frame, and the meter would be measuring itself. Instead numbers are drawn digit-by-digit from a fixed set of pre-built single-character strings, which the font caches per glyph.
Constant Summary collapse
- DIGITS =
%w[0 1 2 3 4 5 6 7 8 9].freeze
- FPS_LABEL =
'FPS'- OBJ_LABEL =
'OBJ'- DELTA_LABEL =
'Δ/f'- COLOR =
frozen so the renderer caches the resolved colour
[80, 255, 120].freeze
- PAD =
margin from the screen edge
8- GAP =
space between a label and its number
8
Instance Method Summary collapse
-
#draw(renderer, view, fps) ⇒ Object
Laid out against the view it is drawn into rather than against the window, so it stays in the corner of whatever region it is given.
-
#initialize ⇒ DebugOverlay
constructor
A new instance of DebugOverlay.
- #toggle ⇒ Object
- #visible? ⇒ Boolean
Constructor Details
#initialize ⇒ DebugOverlay
Returns a new instance of DebugOverlay.
42 43 44 45 46 47 |
# File 'lib/rgame/engine/debug_overlay.rb', line 42 def initialize @visible = false @prev_allocated = 0 @digit_widths = Array.new(10) # measured once, then reused @label_widths = {} # measured once per label, then reused end |
Instance Method Details
#draw(renderer, view, fps) ⇒ Object
Laid out against the view it is drawn into rather than against the
window, so it stays in the corner of whatever region it is given. It
takes fps rather than reading it, for the same reason nothing here
reads a clock: the number is measured by the shell that owns the loop
and handed down. See CLAUDE.md, "draw renders state".
Its own :debug band, which is the last one, so this lands over every
other thing in the frame however the scene is arranged. Not a node, so
it opens its own layer rather than being given one by the traversal.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/rgame/engine/debug_overlay.rb', line 65 def draw(renderer, view, fps) return unless @visible allocated = GC.stat(:total_allocated_objects) delta = allocated - @prev_allocated @prev_allocated = allocated line_h = renderer.text_height # The view's size, not its position: inside a region that has been # translated to its corner, adding `view.x` would offset a second time. right_x = view.width - PAD top_y = view.height - PAD - (line_h * 3) renderer.layered(:debug) do draw_line(renderer, FPS_LABEL, fps, right_x, top_y) draw_line(renderer, OBJ_LABEL, allocated, right_x, top_y + line_h) draw_line(renderer, DELTA_LABEL, delta, right_x, top_y + (line_h * 2)) end end |
#toggle ⇒ Object
51 52 53 54 55 |
# File 'lib/rgame/engine/debug_overlay.rb', line 51 def toggle @visible = !@visible # Baseline the counter on reveal so the first Δ/f isn't the whole run's history. @prev_allocated = GC.stat(:total_allocated_objects) if @visible end |
#visible? ⇒ Boolean
49 |
# File 'lib/rgame/engine/debug_overlay.rb', line 49 def visible? = @visible |