app/console/assets.rb
the library's own source, console 0.2.0 · back to Console::Assets
# Console::Assets -- where a cart's files actually live. # # A cart is a directory, and it owns everything it needs: # # carts/space/ # app/space.rb the cart itself, plus any code files beside it # sprites/ its own art # sounds/ # maps/ # data/ # # A cart writes plain paths -- 'sprites/hero.png', 'maps/Level_0.ldtk' -- and # they mean "somewhere inside my cart". The console rewrites them into real, # game-relative paths ('carts/space/sprites/hero.png') on the way out to # DragonRuby. Only this module has to know where the cart directory sits; the # sprite index, audio, maps and the draw helpers just hand paths over. # # Resolution is cart-first, with exactly one fallback: the console's own root. # A file inside the cart wins; a file that exists only at the console root still # resolves, so the console can ship starter art for a brand-new cart. Each # fallback is logged, because a published cart stages its cart directory alone # (see ./bin/publish-cart) and anything it borrows from the console root is missing # from the build. A cart that owns its assets has no fallbacks. module Console module Assets extend self # The logical asset directory carts keep their art in. Kept as a name # rather than a path: where it actually sits depends on the booted cart. SPRITES_DIR = 'sprites' class << self attr_reader :root, :fallbacks end @root = nil @fallbacks = [] # Point the console at a cart directory. nil means "no cart is booted", and # every path resolves against the console root -- which is what the console # needs when it is not running a cart at all. def boot(cart_root) @root = cart_root.nil? || cart_root.to_s.empty? ? nil : cart_root.to_s @fallbacks = [] self end def scoped? !@root.nil? end # The cart directory in scope, or nil. def cart_root @root end # Rewrite a cart-relative path into a real one. # # Cart copy first, then the console root, and when neither exists it returns # the cart-relative path anyway: that is where the file belongs, and it is # what a "not found" warning should name. def resolve(path) return path unless path.is_a?(String) return path if path.empty? return path if path.include? '://' return path if scoped_inside? path return path unless scoped? inside = "#{@root}/#{path}" if DR.stat_file inside inside elsif DR.stat_file path note_fallback path path else inside end end # True when `path` came from the console root rather than the cart. Used by # the tests, and by anything that wants to warn about an unowned asset. def shared?(path) @fallbacks.include? path.to_s end # Every asset this cart is borrowing from the console root. def shared_assets @fallbacks.dup end private # A path already pointing inside the cart is left alone, so resolving is # idempotent -- a path can pass through resolve twice without being nested. def scoped_inside?(path) return false unless scoped? path == @root || path.start_with?("#{@root}/") end def note_fallback(path) return if @fallbacks.include? path @fallbacks << path return unless Console.respond_to? :debug Console.debug "#{path} is not in the cart; resolved from the console root. " \ "Move it into #{@root}/ if the cart owns it." end end end