Module: RGame::Util::Z
- Defined in:
- lib/rgame/util/z.rb
Overview
The vocabulary of draw order: which band a thing is drawn in, and how the single number the renderer sorts on is built out of one.
RGame::Util::Z::BANDS # => [:world, :hud, :overlay, :debug]
RGame::Util::Z.base(:hud, 3) # the fourth slot handed out in the HUD band
These are values — plain Symbols and Integers with no window, GPU or OS
handle behind them — so they live here rather than in Core, and both sides
of the line may name them. RGame::Engine decides which band a node is
in; RGame::Core::Renderer turns that into a z. Neither could reach the
other's spelling of it, and the vocabulary is exactly what they have to
agree on. Same reasoning as RGame::Util::Controls.
Draw order is tree order, not a number a caller picks
A frame's order is decided in three steps, coarsest first:
- The band. Structural and inherited: everything in
:worldis under everything in:hud, whatever either drew. Bands are 2**40 apart, so no arithmetic below can carry one into the next. - The slot. The scene graph is walked depth-first with siblings in
zorder, and each node takes the next slot in its band as it is reached. Because the walk descends fully before moving on, a node's subtree occupies one contiguous run of slots — which is what makes a subtree atomic, and what stops some of a node's children landing in front of a sibling the node itself is behind. - The offset.
Z_MIN..Z_MAXinside the node's own slot, and the only part az:argument on a drawing call touches. It orders a node's components against each other and its own draw calls against each other, and it cannot reach the next node, let alone the next band.
So a node's z is only ever compared to its siblings'. Its magnitude
means nothing: z: 1 and z: 1_000_000 behave identically if they are
the only two children, and negatives are ordinary.
The arithmetic is exact
Every value here is an integer below 242, and the double the draw
queue sorts on is exact below 253. Two different slots can therefore
never compare equal by rounding — which would show up as two sprites
swapping places between frames, and would be very hard to see as a
precision problem.
Constant Summary collapse
- SLOT =
How much room a node has for ordering its own drawing. Nothing in this repo uses more than three offsets; a thousand is room to stop thinking about it.
1024- HALF =
SLOT / 2
- Z_MIN =
What a
z:argument on a drawing call may be. Signed, because a node drawing something behind its sprite is as ordinary as drawing something in front of it, and the sprite's own default is 0. -HALF
- Z_MAX =
HALF - 1
- STRIDE =
The gap between bands: 2**30 slots each, which no frame will approach.
1 << 40
- SLOTS_PER_BAND =
STRIDE / SLOT
- BANDS =
In order, back to front.
:worldis what a node with no band ancestor is in, so a game that never mentions a band is entirely in it.:hudis one player's own screen space (RGame::Engine::PlayerLayer),:overlayis screen space across the whole window — a cutscene, a results panel — and:debugis the development overlay, over everything by construction. %i[world hud overlay debug].freeze
- DEFAULT =
:world- INDICES =
BANDS.each_with_index.to_h.freeze
Class Method Summary collapse
-
.band!(band) ⇒ Object
Raises unless
bandnames one. - .band?(band) ⇒ Boolean
-
.base(band, slot_index) ⇒ Object
The same thing by name, for a caller that has one rather than an index.
-
.index(band) ⇒ Object
Which band this is, 0-based and back to front.
-
.offset(z) ⇒ Object
Checks a
z:argument and returns it. -
.slot_base(band_index, slot_index) ⇒ Object
The z base of the
slot_index-th slot in the band with that index.
Class Method Details
.band!(band) ⇒ Object
Raises unless band names one. Called where a band is set rather than
where it is used, so a typo surfaces at assignment with the list in the
message instead of as a KeyError from inside a draw.
81 82 83 84 85 |
# File 'lib/rgame/util/z.rb', line 81 def self.band!(band) return band if band?(band) raise ArgumentError, "unknown z band #{band.inspect}; expected one of #{BANDS.inspect}" end |
.band?(band) ⇒ Boolean
76 |
# File 'lib/rgame/util/z.rb', line 76 def self.band?(band) = INDICES.key?(band) |
.base(band, slot_index) ⇒ Object
The same thing by name, for a caller that has one rather than an index.
112 |
# File 'lib/rgame/util/z.rb', line 112 def self.base(band, slot_index) = slot_base(index(band), slot_index) |
.index(band) ⇒ Object
Which band this is, 0-based and back to front. This is what a renderer's slot counters are keyed on, so it is also where a bad band is caught.
89 90 91 |
# File 'lib/rgame/util/z.rb', line 89 def self.index(band) INDICES[band] || band!(band) end |
.offset(z) ⇒ Object
Checks a z: argument and returns it. Drawing methods run this instead
of trusting the caller, because the whole guarantee — that a node cannot
draw outside its band — rests on the offset staying inside one slot.
A stale global z (the bands used to be Integers a caller passed) lands
here and says so rather than sorting somewhere surprising.
hot-path
120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/rgame/util/z.rb', line 120 def self.offset(z) raise TypeError, "no implicit conversion of #{z.class} into Float" unless z.is_a?(Numeric) unless z.between?(Z_MIN, Z_MAX) raise ArgumentError, "z: #{z} is outside a node's slot (#{Z_MIN}..#{Z_MAX}); a `z:` orders one " \ 'node\'s own drawing, and the band comes from the tree' end z end |
.slot_base(band_index, slot_index) ⇒ Object
The z base of the slot_index-th slot in the band with that index.
Offsets are measured from the middle of the slot, so Z_MIN..Z_MAX is
symmetric — a node can draw behind its own sprite as easily as in front.
Takes the band's index rather than its name because a renderer already has the index in hand (it counts slots per index), and looking the same Symbol up twice per node per frame is work with nothing to show for it. hot-path
101 102 103 104 105 106 107 108 109 |
# File 'lib/rgame/util/z.rb', line 101 def self.slot_base(band_index, slot_index) if slot_index >= SLOTS_PER_BAND raise RangeError, "z band #{BANDS[band_index].inspect} is full at " \ "#{SLOTS_PER_BAND} slots per frame" end (band_index * STRIDE) + (slot_index * SLOT) + HALF end |