ray 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (167) hide show
  1. data/README.md +9 -6
  2. data/Rakefile +1 -5
  3. data/ext/audio.c +25 -19
  4. data/ext/audio_source.c +67 -39
  5. data/ext/color.c +19 -19
  6. data/ext/drawable.c +190 -31
  7. data/ext/extconf.rb +16 -14
  8. data/ext/gl.c +310 -30
  9. data/ext/gl_buffer.c +223 -2
  10. data/ext/gl_index_buffer.c +11 -0
  11. data/ext/gl_int_array.c +24 -22
  12. data/ext/gl_vertex.c +84 -49
  13. data/ext/image.c +115 -51
  14. data/ext/image_target.c +58 -10
  15. data/ext/input.c +73 -6
  16. data/ext/mo.c +583 -0
  17. data/ext/mo.h +189 -0
  18. data/ext/music.c +9 -8
  19. data/ext/pixel_bus.c +349 -0
  20. data/ext/polygon.c +68 -45
  21. data/ext/ray.c +1 -0
  22. data/ext/ray.h +19 -1
  23. data/ext/rect.c +9 -47
  24. data/ext/say.h +1 -2
  25. data/ext/say_all.h +6 -0
  26. data/ext/say_audio.h +3 -0
  27. data/ext/say_audio_context.c +1 -4
  28. data/ext/say_basic_type.c +24 -0
  29. data/ext/say_basic_type.h +4 -0
  30. data/ext/say_buffer.c +217 -88
  31. data/ext/say_buffer.h +20 -5
  32. data/ext/say_buffer_renderer.c +10 -7
  33. data/ext/say_buffer_renderer.h +1 -1
  34. data/ext/say_buffer_slice.c +70 -76
  35. data/ext/say_context.c +109 -22
  36. data/ext/say_context.h +14 -0
  37. data/ext/say_drawable.c +113 -25
  38. data/ext/say_drawable.h +23 -2
  39. data/ext/say_error.c +7 -2
  40. data/ext/say_font.c +30 -27
  41. data/ext/say_font.h +3 -6
  42. data/ext/say_get_proc.c +35 -0
  43. data/ext/say_image.c +102 -27
  44. data/ext/say_image.h +11 -4
  45. data/ext/say_image_target.c +88 -34
  46. data/ext/say_image_target.h +3 -2
  47. data/ext/say_index_buffer.c +31 -19
  48. data/ext/say_index_buffer.h +4 -2
  49. data/ext/say_index_buffer_slice.c +78 -70
  50. data/ext/say_music.c +4 -2
  51. data/ext/say_osx.h +3 -2
  52. data/ext/say_osx_context.h +37 -4
  53. data/ext/say_osx_window.h +32 -37
  54. data/ext/say_pixel_bus.c +163 -0
  55. data/ext/say_pixel_bus.h +44 -0
  56. data/ext/say_polygon.c +2 -2
  57. data/ext/say_shader.c +66 -62
  58. data/ext/say_shader.h +2 -0
  59. data/ext/say_sprite.c +1 -2
  60. data/ext/say_target.c +14 -23
  61. data/ext/say_target.h +3 -1
  62. data/ext/say_text.c +45 -7
  63. data/ext/say_text.h +12 -3
  64. data/ext/say_thread.c +13 -6
  65. data/ext/say_thread.h +1 -1
  66. data/ext/say_thread_variable.c +5 -5
  67. data/ext/say_vertex_type.c +79 -41
  68. data/ext/say_vertex_type.h +6 -2
  69. data/ext/say_view.c +10 -31
  70. data/ext/say_view.h +1 -5
  71. data/ext/say_win.h +2 -2
  72. data/ext/say_win_context.h +49 -11
  73. data/ext/say_win_window.h +30 -27
  74. data/ext/say_window.c +3 -3
  75. data/ext/say_x11.h +3 -1
  76. data/ext/say_x11_context.h +64 -10
  77. data/ext/say_x11_window.h +22 -17
  78. data/ext/shader.c +9 -0
  79. data/ext/sprite.c +7 -1
  80. data/ext/target.c +80 -28
  81. data/ext/text.c +43 -1
  82. data/ext/view.c +53 -1
  83. data/ext/window.c +4 -0
  84. data/lib/ray/animation_list.rb +17 -2
  85. data/lib/ray/audio_source.rb +11 -0
  86. data/lib/ray/color.rb +14 -0
  87. data/lib/ray/drawable.rb +23 -0
  88. data/lib/ray/dsl/event.rb +1 -9
  89. data/lib/ray/dsl/event_runner.rb +3 -4
  90. data/lib/ray/dsl/matcher.rb +20 -1
  91. data/lib/ray/effect.rb +116 -0
  92. data/lib/ray/effect/black_and_white.rb +38 -0
  93. data/lib/ray/effect/color_inversion.rb +16 -0
  94. data/lib/ray/effect/generator.rb +145 -0
  95. data/lib/ray/effect/grayscale.rb +32 -0
  96. data/lib/ray/game.rb +25 -5
  97. data/lib/ray/gl/vertex.rb +105 -26
  98. data/lib/ray/helper.rb +5 -0
  99. data/lib/ray/image.rb +54 -13
  100. data/lib/ray/image_target.rb +7 -0
  101. data/lib/ray/matrix.rb +26 -0
  102. data/lib/ray/music.rb +4 -0
  103. data/lib/ray/pixel_bus.rb +22 -0
  104. data/lib/ray/polygon.rb +17 -0
  105. data/lib/ray/pp.rb +28 -0
  106. data/lib/ray/ray.rb +7 -1
  107. data/lib/ray/rect.rb +7 -13
  108. data/lib/ray/scene.rb +24 -5
  109. data/lib/ray/scene_list.rb +9 -0
  110. data/lib/ray/shader.rb +11 -2
  111. data/lib/ray/sound.rb +4 -0
  112. data/lib/ray/sprite.rb +23 -62
  113. data/lib/ray/target.rb +25 -0
  114. data/lib/ray/text.rb +10 -0
  115. data/lib/ray/turtle.rb +9 -3
  116. data/lib/ray/vector.rb +18 -0
  117. data/lib/ray/vertex.rb +6 -0
  118. data/lib/ray/view.rb +22 -0
  119. data/samples/animation/sprite_motion.rb +0 -60
  120. data/samples/audio/{spacial.rb → spatial.rb} +1 -1
  121. data/samples/buffer/buffer.rb +1 -0
  122. data/samples/buffer/index_buffer.rb +2 -0
  123. data/samples/cptn_ruby/cptn_ruby.rb +6 -7
  124. data/samples/effects/effect.rb +39 -0
  125. data/samples/effects/grayscale.rb +27 -0
  126. data/samples/opengl/image.rb +7 -5
  127. data/samples/opengl/instancing.rb +159 -0
  128. data/samples/opengl/instancing.rbc +3231 -0
  129. data/samples/opengl/obj_loader.rb +9 -8
  130. data/samples/opengl/shader.rb +1 -3
  131. data/samples/shaders/geometry.rb +108 -38
  132. data/samples/shaders/geometry.rbc +2074 -0
  133. data/samples/shaders/shape.rb +2 -2
  134. data/samples/starfighter/starfighter.rb +5 -5
  135. data/samples/window/get_pixel.rb +1 -1
  136. data/test/animation_list_test.rb +18 -4
  137. data/test/drawable_test.rb +70 -1
  138. data/test/effect_generator_test.rb +63 -0
  139. data/test/effect_test.rb +61 -0
  140. data/test/game_test.rb +18 -0
  141. data/test/gl_buffer_test.rb +43 -1
  142. data/test/gl_index_buffer_test.rb +5 -0
  143. data/test/gl_vertex_test.rb +28 -1
  144. data/test/image_test.rb +5 -5
  145. data/test/input_test.rb +49 -0
  146. data/test/pixel_bus_test.rb +28 -0
  147. data/test/rect_test.rb +4 -0
  148. data/{samples/_media → test/res}/Beep.wav +0 -0
  149. data/samples/_media/CptnRuby Gem.png b/data/test/res/CptnRuby → Gem.png +0 -0
  150. data/samples/_media/CptnRuby Map.txt b/data/test/res/CptnRuby → Map.txt +0 -0
  151. data/samples/_media/CptnRuby Tileset.png b/data/test/res/CptnRuby → Tileset.png +0 -0
  152. data/{samples/_media → test/res}/CptnRuby.png +0 -0
  153. data/{samples/_media → test/res}/Space.png +0 -0
  154. data/{samples/_media → test/res}/Star.png +0 -0
  155. data/{samples/_media → test/res}/Starfighter.png +0 -0
  156. data/test/res/cube.obj +28 -0
  157. data/test/res/light3d.c +2 -2
  158. data/test/res/stone.png +0 -0
  159. data/test/scene_test.rb +3 -0
  160. data/test/sprite_test.rb +10 -0
  161. data/test/text_test.rb +31 -2
  162. data/test/view_test.rb +13 -1
  163. metadata +38 -17
  164. data/ext/say_array.c +0 -124
  165. data/ext/say_array.h +0 -34
  166. data/ext/say_table.c +0 -86
  167. data/ext/say_table.h +0 -24
