├── Rakefile ├── Gemfile ├── lib ├── hasu │ ├── version.rb │ ├── window.rb │ └── guard.rb └── hasu.rb ├── todo ├── examples ├── chingu │ ├── Gemfile │ ├── game.rb │ └── ball.rb └── gosu │ ├── rect.rb │ └── example.rb ├── .gitignore ├── hasu.gemspec ├── LICENSE.txt └── README.md /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /lib/hasu/version.rb: -------------------------------------------------------------------------------- 1 | module Hasu 2 | VERSION = "0.1.7" 3 | end 4 | -------------------------------------------------------------------------------- /todo: -------------------------------------------------------------------------------- 1 | guard "macro" 2 | dead code removal 3 | autoload? 4 | constant reloading? -------------------------------------------------------------------------------- /examples/chingu/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "hasu", :path => "../../" 4 | gem "chingu" 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | -------------------------------------------------------------------------------- /examples/chingu/game.rb: -------------------------------------------------------------------------------- 1 | require "chingu" 2 | require "hasu" 3 | 4 | Hasu.load "ball.rb" 5 | 6 | class Game < Chingu::Window 7 | prepend Hasu::Guard 8 | 9 | def initialize 10 | super(640, 480, false) 11 | reset 12 | end 13 | 14 | def reset 15 | Ball.create 16 | Ball.create 17 | end 18 | end 19 | 20 | Game.new.show 21 | -------------------------------------------------------------------------------- /examples/gosu/rect.rb: -------------------------------------------------------------------------------- 1 | class Rect 2 | attr_reader :x1, :x2, :y1, :y2 3 | 4 | def initialize(x1, x2, y1, y2) 5 | @x1 = x1 6 | @x2 = x2 7 | @y1 = y1 8 | @y2 = y2 9 | end 10 | 11 | def color 12 | Gosu::Color::WHITE 13 | end 14 | 15 | def draw(window) 16 | window.draw_quad(x1, y1, color, x2, y1, color, x2, y2, color, x1, y2, color) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /examples/gosu/example.rb: -------------------------------------------------------------------------------- 1 | require 'gosu' 2 | $:.unshift '../lib' 3 | require 'hasu' 4 | Hasu.load 'rect.rb' 5 | 6 | class Example < Gosu::Window 7 | prepend Hasu 8 | 9 | def initialize 10 | super(640, 480, false) 11 | end 12 | 13 | def reset 14 | @rect = Rect.new(10, 100, 10, 100) 15 | end 16 | 17 | def update 18 | end 19 | 20 | def draw 21 | @rect.draw(self) 22 | end 23 | end 24 | 25 | Hasu.run(Example) 26 | -------------------------------------------------------------------------------- /examples/chingu/ball.rb: -------------------------------------------------------------------------------- 1 | class Ball < Chingu::GameObject 2 | SIZE = 20 3 | 4 | def initialize(*) 5 | super 6 | 7 | @x = 320 8 | @y = 240 9 | 10 | @dx = rand * 10 11 | @dy = rand * 10 12 | end 13 | 14 | def update 15 | @x += @dx 16 | if @x < 0 || @x > 640 17 | @dx *= -1 18 | end 19 | 20 | @y += @dy 21 | if @y < 0 || @y > 480 22 | @dy *= -1 23 | end 24 | end 25 | 26 | def draw 27 | rect = Chingu::Rect.new(@x - SIZE/2, @y - SIZE/2, SIZE, SIZE) 28 | $window.draw_rect(rect, Gosu::Color::RED, 0) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/hasu/window.rb: -------------------------------------------------------------------------------- 1 | require "gosu" 2 | require "hasu/guard" 3 | 4 | module Hasu 5 | class Window < Gosu::Window 6 | def self.inherited(other) 7 | includer = caller.first.split(":").first 8 | Hasu.reloads[includer] = File.mtime(includer) 9 | if other.respond_to?(:prepend, true) 10 | other.send(:prepend, Hasu::Guard) 11 | else 12 | warn "Most of Hasu's nifty features (e.g. error catching, file reloading) are only available on Ruby >= 2.0." 13 | end 14 | end 15 | 16 | def initialize(*) 17 | super 18 | reset unless Hasu.error 19 | end 20 | 21 | def self.run(*args) 22 | unless @running 23 | @running = true 24 | new(*args).show 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/hasu.rb: -------------------------------------------------------------------------------- 1 | require "hasu/version" 2 | require "hasu/window" 3 | 4 | module Hasu 5 | def self.reloads 6 | @files ||= {} 7 | end 8 | 9 | def self.load(path) 10 | reloads[path] = File.exist?(path) ? File.mtime(path) : Time.now 11 | begin 12 | super 13 | true 14 | rescue Exception => e 15 | Hasu.error = e 16 | false 17 | end 18 | end 19 | 20 | def self.error 21 | @error 22 | end 23 | 24 | def self.error=(error) 25 | @error = error 26 | 27 | if @error 28 | $stderr.puts @error.inspect 29 | $stderr.puts @error.backtrace.join("\n") 30 | end 31 | end 32 | 33 | def self.reload! 34 | to_reload = reloads.select{|f,t| File.exist?(f) && File.mtime(f) > t} 35 | 36 | !to_reload.empty? && to_reload.all? do |file,_| 37 | puts "Reloading #{file}" 38 | load(file) 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /hasu.gemspec: -------------------------------------------------------------------------------- 1 | # coding: utf-8 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'hasu/version' 5 | 6 | Gem::Specification.new do |spec| 7 | spec.name = "hasu" 8 | spec.version = Hasu::VERSION 9 | spec.authors = ["Michael Fairley"] 10 | spec.email = ["michaelfairley@gmail.com"] 11 | spec.description = %q{Prototype Gosu games with ease} 12 | spec.summary = %q{Prototype Gosu games with ease} 13 | spec.homepage = "https://github.com/michaelfairley/hasu" 14 | spec.license = "MIT" 15 | 16 | spec.files = `git ls-files`.split($/) 17 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 18 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 19 | spec.require_paths = ["lib"] 20 | 21 | spec.add_dependency "gosu" 22 | 23 | spec.add_development_dependency "bundler", "~> 1.3" 24 | spec.add_development_dependency "rake" 25 | end 26 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Michael Fairley 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /lib/hasu/guard.rb: -------------------------------------------------------------------------------- 1 | module Hasu 2 | module Guard 3 | def update(*) 4 | if Hasu.reload! 5 | Hasu.error = nil 6 | end 7 | unless Hasu.error 8 | super 9 | end 10 | rescue => e 11 | Hasu.error = e 12 | end 13 | 14 | def reset 15 | super if defined?(super) 16 | Hasu.error = nil 17 | rescue => e 18 | Hasu.error = e 19 | end 20 | 21 | def draw(*) 22 | if Hasu.error 23 | ([Hasu.error.inspect] + Hasu.error.backtrace).each_with_index do |line, i| 24 | _hasu_font.draw_text(line.gsub("\n",''), 10, 10 + i * 16, 0) 25 | end 26 | else 27 | begin 28 | super 29 | rescue => e 30 | Hasu.error = e 31 | draw 32 | end 33 | end 34 | end 35 | 36 | def _hasu_font 37 | @_hasu_font ||= Gosu::Font.new(self, Gosu::default_font_name, 16) 38 | end 39 | 40 | def button_down(id) 41 | if id == Gosu::KbR 42 | reset 43 | else 44 | begin 45 | super(id) 46 | rescue => e 47 | Hasu.error = e 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hasu 2 | 3 | Helps you develop [Gosu](http://www.libgosu.org/) games more quickly. 4 | 5 | ## How? 6 | 7 | ### Hot code loading 8 | 9 | Normally, when you change the code for a Gosu game, you have to close and restart the game for the code to take affect. 10 | With Hasu, modified source files will be reloaded each time through the game loop. 11 | 12 | ### Exception catching 13 | 14 | When an exception bubbles up out of your game loop, Gosu will crash. 15 | When an exception occurs in a Hasu game, Hasu pauses your game, prints the exception details into your window, and resumes your game once the code that fixes it is loaded. 16 | 17 | ### Reset 18 | 19 | Hot code loading unfortunately is worthless for your `initialize` method since your window will only be initialized once, 20 | Instead of putting your game's setup code in `initialize`, place it in `reset` and press R whenever you want to re-initialize your game state. 21 | 22 | ## Using Hasu 23 | 24 | __The above features only work on Ruby 2+, though Hasu will still load on earlier versions (in case you want to pack up your game with Releasy).__ 25 | 26 | ### 0: Install Hasu 27 | 28 | Add this line to your application's Gemfile: 29 | 30 | gem hasu 31 | 32 | Or install it yourself as: 33 | 34 | $ gem install hasu 35 | 36 | ### 1: Subclass `Hasu::Window` (or prepend `Hasu::Guard`). 37 | 38 | Instead of subclassing `Gosu::Window`, use `Hasu::Window`: 39 | 40 | ```ruby 41 | class Game < Hasu::Window 42 | def initialize 43 | super(640, 480, false) 44 | end 45 | 46 | def reset 47 | # ... 48 | end 49 | 50 | def update 51 | # ... 52 | end 53 | 54 | def draw 55 | # ... 56 | end 57 | end 58 | ``` 59 | 60 | If you're using Chingu (or another library which has its own window subclass), you can prepend `Hasu::Guard` onto your window for the same effect: 61 | 62 | ```ruby 63 | class Game < Chingu::Window 64 | prepend Hasu::Guard 65 | 66 | def initialize 67 | super(640, 480, false) 68 | end 69 | 70 | def reset 71 | # ... 72 | end 73 | 74 | def update 75 | # ... 76 | end 77 | 78 | def draw 79 | # ... 80 | end 81 | end 82 | ``` 83 | 84 | ### 2: Use `Hasu.load` to require your game's files. 85 | 86 | For the files you want to be hot loaded, use `Hasu.load` instead of `require`. 87 | 88 | ```ruby 89 | Hasu.load "ball.rb" 90 | ``` 91 | 92 | ### 3: Run your game with `.run` 93 | 94 | Instead of `Game.new.show`, run your Hasu game with `Game.run`. 95 | --------------------------------------------------------------------------------