summaryrefslogtreecommitdiffhomepage
path: root/dragon
diff options
context:
space:
mode:
authorDan Healy <[email protected]>2020-10-09 19:27:13 -0700
committerGitHub <[email protected]>2020-10-09 19:27:13 -0700
commit131acd61d88e559dac3067384a116ee8dec50982 (patch)
tree55cfbb4a9842bac5855b588a6c669c9a583b23ec /dragon
parent732e813bbc03ab14b89e3f4e2d6196fcd696879e (diff)
parentcdf663a63bf59af5eddfd6e9f4ba065516082c13 (diff)
downloaddragonruby-game-toolkit-contrib-131acd61d88e559dac3067384a116ee8dec50982.tar.gz
dragonruby-game-toolkit-contrib-131acd61d88e559dac3067384a116ee8dec50982.zip
Merge branch 'master' into performance-tracing
Diffstat (limited to 'dragon')
-rw-r--r--dragon/args.rb17
-rw-r--r--dragon/assert.rb92
-rw-r--r--dragon/console.rb113
-rw-r--r--dragon/console_menu.rb158
-rw-r--r--dragon/console_prompt.rb41
-rw-r--r--dragon/docs.rb84
-rw-r--r--dragon/easing.rb82
-rw-r--r--dragon/framerate_diagnostics.rb165
-rw-r--r--dragon/grid.rb39
-rw-r--r--dragon/inputs.rb6
-rw-r--r--dragon/kernel_docs.rb8
-rw-r--r--dragon/log.rb40
-rw-r--r--dragon/numeric.rb48
-rw-r--r--dragon/numeric_docs.rb240
-rw-r--r--dragon/outputs_docs.rb8
-rw-r--r--dragon/readme_docs.rb272
-rw-r--r--dragon/runtime_docs.rb17
-rw-r--r--dragon/string.rb7
-rw-r--r--dragon/tests.rb13
-rw-r--r--dragon/trace.rb7
20 files changed, 1187 insertions, 270 deletions
diff --git a/dragon/args.rb b/dragon/args.rb
index 2c4c604..38a28b7 100644
--- a/dragon/args.rb
+++ b/dragon/args.rb
@@ -41,7 +41,7 @@ module GTK
# @return [OpenEntity]
attr_accessor :state
- # Gives you access to the top level DragonRuby runtime.
+ # Gives you access to the top level DragonRuby runtime.
#
# @return [Runtime]
attr_accessor :runtime
@@ -49,6 +49,8 @@ module GTK
attr_accessor :passes
+ attr_accessor :wizards
+
def initialize runtime, recording
@inputs = Inputs.new
@outputs = Outputs.new args: self
@@ -57,12 +59,14 @@ module GTK
@state.tick_count = -1
@runtime = runtime
@recording = recording
- @grid = Grid.new runtime.ffi_draw
+ @grid = Grid.new runtime
@render_targets = {}
@all_tests = []
@geometry = GTK::Geometry
+ @wizards = Wizards.new
end
+
# The number of ticks since the start of the game.
#
# @return [Integer]
@@ -101,15 +105,6 @@ module GTK
end
def render_target name
- if @state.tick_count == 0
- log_important <<-S
-* WARNING:
-~render_target~ with name ~#{name}~ was created
-on ~args.state.tick_count == 0~. You cannot create ~render_targets~ on the
-first frame and need to wait until ~args.state.tick_count >= 1~.
-S
- end
-
name = name.to_s
if !@render_targets[name]
@render_targets[name] = Outputs.new(args: self, target: name, background_color_override: [255, 255, 255, 0])
diff --git a/dragon/assert.rb b/dragon/assert.rb
index 0eb8c2a..0c26f29 100644
--- a/dragon/assert.rb
+++ b/dragon/assert.rb
@@ -4,13 +4,57 @@
# assert.rb has been released under MIT (*only this file*).
module GTK
+=begin
+This is a tiny assertion api for the unit testing portion of Game Toolkit.
+
+@example
+
+1. Create a file called tests.rb under mygame.
+2. Any method that begins with the word test_ will be considered a test.
+
+def test_this_works args, assert
+ assert.equal! 1, 1
+end
+
+3. To run a test, save the file while the game is running.
+
+@example
+
+To add an assertion open up this class and write:
+
+class Assert
+ def custom_assertion actual, expected, message = nil
+ # this tell Game Toolkit that an assertion was performed (so that the test isn't marked inconclusive).
+ @assertion_performed = true
+
+ # perform your custom logic here and rais an exception to denote a failure.
+
+ raise "Some Error. #{message}."
+ end
+end
+=end
class Assert
attr :assertion_performed
+=begin
+Us this if you are throwing your own exceptions and you want to mark the tests as ran (so that it wont be marked as inconclusive).
+=end
def ok!
@assertion_performed = true
end
+=begin
+Assert if a value is a thruthy value. All assert method take an optional final parameter that is the message to display to the user.
+
+@example
+
+def test_does_this_work args, assert
+ some_result = Person.new
+ assert.true! some_result
+ # OR
+ assert.true! some_result, "Person was not created."
+end
+=end
def true! value, message = nil
@assertion_performed = true
if !value
@@ -20,6 +64,16 @@ module GTK
nil
end
+=begin
+Assert if a value is a falsey value.
+
+@example
+
+def test_does_this_work args, assert
+ some_result = nil
+ assert.false! some_result
+end
+=end
def false! value, message = nil
@assertion_performed = true
if value
@@ -29,16 +83,39 @@ module GTK
nil
end
+=begin
+Assert if two values are equal.
+
+@example
+
+def test_does_this_work args, assert
+ a = 1
+ b = 1
+ assert.equal! a, b
+end
+=end
def equal! actual, expected, message = nil
@assertion_performed = true
if actual != expected
actual_string = "#{actual}#{actual.nil? ? " (nil) " : " " }".strip
- message = "actual: #{actual_string} did not equal expected: #{expected}.\n#{message}"
+ message = "actual:\n#{actual_string} did not equal\nexpected:\n#{expected}.\n#{message}"
raise message
end
nil
end
+=begin
+Assert if a value is explicitly nil (not false).
+
+@example
+
+def test_does_this_work args, assert
+ a = nil
+ b = false
+ assert.nil! a # this will pass
+ assert.nil! b # this will throw an exception.
+end
+=end
def nil! value, message = nil
@assertion_performed = true
if !value.nil?
@@ -47,18 +124,5 @@ module GTK
end
nil
end
-
- def raises! exception_class, message = nil
- @assertion_performed = true
- begin
- yield
- rescue exception_class
- return # Success
- rescue Exception => e
- raise "Expected #{exception_class.name} to be raised but instead #{e.class.name} was raised\n#{message}"
- else
- raise "Expected #{exception_class.name} to be raised but nothing was raised\n#{message}"
- end
- end
end
end
diff --git a/dragon/console.rb b/dragon/console.rb
index acacfd2..32deb15 100644
--- a/dragon/console.rb
+++ b/dragon/console.rb
@@ -13,11 +13,12 @@ module GTK
:last_command_errored, :last_command, :error_color, :shown_at,
:header_color, :archived_log, :last_log_lines, :last_log_lines_count,
:suppress_left_arrow_behavior, :command_set_at,
- :toast_ids,
- :font_style
+ :toast_ids, :bottom,
+ :font_style, :menu
def initialize
@font_style = FontStyle.new(font: 'font.ttf', size_enum: -1, line_height: 1.1)
+ @menu = Menu.new self
@disabled = false
@log_offset = 0
@visible = false
@@ -26,6 +27,7 @@ module GTK
@log = [ 'Console ready.' ]
@max_log_lines = 1000 # I guess...?
@max_history = 1000 # I guess...?
+ @log_invocation_count = 0
@command_history = []
@command_history_index = -1
@nonhistory_input = ''
@@ -76,6 +78,7 @@ module GTK
end
def addsprite obj
+ @log_invocation_count += 1
obj[:id] ||= "id_#{obj[:path]}_#{Time.now.to_i}".to_sym
if @last_line_log_index &&
@@ -103,6 +106,7 @@ module GTK
def addtext obj
@last_log_lines_count ||= 1
+ @log_invocation_count += 1
str = obj.to_s
@@ -288,18 +292,17 @@ S
@last_command_errored = false
rescue Exception => e
string_e = "#{e}"
+ puts "* EXCEPTION: #{e}"
+ log "* EXCEPTION: #{e}"
@last_command_errored = true
if (string_e.include? "wrong number of arguments")
method_name = (string_e.split ":")[0].gsub "'", ""
- results = Kernel.docs_search method_name
- if !results.include "* DOCS: No results found."
+ results = (Kernel.docs_search method_name).strip
+ if !results.include? "* DOCS: No results found."
puts results
log results
end
end
-
- puts "#{e}"
- log "#{e}"
end
end
end
@@ -311,6 +314,10 @@ S
(args.inputs.keyboard.key_up.b && args.inputs.keyboard.key_up.control)
end
+ def scroll_to_bottom
+ @log_offset = 0
+ end
+
def scroll_up_full
@log_offset += lines_on_one_page
@log_offset = @log.size if @log_offset > @log.size
@@ -424,6 +431,10 @@ S
@command_history_index -= 1
self.current_input_str = @command_history[@command_history_index].dup
end
+ elsif args.inputs.keyboard.key_down.left
+ prompt.move_cursor_left
+ elsif args.inputs.keyboard.key_down.right
+ prompt.move_cursor_right
elsif inputs_scroll_up_full? args
scroll_up_full
elsif inputs_scroll_down_full? args
@@ -450,7 +461,7 @@ S
def write_primitive_and_return_offset(args, left, y, str, archived: false)
if str.is_a?(Hash)
padding = 10
- args.outputs.reserved << [left + 10, y - padding * 1.66, str[:w], str[:h], str[:path]].sprite
+ args.outputs.reserved << [left + 10, y + 5, str[:w], str[:h], str[:path]].sprite
return str[:h] + padding
else
write_line args, left, y, str, archived: archived
@@ -465,25 +476,25 @@ S
args.outputs.reserved << font_style.label(x: left.shift_right(10), y: y, text: str, color: color)
end
+ def should_tick?
+ return false if !@toggled_at
+ return false if slide_progress == 0
+ return false if @disabled
+ return visible?
+ end
+
def render args
return if !@toggled_at
+ return if slide_progress == 0
- if visible?
- percent = @toggled_at.global_ease(@animation_duration, :flip, :quint, :flip)
- else
- percent = @toggled_at.global_ease(@animation_duration, :flip, :quint)
- end
-
- return if percent == 0
-
- bottom = top - (h * percent)
- args.outputs.reserved << [left, bottom, w, h, *@background_color.mult_alpha(percent)].solid
- args.outputs.reserved << [right.shift_left(110), bottom.shift_up(630), 100, 100, @logo, 0, (80.0 * percent).to_i].sprite
+ @bottom = top - (h * slide_progress)
+ args.outputs.reserved << [left, @bottom, w, h, *@background_color.mult_alpha(slide_progress)].solid
+ args.outputs.reserved << [right.shift_left(110), @bottom.shift_up(630), 100, 100, @logo, 0, (80.0 * slide_progress).to_i].sprite
- y = bottom + 2 # just give us a little padding at the bottom.
+ y = @bottom + 2 # just give us a little padding at the bottom.
prompt.render args, x: left.shift_right(10), y: y
y += line_height_px * 1.5
- args.outputs.reserved << line(y: y, color: @text_color.mult_alpha(percent))
+ args.outputs.reserved << line(y: y, color: @text_color.mult_alpha(slide_progress))
y += line_height_px.to_f / 2.0
((@log.size - @log_offset) - 1).downto(0) do |idx|
@@ -492,8 +503,8 @@ S
break if y > top
end
- # past log seperator
- args.outputs.reserved << line(y: y + line_height_px.half, color: @text_color.mult_alpha(0.25 * percent))
+ # past log separator
+ args.outputs.reserved << line(y: y + line_height_px.half, color: @text_color.mult_alpha(0.25 * slide_progress))
y += line_height_px
@@ -504,29 +515,6 @@ S
end
render_log_offset args
- render_help args, top if percent == 1
- end
-
- def render_help args, top
- [
- "* Prompt Commands: ",
- "You can type any of the following ",
- "commands in the command prompt. ",
- "** docs: Provides API docs. ",
- "** $gtk: Accesses the global runtime.",
- "* Shortcut Keys: ",
- "** full page up: ctrl + b ",
- "** full page down: ctrl + f ",
- "** half page up: ctrl + u ",
- "** half page down: ctrl + d ",
- "** clear prompt: ctrl + g ",
- "** up arrow: next command ",
- "** down arrow: prev command ",
- ].each_with_index do |s, i|
- args.outputs.reserved << [args.grid.right - 10,
- top - 100 - line_height_px * i * 0.8,
- s, -3, 2, 180, 180, 180].label
- end
end
def render_log_offset args
@@ -582,9 +570,17 @@ S
begin
return if @disabled
render args
- calc args
process_inputs args
+ return unless should_tick?
+ calc args
+ prompt.tick
+ menu.tick args
rescue Exception => e
+ begin
+ puts "#{e}"
+ puts "* FATAL: The GTK::Console console threw an unhandled exception and has been reset. You should report this exception (along with reproduction steps) to DragonRuby."
+ rescue
+ end
@disabled = true
$stdout.puts e
$stdout.puts "* FATAL: The GTK::Console console threw an unhandled exception and has been reset. You should report this exception (along with reproduction steps) to DragonRuby."
@@ -680,12 +676,11 @@ S
end
def include_header_marker? log_entry
- return false if log_entry.include? "NOTIFY:"
- return false if log_entry.include? "INFO:"
- return true if log_entry.include? "DOCS:"
- (log_entry.start_with? "* ") ||
- (log_entry.start_with? "** ") ||
- (log_entry.start_with? "*** ")
+ return false if (log_entry.strip.include? ".rb")
+ (log_entry.start_with? "* ") ||
+ (log_entry.start_with? "** ") ||
+ (log_entry.start_with? "*** ") ||
+ (log_entry.start_with? "**** ")
end
def color_for_log_entry(log_entry)
@@ -697,7 +692,7 @@ S
@text_color.mult_alpha(0.5)
elsif include_header_marker? log_entry
@header_color
- elsif log_entry.start_with?("====") || log_entry.include?("app") && !log_entry.include?("apple")
+ elsif log_entry.start_with?("====")
@header_color
else
@text_color
@@ -722,5 +717,15 @@ S
@prompt.clear
:console_silent_eval
end
+
+ def slide_progress
+ return 0 if !@toggled_at
+ if visible?
+ @slide_progress = @toggled_at.global_ease(@animation_duration, :flip, :quint, :flip)
+ else
+ @slide_progress = @toggled_at.global_ease(@animation_duration, :flip, :quint)
+ end
+ @slide_progress
+ end
end
end
diff --git a/dragon/console_menu.rb b/dragon/console_menu.rb
new file mode 100644
index 0000000..7449f4a
--- /dev/null
+++ b/dragon/console_menu.rb
@@ -0,0 +1,158 @@
+# Copyright 2019 DragonRuby LLC
+# MIT License
+# console_menu.rb has been released under MIT (*only this file*).
+
+module GTK
+ class Console
+ class Menu
+ attr_accessor :buttons
+
+ def initialize console
+ @console = console
+ end
+
+ def record_clicked
+ $recording.start 100
+ end
+
+ def replay_clicked
+ $replay.start 'replay.txt'
+ end
+
+ def reset_clicked
+ $gtk.reset
+ end
+
+ def scroll_up_clicked
+ @console.scroll_up_half
+ end
+
+ def scroll_down_clicked
+ @console.scroll_down_half
+ end
+
+ def show_menu_clicked
+ @menu_shown = :visible
+ end
+
+ def close_clicked
+ @menu_shown = :hidden
+ @console.hide
+ end
+
+ def hide_menu_clicked
+ @menu_shown = :hidden
+ end
+
+ def framerate_diagnostics_clicked
+ @console.scroll_to_bottom
+ $gtk.framerate_diagnostics
+ end
+
+ def itch_wizard_clicked
+ @console.scroll_to_bottom
+ $wizards.itch.start
+ end
+
+ def docs_clicked
+ @console.scroll_to_bottom
+ log Kernel.docs_classes
+ end
+
+ def scroll_end_clicked
+ @console.scroll_to_bottom
+ end
+
+ def custom_buttons
+ []
+ end
+
+ def tick args
+ return unless @console.visible?
+
+ @menu_shown ||= :hidden
+
+ if $gtk.production
+ @buttons = [
+ (button id: :record, row: 0, col: 9, text: "record gameplay", method: :record_clicked),
+ (button id: :replay, row: 0, col: 10, text: "start replay", method: :replay_clicked),
+ ]
+ elsif @menu_shown == :hidden
+ @buttons = [
+ (button id: :show_menu, row: 0, col: 10, text: "show menu", method: :show_menu_clicked),
+ ]
+ else
+ @buttons = [
+ (button id: :scroll_up, row: 0, col: 6, text: "scroll up", method: :scroll_up_clicked),
+ (button id: :scroll_down, row: 0, col: 7, text: "scroll down", method: :scroll_down_clicked),
+ (button id: :scroll_down, row: 0, col: 8, text: "scroll end", method: :scroll_end_clicked),
+ (button id: :close, row: 0, col: 9, text: "close console", method: :close_clicked),
+ (button id: :hide, row: 0, col: 10, text: "hide menu", method: :hide_menu_clicked),
+
+ (button id: :record, row: 1, col: 7, text: "record gameplay", method: :record_clicked),
+ (button id: :replay, row: 1, col: 8, text: "start replay", method: :replay_clicked),
+ (button id: :record, row: 1, col: 9, text: "framerate diagnostics", method: :framerate_diagnostics_clicked),
+ (button id: :reset, row: 1, col: 10, text: "reset game", method: :reset_clicked),
+
+ (button id: :reset, row: 2, col: 10, text: "docs", method: :docs_clicked),
+ (button id: :reset, row: 2, col: 9, text: "itch wizard", method: :itch_wizard_clicked),
+ *custom_buttons
+ ]
+ end
+
+ # render
+ args.outputs.reserved << @buttons.map { |b| b[:primitives] }
+
+ # inputs
+ if args.inputs.mouse.click
+ clicked = @buttons.find { |b| args.inputs.mouse.inside_rect? b[:rect] }
+ if clicked
+ send clicked[:method]
+ end
+ end
+ end
+
+ def rect_for_layout row, col
+ col_width = 100
+ row_height = 50
+ col_margin = 5
+ row_margin = 5
+ x = (col_margin + (col * col_width) + (col * col_margin))
+ y = (row_margin + (row * row_height) + (row * row_margin) + row_height).from_top
+ { x: x, y: y, w: col_width, h: row_height }
+ end
+
+ def button args
+ id, row, col, text, method = args[:id], args[:row], args[:col], args[:text], args[:method]
+
+ font_height = @console.font_style.line_height_px.half
+ {
+ id: id,
+ rect: (rect_for_layout row, col),
+ text: text,
+ method: method
+ }.let do |entity|
+ primitives = []
+ primitives << entity[:rect].merge(a: 164).solid
+ primitives << entity[:rect].merge(r: 255, g: 255, b: 255).border
+ primitives << text.wrapped_lines(5)
+ .map_with_index do |l, i|
+ [
+ entity[:rect][:x] + entity[:rect][:w].half,
+ entity[:rect][:y] + entity[:rect][:h].half + font_height - (i * (font_height + 2)),
+ l, -3, 1, 255, 255, 255
+ ]
+ end.labels
+
+ entity.merge(primitives: primitives)
+ end
+ end
+
+ def serialize
+ {
+ not_supported: "#{self}"
+ }
+ end
+ end
+ end
+end
diff --git a/dragon/console_prompt.rb b/dragon/console_prompt.rb
index dd454f7..3d1257b 100644
--- a/dragon/console_prompt.rb
+++ b/dragon/console_prompt.rb
@@ -8,7 +8,7 @@
module GTK
class Console
class Prompt
- attr_accessor :current_input_str, :font_style, :console_text_width
+ attr_accessor :current_input_str, :font_style, :console_text_width, :last_input_str, :last_input_str_changed
def initialize(font_style:, text_color:, console_text_width:)
@prompt = '-> '
@@ -18,22 +18,43 @@ module GTK
@cursor_color = Color.new [187, 21, 6]
@console_text_width = console_text_width
+ @cursor_position = 0
+
@last_autocomplete_prefix = nil
@next_candidate_index = 0
end
+ def current_input_str=(str)
+ @current_input_str = str
+ @cursor_position = str.length
+ end
+
def <<(str)
- @current_input_str << str
+ @current_input_str = @current_input_str[0...@cursor_position] + str + @current_input_str[@cursor_position..-1]
+ @cursor_position += str.length
+ @current_input_changed_at = Kernel.global_tick_count
reset_autocomplete
end
def backspace
- @current_input_str.chop!
+ return if current_input_str.length.zero? || @cursor_position.zero?
+
+ @current_input_str = @current_input_str[0...(@cursor_position - 1)] + @current_input_str[@cursor_position..-1]
+ @cursor_position -= 1
reset_autocomplete
end
+ def move_cursor_left
+ @cursor_position -= 1 if @cursor_position > 0
+ end
+
+ def move_cursor_right
+ @cursor_position += 1 if @cursor_position < current_input_str.length
+ end
+
def clear
@current_input_str = ''
+ @cursor_position = 0
reset_autocomplete
end
@@ -111,7 +132,19 @@ S
def render(args, x:, y:)
args.outputs.reserved << font_style.label(x: x, y: y, text: "#{@prompt}#{current_input_str}", color: @text_color)
- args.outputs.reserved << font_style.label(x: x - 2, y: y + 3, text: (" " * (@prompt.length + current_input_str.length)) + "|", color: @cursor_color)
+ args.outputs.reserved << font_style.label(x: x - 4, y: y + 3, text: (" " * (@prompt.length + @cursor_position)) + "|", color: @cursor_color)
+ end
+
+ def tick
+ if (@current_input_changed_at) &&
+ (@current_input_changed_at < Kernel.global_tick_count) &&
+ (@last_input_str != @current_input_str)
+ @last_input_str_changed = true
+ @last_input_str = "#{@current_input_str}"
+ @current_input_changed_at = nil
+ else
+ @last_input_str_changed = false
+ end
end
private
diff --git a/dragon/docs.rb b/dragon/docs.rb
index 8f5e8bf..67a50e0 100644
--- a/dragon/docs.rb
+++ b/dragon/docs.rb
@@ -89,7 +89,7 @@ module Docs
def docs_classes
DocsOrganizer.sort_docs_classes!
- list = $docs_classes.map { |mod| "** #{mod.name}" }.join "\n"
+ list = $docs_classes.map { |mod| "** #{mod.name}.docs" }.join "\n"
<<-S
* DOCS:
@@ -107,7 +107,7 @@ S
end
def docs
- docs_methods = [DocsOrganizer.find_methods_with_docs(self), :docs_classes].flatten.map { |d| "** #{d}" }.join "\n"
+ docs_methods = [DocsOrganizer.find_methods_with_docs(self), :docs_classes].flatten.map { |d| "** #{self.name}.#{d}" }.join "\n"
if self == Kernel
<<-S
@@ -201,8 +201,10 @@ S
final_string = "* DOCS: No results found."
end
- $gtk.write_file "docs/search_results.txt", final_string
- log "* INFO: Search results have been written to docs/search_results.txt."
+ $gtk.write_file_root "docs/search_results.txt", final_string
+ if !final_string.include? "* DOCS: No results found."
+ log "* INFO: Search results have been written to docs/search_results.txt."
+ end
"\n" + final_string
end
@@ -216,7 +218,7 @@ S
(send m).ltrim + "\n"
end.join "\n"
file_path = "docs/#{self.name}.txt"
- $gtk.write_file "#{file_path}", content
+ $gtk.write_file_root "#{file_path}", content
puts "* INFO: Documentation for #{self.name} has been exported to #{file_path}."
$gtk.console.set_system_command file_path
nil
@@ -235,19 +237,21 @@ S
# may god have mercy on your soul if you try to expand this
def __docs_to_html__ string
parse_log = []
- html_string = <<-S
+
+ html_start_to_toc_start = <<-S
<html>
<head>
<title>DragonRuby Game Toolkit Documentation</title>
- <link href="docs.css" rel="stylesheet" type="text/css" media="all">
- <script src="docs.js"></script>
+ <link href="docs.css?ver=#{Time.now.to_i}" rel="stylesheet" type="text/css" media="all">
</head>
<body>
<div id='toc'>
- {{toc}}
+S
+ html_toc_end_to_content_start = <<-S
</div>
<div id='content'>
- {{content}}
+S
+ html_content_end_to_html_end = <<-S
</div>
</body>
</html>
@@ -319,6 +323,11 @@ S
__docs_append_true_line__ true_lines, current_true_line, parse_log
__docs_append_true_line__ true_lines, l, parse_log
current_true_line = ""
+ elsif l.start_with? "**** "
+ parse_log << "- Header detected."
+ __docs_append_true_line__ true_lines, current_true_line, parse_log
+ __docs_append_true_line__ true_lines, l, parse_log
+ current_true_line = ""
else
current_true_line += l.rstrip + " "
end
@@ -330,7 +339,7 @@ S
true_lines = true_lines[1..-1]
end
- toc = ""
+ toc_html = ""
content_html = ""
inside_pre = false
@@ -371,7 +380,7 @@ S
inside_ol = false
inside_ul = false
- toc = "<h1>Table Of Contents</h1>\n<ul>\n"
+ toc_html = "<h1>Table Of Contents</h1>\n<ul>\n"
parse_log << "* Processing Html Given True Lines"
true_lines.each do |l|
parse_log << "** Processing line: ~#{l.rstrip}~"
@@ -382,7 +391,7 @@ S
inside_ul = false
formatted_html = __docs_line_to_html__ l, parse_log
link_id = text_to_id.call l
- toc += "<li><a href='##{link_id}'>#{formatted_html}</a></li>\n"
+ toc_html += "<li><a href='##{link_id}'>#{formatted_html}</a></li>\n"
content_html += "<h1 id='#{link_id}'>#{formatted_html}</h1>\n"
elsif l.start_with? "** "
parse_log << "- H2 detected."
@@ -391,7 +400,7 @@ S
inside_ul = false
formatted_html = __docs_line_to_html__ l, parse_log
link_id = text_to_id.call l
- # toc += "<a href='##{link_id}'>#{formatted_html}</a></br>\n"
+ # toc_html += "<a href='##{link_id}'>#{formatted_html}</a></br>\n"
content_html += "<h2>#{__docs_line_to_html__ l, parse_log}</h2>\n"
elsif l.start_with? "*** "
parse_log << "- H3 detected."
@@ -400,8 +409,17 @@ S
inside_ul = false
formatted_html = __docs_line_to_html__ l, parse_log
link_id = text_to_id.call l
- # toc += "<a href='##{link_id}'>#{formatted_html}</a></br>\n"
+ # toc_html += "<a href='##{link_id}'>#{formatted_html}</a></br>\n"
content_html += "<h3>#{__docs_line_to_html__ l, parse_log}</h3>\n"
+ elsif l.start_with? "**** "
+ parse_log << "- H4 detected."
+ content_html += close_list_if_needed.call inside_ul, inside_ol
+ inside_ol = false
+ inside_ul = false
+ formatted_html = __docs_line_to_html__ l, parse_log
+ link_id = text_to_id.call l
+ # toc_html += "<a href='##{link_id}'>#{formatted_html}</a></br>\n"
+ content_html += "<h4>#{__docs_line_to_html__ l, parse_log}</h4>\n"
elsif l.strip.length == 0 && !inside_pre
# do nothing
elsif l.start_with? "#+begin_src"
@@ -451,6 +469,8 @@ S
l = l[3..-1]
elsif l.split(".")[0].length == 3
l = l[4..-1]
+ elsif l.split(".")[0].length == 4
+ l = l[5..-1]
end
content_html << "<li>#{__docs_line_to_html__ l, parse_log}</li>\n"
@@ -493,10 +513,13 @@ S
end
end
end
- toc += "</ul>"
+ toc_html += "</ul>"
- final_html = (html_string.gsub "{{toc}}", toc)
- final_html = (final_html.gsub "{{content}}", content_html)
+ final_html = html_start_to_toc_start +
+ toc_html +
+ html_toc_end_to_content_start +
+ content_html +
+ html_content_end_to_html_end
{
original: string,
@@ -504,15 +527,30 @@ S
parse_log: parse_log
}
rescue Exception => e
- $gtk.write_file 'docs/parse_log.txt', (parse_log.join "\n")
+ $gtk.write_file_root 'docs/parse_log.txt', (parse_log.join "\n")
raise "* ERROR in Docs::__docs_to_html__. #{e}"
end
def __docs_line_to_html__ line, parse_log
- line = line.gsub "* DOCS: ", "" if line.start_with? "* DOCS: "
- line = line.gsub "* ", "" if line.start_with? "* "
- line = line.gsub "** ", "" if line.start_with? "** "
- line = line.gsub "*** ", "" if line.start_with? "*** "
+ parse_log << "- Determining if line is a header."
+ if line.start_with? "**** "
+ line = line.gsub "**** ", ""
+ parse_log << "- Line contains ~**** ~... gsub-ing empty string"
+ elsif line.start_with? "*** "
+ line = line.gsub "*** ", ""
+ parse_log << "- Line contains ~*** ~... gsub-ing empty string"
+ elsif line.start_with? "** "
+ line = line.gsub "** ", ""
+ parse_log << "- Line contains ~** ~... gsub-ing empty string"
+ elsif line.start_with? "* "
+ line = line.gsub "* ", ""
+ parse_log << "- Line contains ~* ~... gsub-ing empty string"
+ elsif line.start_with? "* DOCS: "
+ line = line.gsub "* DOCS: ", ""
+ parse_log << "- Line contains ~* DOCS:~... gsub-ing empty string"
+ else
+ parse_log << "- Line does not appear to be a header."
+ end
tilde_count = line.count "~"
line_has_link_marker = (line.include? "[[") && (line.include? "]]")
diff --git a/dragon/easing.rb b/dragon/easing.rb
new file mode 100644
index 0000000..310e7ed
--- /dev/null
+++ b/dragon/easing.rb
@@ -0,0 +1,82 @@
+# coding: utf-8
+# Copyright 2019 DragonRuby LLC
+# MIT License
+# easing.rb has been released under MIT (*only this file*).
+
+module GTK
+ module Easing
+ def self.ease_extended start_tick, current_tick, end_tick, default_before, default_after, *definitions
+ definitions.flatten!
+ definitions = [:identity] if definitions.length == 0
+ duration = end_tick - start_tick
+ elapsed = current_tick - start_tick
+ y = elapsed.percentage_of(duration).cap_min_max(0, 1)
+
+ definitions.map do |definition|
+ y = Easing.exec_definition(definition, start_tick, duration, y)
+ end
+
+ y
+ end
+
+ def self.ease_spline_extended start_tick, current_tick, end_tick, spline
+ duration = end_tick - start_tick
+ t = (current_tick - start_tick).fdiv duration
+ time_allocation_per_curve = 1.fdiv(spline.length)
+ curve_index, curve_t = t.fdiv(time_allocation_per_curve).let do |spline_t|
+ [spline_t.to_i, spline_t - spline_t.to_i]
+ end
+ Geometry.cubic_bezier curve_t, *spline[curve_index]
+ end
+
+ def self.initial_value *definitions
+ definitions.flatten!
+ return Easing.exec_definition (definitions.value(-1) || :identity), 0, 10, 0
+ end
+
+ def self.final_value *definitions
+ definitions.flatten!
+ return Easing.exec_definition (definitions.value(-1) || :identity), 0, 10, 1.0
+ end
+
+ def self.exec_definition definition, start_tick, duration, x
+ if definition.is_a? Symbol
+ return Easing.send(definition, x).cap_min_max(0, 1)
+ elsif definition.is_a? Proc
+ return definition.call(x, start_tick, duration).cap_min_max(0, 1)
+ end
+
+ raise <<-S
+* ERROR:
+I don't know how to execute easing function with definition #{definition}.
+
+S
+ end
+
+ def self.identity x
+ x
+ end
+
+ def self.flip x
+ 1 - x
+ end
+
+ def self.quad x
+ x * x
+ end
+
+ def self.cube x
+ x * x * x
+ end
+
+ def self.quart x
+ x * x * x * x * x
+ end
+
+ def self.quint x
+ x * x * x * x * x * x
+ end
+ end
+end
+
+Easing = GTK::Easing
diff --git a/dragon/framerate_diagnostics.rb b/dragon/framerate_diagnostics.rb
new file mode 100644
index 0000000..6b952bd
--- /dev/null
+++ b/dragon/framerate_diagnostics.rb
@@ -0,0 +1,165 @@
+# Copyright 2019 DragonRuby LLC
+# MIT License
+# framerate_diagnostics.rb has been released under MIT (*only this file*).
+
+module GTK
+ class Runtime
+ # @visibility private
+ module FramerateDiagnostics
+ def framerate_get_diagnostics
+ <<-S
+* INFO: Framerate Diagnostics
+You can display these diagnostics using:
+
+#+begin_src
+ args.outputs.debug << args.gtk.framerate_diagnostics_primitives
+#+end_src
+
+** Draw Calls: ~<<~ Invocation Perf Counter
+Here is how many times ~args.outputs.PRIMITIVE_ARRAY <<~ was called:
+
+ #{$perf_counter_outputs_push_count} times invoked.
+
+If the number above is high, consider batching primitives so you can lower the invocation of ~<<~. For example.
+
+Instead of:
+
+#+begin_src
+ args.state.enemies.map do |e|
+ e.alpha = 128
+ args.outputs.sprites << e # <-- ~args.outputs.sprites <<~ is invoked a lot
+ end
+#+end_src
+
+Do this:
+
+#+begin_src
+ args.outputs.sprites << args.state
+ .enemies
+ .map do |e| # <-- ~args.outputs.sprites <<~ is only invoked once.
+ e.alpha = 128
+ e
+ end
+#+end_src
+
+** Array Primitives
+~Primitives~ represented as an ~Array~ (~Tuple~) are great for prototyping, but are not as performant as using a ~Hash~.
+
+Here is the number of ~Array~ primitives that were encountered:
+
+ #{$perf_counter_primitive_is_array} Array Primitives.
+
+If the number above is high, consider converting them to hashes. For example.
+
+Instead of:
+
+#+begin_src
+ args.outputs.sprites << [0, 0, 100, 100, 'sprites/enemy.png']
+#+begin_end
+
+Do this:
+
+#+begin_src
+ args.outputs.sprites << { x: 0,
+ y: 0,
+ w: 100,
+ h: 100,
+ path: 'sprites/enemy.png' }
+#+begin_end
+
+** Primitive Counts
+Here are the draw counts ordered by lowest to highest z order:
+
+PRIMITIVE COUNT, STATIC COUNT
+solids: #{@args.outputs.solids.length}, #{@args.outputs.static_solids.length}
+sprites: #{@args.outputs.sprites.length}, #{@args.outputs.static_sprites.length}
+primitives: #{@args.outputs.primitives.length}, #{@args.outputs.static_primitives.length}
+labels: #{@args.outputs.labels.length}, #{@args.outputs.static_labels.length}
+lines: #{@args.outputs.lines.length}, #{@args.outputs.static_lines.length}
+borders: #{@args.outputs.borders.length}, #{@args.outputs.static_borders.length}
+debug: #{@args.outputs.debug.length}, #{@args.outputs.static_debug.length}
+reserved: #{@args.outputs.reserved.length}, #{@args.outputs.static_reserved.length}
+
+** Additional Help
+Come to the DragonRuby Discord channel if you need help troubleshooting performance issues. http://discord.dragonruby.org.
+
+Source code for these diagnostics can be found at: [[https://github.com/dragonruby/dragonruby-game-toolkit-contrib/]]
+S
+ end
+
+ def framerate_warning_message
+ <<-S
+* WARNING:
+Your average framerate dropped below 60 fps for two seconds.
+
+The average FPS was #{current_framerate}.
+
+** How To Disable Warning
+If this warning is getting annoying put the following in your tick method:
+
+#+begin_src
+ args.gtk.log_level = :off
+#+end_src
+
+#{framerate_get_diagnostics}
+ S
+ end
+
+ def current_framerate_primitives
+ framerate_diagnostics_primitives
+ end
+
+ def framerate_diagnostics_primitives
+ [
+ { x: 0, y: 93.from_top, w: 500, h: 93, a: 128 }.solid,
+ {
+ x: 5,
+ y: 5.from_top,
+ text: "More Info via DragonRuby Console: $gtk.framerate_diagnostics",
+ r: 255,
+ g: 255,
+ b: 255,
+ size_enum: -2
+ }.label,
+ {
+ x: 5,
+ y: 20.from_top,
+ text: "FPS: %.2f" % args.gtk.current_framerate,
+ r: 255,
+ g: 255,
+ b: 255,
+ size_enum: -2
+ }.label,
+ {
+ x: 5,
+ y: 35.from_top,
+ text: "Draw Calls: #{$perf_counter_outputs_push_count}",
+ r: 255,
+ g: 255,
+ b: 255,
+ size_enum: -2
+ }.label,
+ {
+ x: 5,
+ y: 50.from_top,
+ text: "Array Primitives: #{$perf_counter_primitive_is_array}",
+ r: 255,
+ g: 255,
+ b: 255,
+ size_enum: -2
+ }.label,
+ {
+ x: 5,
+ y: 65.from_top,
+ text: "Mouse: #{@args.inputs.mouse.point}",
+ r: 255,
+ g: 255,
+ b: 255,
+ size_enum: -2
+ }.label,
+ ]
+ end
+
+ end
+ end
+end
diff --git a/dragon/grid.rb b/dragon/grid.rb
index e5c21a3..c2a6a43 100644
--- a/dragon/grid.rb
+++ b/dragon/grid.rb
@@ -60,8 +60,9 @@ module GTK
attr_accessor :left_margin, :bottom_margin
- def initialize ffi_draw
- @ffi_draw = ffi_draw
+ def initialize runtime
+ @runtime = runtime
+ @ffi_draw = runtime.ffi_draw
origin_bottom_left!
end
@@ -109,18 +110,18 @@ module GTK
return if @name == :bottom_left
@name = :bottom_left
@origin_x = 0.0
- @origin_y = $gtk.logical_height
+ @origin_y = @runtime.logical_height
@left = 0.0
- @right = $gtk.logical_width
- @top = $gtk.logical_height
+ @right = @runtime.logical_width
+ @top = @runtime.logical_height
@bottom = 0.0
@left_margin = 0.0
@bottom_margin = 0.0
- @center_x = $gtk.logical_width.half
- @center_y = $gtk.logical_height.half
- @rect = [@left, @bottom, $gtk.logical_width, $gtk.logical_height].rect
+ @center_x = @runtime.logical_width.half
+ @center_y = @runtime.logical_height.half
+ @rect = [@left, @bottom, @runtime.logical_width, @runtime.logical_height].rect
@center = [@center_x, @center_y].point
- @ffi_draw.set_gtk_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION
+ @ffi_draw.set_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION
end
# Sets the rendering coordinate system to have its origin in the center.
@@ -130,24 +131,24 @@ module GTK
def origin_center!
return if @name == :center
@name = :center
- @origin_x = $gtk.logical_width.half
- @origin_y = $gtk.logical_height.half
- @left = -$gtk.logical_width.half
- @right = $gtk.logical_width.half
- @top = $gtk.logical_height.half
- @bottom = -$gtk.logical_height.half
+ @origin_x = @runtime.logical_width.half
+ @origin_y = @runtime.logical_height.half
+ @left = [email protected]_width.half
+ @right = @runtime.logical_width.half
+ @top = @runtime.logical_height.half
+ @bottom = [email protected]_height.half
@center_x = 0.0
@center_y = 0.0
- @rect = [@left, @bottom, $gtk.logical_width, $gtk.logical_height].rect
+ @rect = [@left, @bottom, @runtime.logical_width, @runtime.logical_height].rect
@center = [@center_x, @center_y].point
- @ffi_draw.set_gtk_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION
+ @ffi_draw.set_grid @origin_x, @origin_y, SCREEN_Y_DIRECTION
end
# The logical width used for rendering.
#
# @return [Float]
def w
- $gtk.logical_width
+ @runtime.logical_width
end
# Half the logical width used for rendering.
@@ -161,7 +162,7 @@ module GTK
#
# @return [Float]
def h
- $gtk.logical_height
+ @runtime.logical_height
end
# Half the logical height used for rendering.
diff --git a/dragon/inputs.rb b/dragon/inputs.rb
index fc6d8ef..a4ef40f 100644
--- a/dragon/inputs.rb
+++ b/dragon/inputs.rb
@@ -295,7 +295,7 @@ S
return self.send m
rescue Exception => e
- log_important "#{e}}"
+ log_important "#{e}"
end
raise <<-S
@@ -498,6 +498,10 @@ module GTK
[@x, @y].point
end
+ def inside_rect? rect
+ point.inside_rect? rect
+ end
+
alias_method :position, :point
def clear
diff --git a/dragon/kernel_docs.rb b/dragon/kernel_docs.rb
index 1f4977b..95e35be 100644
--- a/dragon/kernel_docs.rb
+++ b/dragon/kernel_docs.rb
@@ -53,11 +53,13 @@ S
final_string += k.docs_all
end
+ final_string += "\n" + (($gtk.read_file_root "docs/source.txt") || "")
+
html_parse_result = (__docs_to_html__ final_string)
- $gtk.write_file 'docs/docs.txt', "#{final_string}"
- $gtk.write_file 'docs/docs.html', html_parse_result[:html]
- $gtk.write_file 'docs/parse_log.txt', (html_parse_result[:parse_log].join "\n")
+ $gtk.write_file_root 'docs/docs.txt', "#{final_string}"
+ $gtk.write_file_root 'docs/docs.html', html_parse_result[:html]
+ $gtk.write_file_root 'docs/parse_log.txt', (html_parse_result[:parse_log].join "\n")
log "* INFO: All docs have been exported to docs/docs.txt."
log "* INFO: All docs have been exported to docs/docs.html."
diff --git a/dragon/log.rb b/dragon/log.rb
index 60246e6..7bf09fd 100644
--- a/dragon/log.rb
+++ b/dragon/log.rb
@@ -27,19 +27,19 @@ module GTK
class Log
def self.write_to_log_and_puts *args
return if $gtk.production
- $gtk.append_file 'logs/log.txt', args.join("\n") + "\n"
+ $gtk.append_file_root 'logs/log.txt', args.join("\n") + "\n"
args.each { |obj| $gtk.log obj, self }
end
def self.write_to_log_and_print *args
return if $gtk.production
- $gtk.append_file 'logs/log.txt', args.join("\n")
+ $gtk.append_file_root 'logs/log.txt', args.join("\n")
Object.print(*args)
end
def self.puts_important *args
return if $gtk.production
- $gtk.append_file 'logs/log.txt', args.join("\n")
+ $gtk.append_file_root 'logs/log.txt', args.join("\n")
$gtk.notify! "Important notification occurred."
args.each { |obj| $gtk.log obj }
end
@@ -81,6 +81,30 @@ module GTK
"
end
+ def self.puts_error *args
+ args ||= []
+ title = args[0]
+ additional = args[1..-1] || []
+ additional = "" if additional.length == 0
+ if !title.multiline? && join_lines(additional).multiline?
+ message = headline "ERROR: #{title}" do
+ dynamic_block do
+ additional
+ end
+ end
+ elsif title.multiline?
+ message = headline "ERROR: " do
+ dynamic_block do
+ args
+ end
+ end
+ else
+ message = "* ERROR: #{title} #{additional}".strip
+ end
+
+ self.puts message
+ end
+
def self.puts_info *args
args ||= []
title = args[0]
@@ -110,14 +134,14 @@ module GTK
@once ||= {}
return if @once[id]
@once[id] = id
+ if !$gtk.cli_arguments[:replay] && !$gtk.cli_arguments[:record]
+ $gtk.notify!("Open the DragonRuby Console by pressing [`] [~] [²] [^] [º] or [§]. [Message ID: #{id}].")
+ end
write_to_log_and_puts ""
write_to_log_and_puts "#{message.strip}"
write_to_log_and_puts ""
write_to_log_and_puts "[Message ID: #{id}]"
write_to_log_and_puts ""
- return if $gtk.cli_arguments[:replay]
- return if $gtk.cli_arguments[:record]
- $gtk.notify!("One time notification occurred. [Message ID: #{id}] (Open console for more info.)")
end
def self.puts_once_info *ids, message
@@ -222,6 +246,10 @@ class Object
log_with_color XTERM_COLOR[:bright_white], *args
end
+ def log_error *args
+ GTK::Log.puts_error(*args)
+ end
+
def log_info *args
GTK::Log.puts_info(*args)
end
diff --git a/dragon/numeric.rb b/dragon/numeric.rb
index c2c461f..6d8b84d 100644
--- a/dragon/numeric.rb
+++ b/dragon/numeric.rb
@@ -28,12 +28,6 @@ class Numeric
clamp(0, 255).to_i
end
- # For a given number, the elapsed frames since that number is returned.
- # `Kernel.tick_count` is used to determine how many frames have elapsed.
- # An override numeric value can be passed in which will be used instead
- # of `Kernel.tick_count`.
- #
- # @gtk
def elapsed_time tick_count_override = nil
(tick_count_override || Kernel.tick_count) - self
end
@@ -51,25 +45,42 @@ class Numeric
# moment in time.
#
# @gtk
- def elapsed? offset, tick_count_override = nil
- (self + offset) < (tick_count_override || Kernel.tick_count)
+ def elapsed? offset = 0, tick_count_override = Kernel.tick_count
+ (self + offset) < tick_count_override
end
- # This is helpful for determining the index of frame-by-frame sprite animation.
- # The numeric value `self` represents the moment the animation started. `frame_index`
- # takes three additional parameters: how many frames exist in the sprite animation;
- # how long to hold each animation for; and whether the animation should repeat.
- #
- # @gtk
- def frame_index frame_count, hold_for, repeat, tick_count_override = nil
+ def frame_index *opts
+ frame_count_or_hash, hold_for, repeat, tick_count_override = opts
+ if frame_count_or_hash.is_a? Hash
+ frame_count = frame_count_or_hash[:count]
+ hold_for = frame_count_or_hash[:hold_for]
+ repeat = frame_count_or_hash[:repeat]
+ tick_count_override = frame_count_or_hash[:tick_count_override]
+ else
+ frame_count = frame_count_or_hash
+ end
+
+ tick_count_override ||= Kernel.tick_count
animation_frame_count = frame_count
animation_frame_hold_time = hold_for
animation_length = animation_frame_hold_time * animation_frame_count
- if !repeat && self.+(animation_length) < (tick_count_override || Kernel.tick_count).-(1)
+ return nil if Kernel.tick_count < self
+
+ if !repeat && (self + animation_length) < (tick_count_override - 1)
return nil
else
return self.elapsed_time.-(1).idiv(animation_frame_hold_time) % animation_frame_count
end
+ rescue Exception => e
+ raise <<-S
+* ERROR:
+#{opts}
+#{e}
+S
+ end
+
+ def zero?
+ self == 0
end
def zero
@@ -479,6 +490,11 @@ S
def serialize
self
end
+
+ def from_top
+ return 720 - self unless $gtk
+ $gtk.args.grid.h - self
+ end
end
class Fixnum
diff --git a/dragon/numeric_docs.rb b/dragon/numeric_docs.rb
new file mode 100644
index 0000000..fb8a94d
--- /dev/null
+++ b/dragon/numeric_docs.rb
@@ -0,0 +1,240 @@
+# coding: utf-8
+# Copyright 2019 DragonRuby LLC
+# MIT License
+# numeric_docs.rb has been released under MIT (*only this file*).
+
+module NumericDocs
+ def docs_method_sort_order
+ [
+ :docs_frame_index,
+ :docs_elapsed_time,
+ :docs_elapsed?,
+ :docs_new?
+ ]
+ end
+
+ def docs_frame_index
+ <<-S
+* DOCS: ~Numeric#frame_index~
+
+This function is helpful for determining the index of frame-by-frame
+ sprite animation. The numeric value ~self~ represents the moment the
+ animation started.
+
+~frame_index~ takes three additional parameters:
+
+- How many frames exist in the sprite animation.
+- How long to hold each animation for.
+- Whether the animation should repeat.
+
+~frame_index~ will return ~nil~ if the time for the animation is out
+of bounds of the parameter specification.
+
+Example using variables:
+
+#+begin_src ruby
+ def tick args
+ start_looping_at = 0
+ number_of_sprites = 6
+ number_of_frames_to_show_each_sprite = 4
+ does_sprite_loop = true
+
+ sprite_index =
+ start_looping_at.frame_index number_of_sprites,
+ number_of_frames_to_show_each_sprite,
+ does_sprite_loop
+
+ sprite_index ||= 0
+
+ args.outputs.sprites << [
+ 640 - 50,
+ 360 - 50,
+ 100,
+ 100,
+ "sprites/dragon-\#{sprite_index}.png"
+ ]
+ end
+#+end_src
+
+Example using named parameters:
+
+#+begin_src ruby
+ def tick args
+ start_looping_at = 0
+
+ sprite_index =
+ start_looping_at.frame_index count: 6,
+ hold_for: 4,
+ repeat: true,
+ tick_count_override: args.state.tick_count
+
+ sprite_index ||= 0
+
+ args.outputs.sprites << [
+ 640 - 50,
+ 360 - 50,
+ 100,
+ 100,
+ "sprites/dragon-\#{sprite_index}.png"
+ ]
+ end
+#+end_src
+
+S
+ end
+
+ def docs_new?
+ <<-S
+* DOCS: ~Numeric#created?~
+Returns true if ~Numeric#elapsed_time == 0~. Essentially communicating that
+number is equal to the current frame.
+
+Example usage:
+
+#+begin_src ruby
+ def tick args
+ args.state.box_queue ||= []
+
+ if args.state.box_queue.empty?
+ args.state.box_queue << { name: :red,
+ create_at: args.state.tick_count + 60 }
+ end
+
+ boxes_to_spawn_this_frame = args.state
+ .box_queue
+ .find_all { |b| b[:create_at].new? }
+
+ boxes_to_spawn_this_frame.each { |b| puts "box \#{b} was new? on \#{args.state.tick_count}." }
+
+ args.state.box_queue -= boxes_to_spawn_this_frame
+ end
+#+end_src
+S
+ end
+
+ def docs_elapsed?
+ <<-S
+* DOCS: ~Numeric#elapsed?~
+Returns true if ~Numeric#elapsed_time~ is greater than the number. An optional parameter can be
+passed into ~elapsed?~ which is added to the number before evaluating whether ~elapsed?~ is true.
+
+Example usage (no optional parameter):
+
+#+begin_src ruby
+ def tick args
+ args.state.box_queue ||= []
+
+ if args.state.box_queue.empty?
+ args.state.box_queue << { name: :red,
+ destroy_at: args.state.tick_count + 60 }
+ args.state.box_queue << { name: :green,
+ destroy_at: args.state.tick_count + 60 }
+ args.state.box_queue << { name: :blue,
+ destroy_at: args.state.tick_count + 120 }
+ end
+
+ boxes_to_destroy = args.state
+ .box_queue
+ .find_all { |b| b[:destroy_at].elapsed? }
+
+ if !boxes_to_destroy.empty?
+ puts "boxes to destroy count: \#{boxes_to_destroy.length}"
+ end
+
+ boxes_to_destroy.each { |b| puts "box \#{b} was elapsed? on \#{args.state.tick_count}." }
+
+ args.state.box_queue -= boxes_to_destroy
+ end
+#+end_src
+
+Example usage (with optional parameter):
+
+#+begin_src ruby
+ def tick args
+ args.state.box_queue ||= []
+
+ if args.state.box_queue.empty?
+ args.state.box_queue << { name: :red,
+ create_at: args.state.tick_count + 120,
+ lifespan: 60 }
+ args.state.box_queue << { name: :green,
+ create_at: args.state.tick_count + 120,
+ lifespan: 60 }
+ args.state.box_queue << { name: :blue,
+ create_at: args.state.tick_count + 120,
+ lifespan: 120 }
+ end
+
+ # lifespan is passed in as a parameter to ~elapsed?~
+ boxes_to_destroy = args.state
+ .box_queue
+ .find_all { |b| b[:create_at].elapsed? b[:lifespan] }
+
+ if !boxes_to_destroy.empty?
+ puts "boxes to destroy count: \#{boxes_to_destroy.length}"
+ end
+
+ boxes_to_destroy.each { |b| puts "box \#{b} was elapsed? on \#{args.state.tick_count}." }
+
+ args.state.box_queue -= boxes_to_destroy
+ end
+#+end_src
+
+S
+ end
+
+ def docs_elapsed_time
+ <<-S
+* DOCS: ~Numeric#elapsed_time~
+For a given number, the elapsed frames since that number is returned.
+`Kernel.tick_count` is used to determine how many frames have elapsed.
+An optional numeric argument can be passed in which will be used instead
+of `Kernel.tick_count`.
+
+Here is an example of how elapsed_time can be used.
+
+#+begin_src ruby
+ def tick args
+ args.state.last_click_at ||= 0
+
+ # record when a mouse click occurs
+ if args.inputs.mouse.click
+ args.state.last_click_at = args.state.tick_count
+ end
+
+ # Use Numeric#elapsed_time to determine how long it's been
+ if args.state.last_click_at.elapsed_time > 120
+ args.outputs.labels << [10, 710, "It has been over 2 seconds since the mouse was clicked."]
+ end
+ end
+#+end_src
+
+And here is an example where the override parameter is passed in:
+
+#+begin_src ruby
+ def tick args
+ args.state.last_click_at ||= 0
+
+ # create a state variable that tracks time at half the speed of args.state.tick_count
+ args.state.simulation_tick = args.state.tick_count.idiv 2
+
+ # record when a mouse click occurs
+ if args.inputs.mouse.click
+ args.state.last_click_at = args.state.simulation_tick
+ end
+
+ # Use Numeric#elapsed_time to determine how long it's been
+ if (args.state.last_click_at.elapsed_time args.state.simulation_tick) > 120
+ args.outputs.labels << [10, 710, "It has been over 4 seconds since the mouse was clicked."]
+ end
+ end
+#+end_src
+
+S
+ end
+end
+
+class Numeric
+ extend Docs
+ extend NumericDocs
+end
diff --git a/dragon/outputs_docs.rb b/dragon/outputs_docs.rb
index 1e8eecc..16c97ec 100644
--- a/dragon/outputs_docs.rb
+++ b/dragon/outputs_docs.rb
@@ -4,6 +4,14 @@
# outputs_docs.rb has been released under MIT (*only this file*).
module OutputsDocs
+ def docs_method_sort_order
+ [
+ :docs_class,
+ :docs_solids,
+ :docs_borders
+ ]
+ end
+
def docs_class
<<-S
* DOCS: ~GTK::Outputs~
diff --git a/dragon/readme_docs.rb b/dragon/readme_docs.rb
index 870a5a1..f4d3696 100644
--- a/dragon/readme_docs.rb
+++ b/dragon/readme_docs.rb
@@ -240,13 +240,16 @@ Here's a fun Ruby thing: ~args.state.rotation ||= 0~ is shorthand for
nice way to embed your initialization code right next to where you
need the variable.
-~args.state~ is a place you can hang your own data and have it survive
-past the life of the function call. In this case, the current rotation
-of our sprite, which is happily spinning at 60 frames per second. If
-you don't specify rotation (or alpha, or color modulation, or a source
-rectangle, etc), DragonRuby picks a reasonable default, and the array
-is ordered by the most likely things you need to tell us: position,
-size, name.
+
+~args.state~ is a place you can hang your own data. It's an open data
+structure that allows you to define properties that are arbitrarily
+nested. You don't need to define any kind of class.
+
+In this case, the current rotation of our sprite, which is happily
+spinning at 60 frames per second. If you don't specify rotation (or
+alpha, or color modulation, or a source rectangle, etc), DragonRuby
+picks a reasonable default, and the array is ordered by the most
+likely things you need to tell us: position, size, name.
** There Is No Delta Time
@@ -335,79 +338,43 @@ around. Experiment a little. Add a few more things and have them
interact in small ways. Want something to go away? Just don't add it
to ~args.output~ anymore.
-** IMPORTANT: Go Through All Of The Sample Apps! Study Them Thoroughly!!
+* IMPORTANT: Go through all of the sample apps! Study them thoroughly!! No really, you should definitely do this!
Now that you've completed the Hello World tutorial. Head over to the
`samples` directory. It is very very important that you study the
sample apps thoroughly! Go through them in order. Here is a short
description of each sample app.
-1. 00_beginner_ruby_primer: This is an interactive tutorial that shows how to render ~solid~s, animated ~sprite~s, ~label~s.
-2. 00_intermediate_ruby_primer: This is a set of sample Ruby snippets that give you a high level introduction to the programming language.
-3. 01_api_01_labels: Various ways to render ~label~s.
-4. 01_api_02_lines: Various ways to render ~line~s.
-5. 01_api_03_rects: Sample app shows various ways to render ~solid~s and ~border~s.
-6. 01_api_04_sprites: Sample app shows various ways to render ~sprite~s.
-7. 01_api_05_keyboard: Hows how to get keyboard input from the user.
-8. 01_api_06_mouse: Hows how to get mouse mouse position.
-9. 01_api_07_point_to_rect: How to get mouse input from the user and shows collision/hit detection.
-10. 01_api_08_rect_to_rect: Hit detection/collision between two rectangles.
-11. 01_api_10_controller: Interaction with a USB/Bluetooth controller.
-12. 01_api_99_tech_demo: All the different render primitives along with using ~render_targets~.
-13. 02_collision_01_simple: Collision detection with dynamically moving bodies.
-14. 02_collision_02_moving_objects: Collision detection between many primitives, simple platformer physics, and keyboard input.
-15. 02_collision_03_entities: Collision with entities and serves as a small introduction to ECS (entity component system).
-16. 02_collision_04_ramp_with_debugging: How ramp trajectory can be calculated.
-17. 02_collision_05_ramp_with_debugging_two: How ramp trajectory can be calculated.
-18. 02_sprite_animation_and_keyboard_input: How to animate a sprite based off of keyboard input.
-19. 03_mouse_click: How to determine what direction/vector a mouse was clicked relative to a player.
-20. 04_sounds: How to play sounds and work with buttons.
-21. 05_mouse_move: How to determine what direction/vector a mouse was clicked relative to a player.
-22. 05_mouse_move_paint_app: Represents a simple paint app.
-23. 05_mouse_move_tile_editor: A starting point for a tile editor.
-24. 06_coordinate_systems: Shows the two origin systems within Game Toolkit where the origin is in the center and where the origin is at the bottom left.
-25. 07_render_targets: Shows a powerful concept called ~render_target~s. You can use this to programatically create sprites (it's also useful for representing parts of a scene as if it was a view port/camera).
-26. 07_render_targets_advanced: Advanced usage of ~render_target~s.
-27. 08_platformer_collisions: Axis aligned collision along with platformer physics.
-28. 08_platformer_collisions_metroidvania: How to save map data and place sprites live within a game.
-29. 08_platformer_jumping_inertia: Jump physics and how inertia affects collision.
-30. 09_controller_analog_usage_advanced_sprites: Extended properties of a ~sprite~ and how to change the rotation anchor point and render a subset/tile of a sprite.
-31. 09_sprite_animation_using_tile_sheet: How to perform sprite animates using a tile sheet.
-32. 10_save_load_game: Save and load game data.
-33. 11_coersion_of_primitives: How primitives of one specific type can be rendered as another primitive type.
-34. 11_hash_primitives: How primitives can be represented using a ~Hash~.
-35. 12_controller_input_sprite_sheet_animations: How to leverage vectors to move a player around the screen.
-36. 12_top_down_area: How to render a top down map and how to manage collision of a player.
-37. 13_01_easing_functions: How to use lerping functions to define animations/movement.
-38. 13_02_cubic_bezier: How to create a bezier curve using lines.
-39. 13_03_easing_using_spline: How a collection of bezier curves can be used to define an animation.
-40. 13_04_parametric_enemy_movement: How to define the movement of enemies and projectiles using lerping/parametric functions.
-41. 14_sprite_limits: Upper limit for how many sprites can be rendered to the screen.
-42. 14_sprite_limits_static_references: Upper limit for how many sprites can be rendered to the screen using ~static~ output collections (which are updated by reference as opposed to by value).
-43. 15_collision_limits: How many collisions can be processed across many primitives.
-44. 18_moddable_game: How you can make a game where content is authored by the player (modding support).
-45. 19_lowrez_jam: How to use ~render_targets~ to create a low resolution game.
-46. 20_roguelike_starting_point: A starting point for a roguelike and explores concepts such as line of sight.
-47. 20_roguelike_starting_point_two: A starting point for a roguelike where sprites are provided from a tile map/tile sheet.
-48. 21_mailbox_usage: How to do interprocess communication.
-49. 22_trace_debugging: Debugging techniques and tracing execution through your game.
-50. 22_trace_debugging_classes: Debugging techniques and tracing execution through your game.
-51. 23_hexagonal_grid: How to make a tactical grid/map made of hexagons.
-52. 23_isometric_grid: How to make a tactical grid/map made of isometric sprites.
-53. 24_http_example: How to make http requests.
-54. 25_3d_experiment_01_square: How to create 3D objects.
-55. 26_jam_craft: Starting point for crafting game. It also shows how to customize the mouse cursor.
-56. 99_sample_game_basic_gorillas: Reference implementation of a full game. Topics covered: physics, keyboard input, collision, sprite animation.
-57. 99_sample_game_clepto_frog: Reference implementation of a full game. Topics covered: camera control, spring/rope physics, scene orchestration.
-58. 99_sample_game_dueling_starships: Reference implementation that shows local multiplayer. Topics covered: vectors, particles, friction, inertia.
-59. 99_sample_game_flappy_dragon: Reference implementation that is a clone of Flappy Bird. Topics covered: scene orchestration, collision, sound, sprite animations, lerping.
-60. 99_sample_game_pong: Reference implementation of pong.
-61. 99_sample_game_return_of_serenity: Reference implementation of low resolution story based game.
-62. 99_sample_game_the_little_probe: Reference implementation of a full game. Topics covered: Arbitrary collision detection, loading map data, bounce/ball physics.
-63. 99_sample_nddnug_workshop: Reference implementation of a full game. Topics covered: vectors, controller input, sound, trig functions.
-64. 99_sample_snakemoji: Shows that Ruby supports coding with emojis.
-65. 99_zz_gtk_unit_tests: A collection of unit tests that exercise parts of DragonRuby's API.
-
+** Guided Samples
+
+1. ~samples/00_learn_ruby_optional~: This directory contains sample apps that will help you learn the language.
+2. ~samples/01_rendering_basics~: This set of samples will show you how to render basic primitives such as ~labels~, ~solids~, ~borders~, ~lines~, ~sprites~, and how to play ~sounds~.
+3. ~samples/02_input_basics~: This set of samples show you how to accept input from the ~mouse~, ~keyboard~, and ~controllers~.
+4. ~samples/03_rendering_sprites~: This set of samples shows you all the different ways to render sprites (including how to use a sprite sheet).
+4. ~samples/04_physics_and_collision~: This set of samples shows how to do various types of collisions and physics.
+5. ~samples/05_mouse~: This set of samples show more advanced usages of the mouse.
+6. ~samples/06_save_load~: This set of samples show how to save and load game data.
+7. ~samples/07_advanced_rendering~: This set of samples show how to programmatically render sprites using render targets.
+8. ~samples/08_tweening_lerping_easing_functions~: This set of samples show how to perform animations.
+9. ~samples/09_performance~: This set of samples show how to handle performance issues when a large number of sprites on the screen.
+10. ~samples/10_advanced_debugging~: This set of samples show how advanced debugging techniques and testing techniques.
+11. ~samples/11_http~: This set of samples show how use http.
+
+** Sample Games
+
+There are samples that contain the path ~samples/99_*~. The sample apps that are prefixed with ~99_~ show non-trivial implemementations for a real game:
+
+1. 3D Cube: Shows how to do faux 3D in DragonRuby.
+2. Dueling Starships: A two player top-down versus game where each player controls a ship.
+3. Flappy Dragon: DragonRuby's clone of Flappy Bird.
+4. Pong: A simple implementation of the game Pong.
+5. Snakemoji: The classic game of Snake but with all of the code written using emojis (sometimes you just have to have a little fun).
+6. Solar System: A simulation of our solar system.
+7. Crafting Starting Point: A starting point for those that want to build a crafting game.
+8. Dev Tools: A set of sample apps that show how you can extend DragonRuby's Console, starting point for a tile editor, and a starting point for a paint app.
+9. LOWREZ: Sample apps that show how to render at different resolutions.
+10. RPG: Various sample apps that show how to create narrative, topdown, tactical grid-based, and roguelike RPGs.
+11. Platformers: Various sample apps that show how to create different kinds of physics/collision based platformers.
S
end
@@ -441,11 +408,11 @@ make it look like this:
NOTE: Remove the ~#~ at the beginning of each line.
#+begin_src
-devid=bob
-devtitle=Bob The Game Developer
-gameid=mygame
-gametitle=My Game
-version=0.1
+ devid=bob
+ devtitle=Bob The Game Developer
+ gameid=mygame
+ gametitle=My Game
+ version=0.1
#+end_src
The ~devid~ property is the username you use to log into Itch.io.
@@ -459,7 +426,7 @@ The ~version~ can be any ~major.minor~ number format.
Open up the terminal and run this from the command line:
#+begin_src
-./dragonruby-publish --only-package mygame
+ ./dragonruby-publish --only-package mygame
#+end_src
(if you're on Windows, don't put the "./" on the front. That's a Mac and
@@ -474,7 +441,7 @@ For the HTML version of your game after you upload it. Check the checkbox labele
For subsequent updates you can use an automated deployment to Itch.io:
#+begin_src
-./dragonruby-publish mygame
+ ./dragonruby-publish mygame
#+end_src
DragonRuby will package _and publish_ your game to itch.io! Tell your
@@ -487,7 +454,7 @@ S
def docs_dragonruby_philosophy
<<-S
-** DragonRuby's Philosophy
+* DragonRuby's Philosophy
The following tenants of DragonRuby are what set us apart from other
game engines. Given that Game Toolkit is a relatively new engine,
@@ -495,7 +462,7 @@ there are definitely features that are missing. So having a big check
list of "all the cool things" is not this engine's forte. This is
compensated with a strong commitment to the following principals.
-*** Challenge The Status Quo
+** Challenge The Status Quo
Game engines of today are in a local maximum and don't take into
consideration the challenges of this day and age. Unity and GameMaker
@@ -509,7 +476,22 @@ It's a hard pill to swallow, but forget blindly accepted best
practices and try to figure out the underlying motivation for a
specific approach to game development. Collaborate with us.
-*** Release Often And Quickly
+** Continuity of Design
+
+There is a programming idiom in software called "the pit of
+success". The term normalizes up front pain as a necessity in the
+(hopes that the investment will yield dividends "when you become
+successful"). This results in more "Enterprise TM" code upfront, and
+makes it more difficult to get started when you are new to programming.
+
+DragonRuby's philosophy is to provide a spectrum across the "make it
+fast" vs "make it right" spectrum and provide incremental, intuitive
+transitions between points on that spectrum. This is captured in how
+render primitives can be represented as tuples/arrays, hashes, open
+structs/entities, and then finally classes (as opposed to forcing devs
+to use classes upfront).
+
+** Release Often And Soon
The biggest mistake game devs make is spending too much time in
isolation building their game. Release something, however small, and
@@ -526,7 +508,7 @@ Remember:
Real artists ship.
#+end_quote
-*** Sustainable And Ethical Monetization
+** Sustainable And Ethical Monetization
We all aspire to put food on the table doing what we love. Whether it
is building games, writing tools to support game development, or
@@ -536,7 +518,7 @@ Charge a fair amount of money for the things you create. It's expected
and encouraged within the community. Give what you create away for
free to those that can't afford it.
-*** Sustainable And Ethical Open Source
+** Sustainable And Ethical Open Source
This goes hand in hand with sustainable and ethical monetization. The
current state of open source is not sustainable. There is an immense
@@ -547,7 +529,7 @@ still trying to figure out the best solution).
So, don't be "that guy" in the Discord that says "DragonRuby should be
free and open source!" You will be personally flogged by Amir.
-*** People Over Entities
+** People Over Entities
We prioritize the endorsement of real people over faceless
entities. This game engine, and other products we create, are not
@@ -555,13 +537,13 @@ insignificant line items of a large company. And you aren't a generic
"commodity" or "corporate resource". So be active in the community
Discord and you'll reap the benefits as more devs use DragonRuby.
-*** Building A Game Should Be Fun And Bring Happiness
+** Building A Game Should Be Fun And Bring Happiness
We will prioritize the removal of pain. The aesthetics of Ruby make it
such a joy to work with, and we want to capture that within the
engine.
-*** Real World Application Drives Features
+** Real World Application Drives Features
We are bombarded by marketing speak day in and day out. We don't do
that here. There are things that are really great in the engine, and
@@ -879,25 +861,25 @@ sure_ you've initialized a default value.
#+begin_src
def tick args
# initialize your game state ONCE
- args.player.x ||= 0
- args.player.y ||= 0
- args.player.hp ||= 100
+ args.state.player.x ||= 0
+ args.state.player.y ||= 0
+ args.state.player.hp ||= 100
# increment the x position of the character by one every frame
- args.player.x += 1
+ args.state.player.x += 1
# Render a sprite with a label above the sprite
args.outputs.sprites << [
- args.player.x,
- args.player.y,
+ args.state.player.x,
+ args.state.player.y,
32, 32,
"player.png"
]
args.outputs.labels << [
- args.player.x,
- args.player.y - 50,
- args.player.hp
+ args.state.player.x,
+ args.state.player.y - 50,
+ args.state.player.hp
]
end
#+end_src
@@ -956,11 +938,9 @@ Under DragonRuby LLP, we offer a number of products (with more on the
way):
- Game Toolkit (GTK): A 2D game engine that is compatible with modern
- gaming platforms. [Home Page]() [FAQ Page]()
+ gaming platforms.
- RubyMotion (RM): A compiler toolchain that allows you to build native, cross-platform mobile
- apps. [Home Page]() [FAQ Page]()
-- Commandline Toolkit (CTK): A zero dependency, zero installation Ruby
- environment that works on Windows, Mac, and Linux. [Home Page]() [FAQ Page]()
+ apps. [[http://rubymotion.com]]
All of the products above leverage a shared core called DragonRuby.
@@ -979,7 +959,7 @@ identifier huh?
The response to this question requires a few subparts. First we need
to clarify some terms. Specifically _language specification_ vs _runtime_.
-*** Okay... so what is the difference between a language specification and a runtime?
+**** Okay... so what is the difference between a language specification and a runtime?
A runtime is an _implementation_ of a language specification. When
people say "Ruby," they are usually referring to "the Ruby 3.0+ language
@@ -988,13 +968,13 @@ specification implemented via the CRuby/MRI Runtime."
But, there are many Ruby Runtimes: CRuby/MRI, JRuby, Truffle, Rubinius, Artichoke,
and (last but certainly not least) DragonRuby.
-*** Okay... what language specification does DragonRuby use then?
+**** Okay... what language specification does DragonRuby use then?
DragonRuby's goal is to be compliant with the ISO/IEC 30170:2012 standard. It's
syntax is Ruby 2.x compatible, but also contains semantic changes that help
it natively interface with platform specific libraries.
-*** So... why another runtime?
+**** So... why another runtime?
The elevator pitch is:
@@ -1003,7 +983,7 @@ within the runtime allows us to target platforms no other Ruby can
target: PC, Mac, Linux, Raspberry Pi, WASM, iOS, Android, Nintendo
Switch, PS4, Xbox, and Scadia.
-*** What does Multilevel Cross-platform mean?
+**** What does Multilevel Cross-platform mean?
There are complexities associated with targeting all the platforms we
support. Because of this, the runtime had to be architected in such a
@@ -1040,13 +1020,87 @@ implementations of Ruby; provide fast, native code execution
on proprietary platforms; ensure good separation between these two
worlds; and provides a means to add new platforms without going insane.
-*** Cool cool. So given that I understand everything to this point, can we answer the original question? What is DragonRuby?
+**** Cool cool. So given that I understand everything to this point, can we answer the original question? What is DragonRuby?
DragonRuby is a Ruby runtime implementation that takes all the lessons
we've learned from MRI/CRuby, and merges it with the latest and greatest
compiler and OSS technologies.
-** Frequent Comments
+*** How is DragonRuby different than MRI?
+
+DragonRuby supports a subset of MRI apis. Our target is to support all
+of mRuby's standard lib. There are challenges to this given the number
+of platforms we are trying to support (specifically console).
+
+**** Does DragonRuby support Gems?
+
+DragonRuby does not support gems because that requires the
+installation of MRI Ruby on the developer's machine (which is a
+non-starter given that we want DragonRuby to be a zero dependency
+runtime). While this seems easy for Mac and Linux, it is much harder
+on Windows and Raspberry Pi. mRuby has taken the approach of having a
+git repository for compatible gems and we will most likely follow
+suite: [[https://github.com/mruby/mgem-list]].
+
+**** Does DragonRuby have a REPL/IRB?
+
+You can use DragonRuby's Console within the game to inspect object and
+execute small pieces of code. For more complex pieces of code create a
+file called ~repl.rb~ and put it in ~mygame/app/repl.rb~:
+
+- Any code you write in there will be executed when you change the file. You can organize different pieces of code using the ~repl~ method:
+
+#+begin_src ruby
+ repl do
+ puts "hello world"
+ puts 1 + 1
+ end
+#+end_src
+
+- If you use the `repl` method, the code will be executed and the DragonRuby Console will automatically open so you can see the results (on Mac and Linux, the results will also be printed to the terminal).
+
+- All ~puts~ statements will also be saved to ~logs/log.txt~. So if you want to stay in your editor and not look at the terminal, or the DragonRuby Console, you can ~tail~ this file.
+
+4. To ignore code in ~repl.rb~, instead of commenting it out, prefix ~repl~ with the letter ~x~ and it'll be ignored.
+
+#+begin_src ruby
+ xrepl do # <------- line is prefixed with an "x"
+ puts "hello world"
+ puts 1 + 1
+ end
+
+ # This code will be executed when you save the file.
+ repl do
+ puts "Hello"
+ end
+
+ repl do
+ puts "This code will also be executed."
+ end
+
+ # use xrepl to "comment out" code
+ xrepl do
+ puts "This code will not be executed because of the x infront of repl".
+ end
+#+end_src
+
+**** Does DragonRuby support ~pry~ or have any other debugging facilities?
+
+~pry~ is a gem that assumes you are using the MRI Runtime (which is
+incompatible with DragonRuby). Eventually DragonRuby will have a pry
+based experience that is compatible with a debugging infrastructure
+called LLDB. Take the time to read about LLDB as it shows the
+challenges in creating something that is compatible.
+
+You can use DragonRuby's replay capabilities to troubleshoot:
+
+1. DragonRuby is hot loaded which gives you a very fast feedback loop (if the game throws an exception, it's because of the code you just added).
+2. Use ~./dragonruby mygame --record~ to create a game play recording that you can use to find the exception (you can replay a recoding by executing ~./dragonruby mygame --replay last_replay.txt~ or through the DragonRuby Console using ~$gtk.recording.start_replay "last_replay.txt"~.
+3. DragonRuby also ships with a unit testing facility. You can invoke the following command to run a test: ~./dragonruby . --eval some_ruby_file.rb --no-tick~.
+4. Get into the habit of adding debugging facilities within the game itself. You can add drawing primitives to ~args.outputs.debug~ that will render on top of your game but will be ignored in a production release.
+5. Debugging something that runs at 60fps is (imo) not that helpful. The exception you are seeing could have been because of a change that occurred many frames ago.
+
+** Frequent Comments About Ruby as a Language Choice
*** But Ruby is dead.
@@ -1098,6 +1152,8 @@ If you have ideas on how we can do this, email us!
If the reason above isn't sufficient, then definitely use something else.
+All this being said, we do have parts of the engine open sourced on GitHub: [[https://github.com/dragonruby/dragonruby-game-toolkit-contrib/]]
+
*** DragonRuby is for pay. You should offer a free version.
If you can afford to pay for DragonRuby, you should (and will). We don't go
diff --git a/dragon/runtime_docs.rb b/dragon/runtime_docs.rb
index 09759b4..50734ce 100644
--- a/dragon/runtime_docs.rb
+++ b/dragon/runtime_docs.rb
@@ -31,6 +31,23 @@ This function returns the width and height of a string.
#+end_src
S
end
+
+ def docs_write_file
+ <<-S
+* DOCS: ~GTK::Runtime#write_file~
+This function takes in two parameters. The first paramter is the file path and assumes the the game
+directory is the root. The second parameter is the string that will be written. The method overwrites whatever
+is currently in the file. Use ~GTK::Runtime#append_file~ to append to the file as opposed to overwriting.
+
+#+begin_src ruby
+ def tick args
+ if args.inputs.mouse.click
+ args.gtk.write_file "last-mouse-click.txt", "Mouse was clicked at \#{args.state.tick_count}."
+ end
+ end
+#+end_src
+S
+ end
end
class GTK::Runtime
diff --git a/dragon/string.rb b/dragon/string.rb
index 8d2d9ba..5fa273c 100644
--- a/dragon/string.rb
+++ b/dragon/string.rb
@@ -38,6 +38,7 @@ S
self[0..-2]
end
+ # @gtk
def wrapped_lines length
self.each_line.map do |l|
l = l.rstrip
@@ -50,20 +51,22 @@ S
end.flatten
end
+ # @gtk
def wrap length
wrapped_lines(length).join.rstrip
end
+ # @gtk
def multiline?
include? "\n"
end
- def indent_lines amount
+ def indent_lines amount, char = " "
self.each_line.each_with_index.map do |l, i|
if i == 0
l
else
- " " * amount + l
+ char * amount + l
end
end.join
end
diff --git a/dragon/tests.rb b/dragon/tests.rb
index 5cda2fe..7cbba09 100644
--- a/dragon/tests.rb
+++ b/dragon/tests.rb
@@ -16,7 +16,6 @@ module GTK
def run_test m
args = Args.new $gtk, nil
assert = Assert.new
- setup(args) if respond_to?(:setup)
begin
log_test_running m
send(m, args, assert)
@@ -32,7 +31,6 @@ module GTK
mark_test_failed m, e
end
end
- teardown if respond_to?(:teardown)
end
def test_methods_focused
@@ -43,6 +41,7 @@ module GTK
Object.methods.find_all { |m| m.start_with? "test_" }
end
+ # @gtk
def start
log "* TEST: gtk.test.start has been invoked."
if test_methods_focused.length != 0
@@ -73,10 +72,6 @@ module GTK
def log_inconclusive m
self.inconclusive << {m: m}
log "Inconclusive."
- log_once :assertion_ok_note, <<-S
-NOTE FOR INCONCLUSIVE TESTS: No assertion was performed in the test.
-Add assert.ok! at the end of the test if you are using your own assertions.
-S
end
def log_passed m
@@ -125,6 +120,12 @@ S
log "#{self.passed.length} test(s) passed."
self.passed.each { |h| log "**** :#{h[:m]}" }
log "*** Inconclusive"
+ if self.inconclusive.length > 0
+ log_once :assertion_ok_note, <<-S
+NOTE FOR INCONCLUSIVE TESTS: No assertion was performed in the test.
+Add assert.ok! at the end of the test if you are using your own assertions.
+S
+ end
log "#{self.inconclusive.length} test(s) inconclusive."
self.inconclusive.each { |h| log "**** :#{h[:m]}" }
log "*** Failed"
diff --git a/dragon/trace.rb b/dragon/trace.rb
index d038ce5..3d92ba2 100644
--- a/dragon/trace.rb
+++ b/dragon/trace.rb
@@ -65,7 +65,7 @@ module GTK
@traced_classes.clear
$trace_enabled = false
if !$gtk.production
- $gtk.write_file 'logs/trace.txt', "Add trace!(SOMEOBJECT) to the top of `tick` and this file will be populated with invocation information.\n"
+ $gtk.write_file_root 'logs/trace.txt', "Add trace!(SOMEOBJECT) to the top of ~tick~ and this file will be populated with invocation information.\n"
end
end
@@ -96,14 +96,15 @@ module GTK
if $trace_puts.length > 0
text = $trace_puts.join("").strip + "\n" + self.trace_times_string + "\n"
if pad_with_newline
- $gtk.append_file 'logs/trace.txt', "\n" + text
+ $gtk.append_file_root 'logs/trace.txt', "\n" + text.strip
else
- $gtk.append_file 'logs/trace.txt', text
+ $gtk.append_file_root 'logs/trace.txt', text.strip
end
end
$trace_puts.clear
end
+ # @gtk
def self.trace! instance = nil
$trace_history ||= []
$trace_enabled = true