@@ -0,0 +1,38 @@
1
+ module Ray
2
+ class Effect
3
+ # A black and white effect. It considers the grayscale level of the image,
4
+ # and sets pixels that are darker than a given value to black, and the
5
+ # others to white.
6
+ class BlackAndWhite < Effect
7
+ effect_name :black_and_white
8
+ attribute :ratio, :vec3
9
+ attribute :value, :float
10
+
11
+ # @param [Ray::Vector3] ratio Default ratio to compute grayscale
12
+ # @param [Float] value Minimal grayscale level of white pixels
13
+ def initialize(value = 0.5, ratio = [0.299, 0.587, 0.114])
14
+ @ratio = ratio
15
+ @value = value
16
+ end
17
+
18
+ # @return [Ray::Vector3] ratio
19
+ attr_accessor :ratio
20
+
21
+ def defaults
22
+ {:ratio => @ratio, :value => @value}
23
+ end
24
+
25
+ def code
26
+ return <<code
27
+ vec4 do_black_and_white(ray_black_and_white args, vec4 color) {
28
+ float gray = dot(color.rgb, args.ratio);
29
+ if (gray > args.value)
30
+ return vec4(1, 1, 1, color.a);
31
+ else
32
+ return vec4(0, 0, 0, color.a);
33
+ }
34
+ code
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,16 @@
1
+ module Ray
2
+ class Effect
3
+ # An effect to invert the color of a pixel. This preserves the alpha component.
4
+ class ColorInversion < Effect
5
+ effect_name :color_inversion
6
+
7
+ def code
8
+ return <<code
9
+ vec4 do_color_inversion(ray_color_inversion args, vec4 color) {
10
+ return vec4(vec3(1, 1, 1) - color.rgb, color.a);
11
+ }
12
+ code
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,145 @@
1
+ module Ray
2
+ class Effect
3
+ class Generator
4
+ include Enumerable
5
+
6
+ # @param [Integer] version GLSL version to use
7
+ # @yield Yields itself if a block is given
8
+ def initialize(version = 110)
9
+ @effects = []
10
+ @version = version
11
+
12
+ if version >= 130
13
+ @input = <<-input
14
+ in vec4 var_Color;
15
+ in vec2 var_TexCoord;
16
+ input
17
+ else
18
+ @input = <<-input
19
+ varying vec4 var_Color;
20
+ varying vec2 var_TexCoord;
21
+ input
22
+ end
23
+
24
+ @uniforms = <<-uniforms
25
+ uniform sampler2D in_Texture;
26
+ uniform bool in_TextureEnabled;
27
+ uniforms
28
+
29
+ @color = "color"
30
+
31
+ @default = <<-default
32
+ /* Apply default value */
33
+ vec4 color;
34
+ if (in_TextureEnabled)
35
+ color = texture#{"2D" if version<130}(in_Texture, var_TexCoord) * var_Color;
36
+ else
37
+ color = var_Color;
38
+ default
39
+
40
+ yield self if block_given?
41
+ end
42
+
43
+ # @param [Array<Ray:::Effect>] effects effects to add to the generator
44
+ def push(*effects)
45
+ @effects.concat effects
46
+ self
47
+ end
48
+
49
+ alias << push
50
+
51
+ def each(&block)
52
+ @effects.each(&block)
53
+ end
54
+
55
+ # @return [Array<Ray::Effect>] All of the effects used by the generator
56
+ attr_reader :effects
57
+ alias to_a effects
58
+
59
+ # @return [Integer] GLSL version number
60
+ attr_reader :version
61
+
62
+ # @return [String] Code defining GLSL input (with varying or in, depending
63
+ # on the GLSL version).
64
+ attr_accessor :input
65
+
66
+ # @return [String] Code defining uniforms
67
+ attr_accessor :uniforms
68
+
69
+ # @return [String] Name of the variable containing the
70
+ # color once the default code is run. "color" by default.
71
+ attr_accessor :color
72
+
73
+ # @return [String] Code to ste the default color. It should apply
74
+ # texturing, for example.
75
+ attr_accessor :default
76
+
77
+ # @return [String] code of the pixel shader
78
+ def code
79
+ str = "#version #@version\n"
80
+ str << "\n"
81
+
82
+ str << input << "\n"
83
+ str << uniforms << "\n"
84
+
85
+
86
+ str << "out vec4 out_FragColor;\n" if version >= 130
87
+ str << "\n"
88
+
89
+ str << "/* Headers */\n"
90
+ each do |effect|
91
+ str << effect.header << "\n"
92
+ end
93
+ str << "\n"
94
+
95
+ str << "/* Structs */\n\n"
96
+ each do |effect|
97
+ str << effect.struct << "\n"
98
+ end
99
+
100
+ str << "/* Effects parameters */\n"
101
+ each do |effect|
102
+ str << "uniform ray_#{effect.name} #{effect.name};\n"
103
+ end
104
+ str << "\n"
105
+
106
+ str << "/* Functions */\n"
107
+ each do |effect|
108
+ str << effect.code << "\n"
109
+ end
110
+
111
+ str << "void main() {\n"
112
+ str << default << "\n"
113
+
114
+ each do |effect|
115
+ str << " if (#{effect.name}.enabled)\n"
116
+ str << " #@color = do_#{effect.name}(#{effect.name}, #@color);\n"
117
+ str << "\n"
118
+ end
119
+
120
+ if version >= 130
121
+ str << " out_FragColor = #@color;\n"
122
+ else
123
+ str << " gl_FragColor = #@color;\n"
124
+ end
125
+
126
+ str << "}\n"
127
+ end
128
+
129
+ # Generates a shader
130
+ # @param [Ray::Shader] shader Shader to compile and apply defaults to
131
+ # @return [Ray::Shader] shader
132
+ def build(shader = Ray::Shader.new)
133
+ shader.compile :frag => StringIO.new(code)
134
+ apply_defaults shader
135
+ shader
136
+ end
137
+
138
+ # Apply generator defaults to a shader
139
+ # @param [Ray::Shader] shader Shader to apply defaults to
140
+ def apply_defaults(shader)
141
+ each { |effect| effect.apply_defaults(shader) }
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,32 @@
1
+ module Ray
2
+ class Effect
3
+ # A grayscale effect. To accomplish it, it computes the dot product of the
4
+ # color by a ratio, and assigns the result to red, green, and blue
5
+ # components. Alpha component is preserved.
6
+ class Grayscale < Effect
7
+ effect_name :grayscale
8
+ attribute :ratio, :vec3
9
+
10
+ # @param [Ray::Vector3] ratio Default ratio
11
+ def initialize(ratio = [0.299, 0.587, 0.114])
12
+ @ratio = ratio
13
+ end
14
+
15
+ # @return [Ray::Vector3] ratio
16
+ attr_accessor :ratio
17
+
18
+ def defaults
19
+ {:ratio => @ratio}
20
+ end
21
+
22
+ def code
23
+ return <<code
24
+ vec4 do_grayscale(ray_grayscale args, vec4 color) {
25
+ float gray = dot(color.rgb, args.ratio);
26
+ return vec4(gray, gray, gray, color.a);
27
+ }
28
+ code
29
+ end
30
+ end
31
+ end
32
+ end
@@ -120,11 +120,31 @@ module Ray
120
120
  @game_scenes.push(scene_name, *args)
121
121
  end
122
122
 
123
- # Pops the last scene.
123
+ # Pops the last scene
124
124
  def pop_scene
125
125
  @game_scenes.pop
126
126
  end
127
127
 
128
+ # Pops scenes while a condition is true
129
+ #
130
+ # @yield [scene] To determine if a scene must be popped.
131
+ # @yieldparam [Ray::Scene] scene The scene that will be popped
132
+ # @yieldreturn [Boolean] True to pop the next scene
133
+ def pop_scene_while
134
+ while yield scenes.current
135
+ scenes.current.pop_scene # ensure exit is set to false
136
+ end
137
+ end
138
+
139
+ # Pops scenes until a condition is met
140
+ #
141
+ # @yield (see #pop_scene_while)
142
+ # @yieldparam scene (see #pop_scene_while)
143
+ # @yieldreturn [Boolean] False to pop the next scene
144
+ def pop_scene_until
145
+ pop_scene_while { |o| !yield(o) }
146
+ end
147
+
128
148
  # Registers a new scene with a given name. the block will be passed
129
149
  # to klass.new.
130
150
  #
@@ -136,6 +156,7 @@ module Ray
136
156
  scene.game = self
137
157
  scene.event_runner = event_runner
138
158
  scene.window = @game_window
159
+ scene.name = name
139
160
  end
140
161
 
141
162
  # @return [Ray::Scene] scene register for a given name
@@ -183,14 +204,17 @@ module Ray
183
204
  @game_scenes.clear
184
205
  end
185
206
 
207
+ # @return [Ray::String]
186
208
  def title
187
209
  @game_title
188
210
  end
189
211
 
212
+ # @return [Ray::SceneList]
190
213
  def scenes
191
214
  @game_scenes
192
215
  end
193
216
 
217
+ # @return [Ray::Window]
194
218
  def window
195
219
  @game_window
196
220
  end
@@ -210,10 +234,6 @@ module Ray
210
234
  scene.event_runner = runner
211
235
  end
212
236
  end
213
-
214
- def inspect
215
- "game(#{title.inspect})"
216
- end
217
237
  end
218
238
 
219
239
  # (see Ray::Game#initialize)
@@ -1,15 +1,51 @@
1
1
  module Ray
2
2
  module GL
3
3
  class Vertex
4
+ include Ray::PP
5
+
4
6
  @vertex_classes = {0 => Ray::Vertex}
5
7
 
8
+ class Instance
9
+ include Ray::PP
10
+
11
+ @instance_classes = {}
12
+
13
+ class << self
14
+ attr_reader :instance_classes
15
+ end
16
+
17
+ def self.layout
18
+ @vertex_instance_layout
19
+ end
20
+
21
+ def to_s
22
+ pairs = []
23
+ self.class.layout.each do |key, _, _, per_instance|
24
+ pairs << "#{key}=#{send(key)}" if per_instance
25
+ end
26
+
27
+ "#<#{self.class} #{pairs.join " "}>"
28
+ end
29
+
30
+ def pretty_print(q)
31
+ attr = []
32
+ self.class.layout.each do |key, _, _, per_instance|
33
+ attr << key if per_instance
34
+ end
35
+
36
+ pretty_print_attributes q, attr
37
+ end
38
+ end
39
+
6
40
  # Creates a new Vertex class with a custom layout. Layout is an array of
7
- # arrays, where each row contains 3 elements:
41
+ # arrays, where each row contains 3 or 4 elements:
8
42
  #
9
43
  # 1. Attribute name in Ruby
10
44
  # 2. Attribute name in GLSL shaders
11
45
  # 3. Attribute type, one of the following symbols: float, int, ubyte,
12
46
  # bool, color, vector2, vector3.
47
+ # 4. Whether the attribute is only accessibel on a per-instance basis (as
48
+ # opposed to per-vertex, which is the default)
13
49
  #
14
50
  # Getters and setters are created for all of the attributes.
15
51
  #
@@ -23,10 +59,13 @@ module Ray
23
59
  # [:bool, "in_Bool", :bool],
24
60
  # [:color, "in_Color", :color],
25
61
  # [:vector2, "in_Vector2", :vector2],
26
- # [:vector3, "in_Vector3", :vector3]
62
+ # [:vector3, "in_Vector3", :vector3],
63
+ #
64
+ # # per-instance data
65
+ # [:instance_color, "magic", :color, true]
27
66
  # ]
28
67
  def self.make(layout)
29
- layout.each do |_, _, type|
68
+ layout.each do |_, _, type, _|
30
69
  unless TypeMap.has_key? type
31
70
  raise ArgumentError, "unknown type in a vertex: #{type.inspect}"
32
71
  end
@@ -40,6 +79,20 @@ module Ray
40
79
  @vertex_type_size = Vertex.size(vtype)
41
80
  @vertex_type_layout = layout
42
81
 
82
+ if layout.any? { |_, _, _, per_instance| per_instance }
83
+ const_set :Instance, Class.new(Ray::GL::Vertex::Instance) {
84
+ @vertex_instance_type_id = vtype
85
+ @vertex_instance_size = Vertex.instance_size(vtype)
86
+ @vertex_instance_layout = layout
87
+
88
+ class << self
89
+ undef instance_classes
90
+ end
91
+
92
+ Vertex.define_layout self, layout, vtype, true
93
+ }
94
+ end
95
+
43
96
  class << self
44
97
  undef make
45
98
  undef make_type
@@ -47,18 +100,51 @@ module Ray
47
100
  undef offset_of
48
101
  end
49
102
 
103
+ Vertex.define_layout self, layout, vtype, false
104
+ end
105
+
106
+ if @vertex_classes[vtype].const_defined? :Instance
107
+ Instance.instance_classes[vtype] = @vertex_classes[vtype]::Instance
108
+ end
109
+
110
+ @vertex_classes[vtype]
111
+ end
112
+
113
+ def self.layout
114
+ @vertex_type_layout
115
+ end
116
+
117
+ def self.default_for(type)
118
+ case type
119
+ when :float, :int, :ubyte then 0
120
+ when :bool then true
121
+ when :vector2 then Ray::Vector2[0, 0]
122
+ when :vector3 then Ray::Vector3[0, 0, 0]
123
+ when :color then Ray::Color.white
124
+ end
125
+ end
126
+
127
+ def self.define_layout(on, layout, vtype, instance = false)
128
+ argument_layout = layout.reject do |_, _, _, per_instance|
129
+ per_instance ^ instance
130
+ end
131
+
132
+ on.class_eval do
50
133
  define_method :initialize do |*args|
51
134
  if args.size > layout.size
52
- raise ArgumentError, "wrong number of arguments: %d for %d",
53
- args.size, layout.size
135
+ msg = "wrong number of arguments: #{args.size} for" <<
136
+ " #{layout.size}"
137
+ raise ArgumentError, msg
54
138
  end
55
139
 
56
- layout.each_with_index do |(attr, _, type), i|
57
- send("#{attr}=", args[i] || default_for(type))
140
+ argument_layout.each_with_index do |(attr, _, type, _), i|
141
+ send("#{attr}=", args[i] || Vertex.default_for(type))
58
142
  end
59
143
  end
60
144
 
61
- layout.each_with_index do |(attr, _, type), i|
145
+ layout.each_with_index do |(attr, _, type, per_instance), i|
146
+ next if per_instance ^ instance
147
+
62
148
  offset = Vertex.offset_of(vtype, i)
63
149
 
64
150
  # Faster than define_method.
@@ -77,29 +163,22 @@ module Ray
77
163
  end
78
164
  end
79
165
 
80
- def self.layout
81
- @vertex_type_layout
82
- end
83
-
84
166
  def to_s
85
- "#<#{self.class} #{pairs}>"
86
- end
167
+ pairs = []
168
+ self.class.layout.each do |key, _, _, per_instance|
169
+ pairs << "#{key}=#{send(key)}" unless per_instance
170
+ end
87
171
 
88
- def pairs
89
- self.class.layout.map { |key, _, _|
90
- "#{key}=#{send(key)}"
91
- }.join " "
172
+ "#<#{self.class} #{pairs.join " "}>"
92
173
  end
93
174
 
94
- private
95
- def default_for(type)
96
- case type
97
- when :float, :int, :ubyte then 0
98
- when :bool then true
99
- when :vector2 then Ray::Vector2[0, 0]
100
- when :vector3 then Ray::Vector3[0, 0, 0]
101
- when :color then Ray::Color.white
175
+ def pretty_print(q)
176
+ attr = []
177
+ self.class.layout.each do |key, _, _, per_instance|
178
+ attr << key unless per_instance
102
179
  end
180
+
181
+ pretty_print_attributes q, attr
103
182
  end
104
183
  end
105
184
  end