Class: Pixiurge::App

Inherits:
Object
  • Object
show all
Defined in:
lib/pixiurge/app.rb,
lib/pixiurge/config_ru.rb

Overview

App is the parent class of Pixiurge applications (games). By inheriting from this and overriding various handlers, your Pixiurge server-side application can respond appropriately to the Javascript/Pixi client-side browsers.

An app is used in config.ru to set up web server setup like asset directories. It also defines methods for interacting with players. This basic App class is very bare-bones. See AuthenticatedApp for a version with accounts and login.

For full documentation of the interface, see AppInterface.

See Also:

Since:

  • 0.1.0

Direct Known Subclasses

AuthenticatedApp

Constant Summary

EVENTS =

Since:

  • 0.1.0

[ "player_login", "player_logout", "player_create_body", "player_action", "player_reconnect", "open", "close", "error", "message", "login" ]
INIT_OPTIONS =

Legal options to pass to Pixiurge::App.new

Since:

  • 0.1.0

[ :debug, :record_traffic, :incoming_traffic_logfile, :outgoing_traffic_logfile ]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = { :debug => false, :record_traffic => false, :incoming_traffic_logfile => "log/incoming_traffic.json", :outgoing_traffic_logfile => "log/outgoing_traffic.json" }) ⇒ App

Constructor for Pixiurge App base class.

Debugging options:

  • :debug - whether to print debugging messages
  • :record_traffic - whether to write network messages to a JSON logfile
  • :incoming_traffic_logfile - the logfile for client-to-server traffic
  • :outgoing_traffic_logfile - the logfile for server-to-client traffic

Parameters:

  • options (Hash) (defaults to: { :debug => false, :record_traffic => false, :incoming_traffic_logfile => "log/incoming_traffic.json", :outgoing_traffic_logfile => "log/outgoing_traffic.json" })

    Options to configure app behavior

Options Hash (options):

  • :debug (Boolean)

    Whether to print debug output

  • :record_traffic (Boolean)

    Whether to record incoming and outgoing websocket traffic to logfiles

  • :incoming_traffic_logfile (String)

    Pathname to record incoming websocket traffic

  • :outgoing_traffic_logfile (String)

    Pathname to record outgoing websocket traffic

Since:

  • 0.1.0



298
299
300
301
302
303
304
305
306
307
# File 'lib/pixiurge/app.rb', line 298

def initialize(options = { :debug => false, :record_traffic => false,
                 :incoming_traffic_logfile => "log/incoming_traffic.json", :outgoing_traffic_logfile => "log/outgoing_traffic.json" })
  illegal_options = options.keys - INIT_OPTIONS
  raise("Illegal options passed to Pixiurge::App#new: {illegal_options.inspect}!") unless illegal_options.empty?
  @debug = options[:debug]
  @record_traffic = options[:record_traffic]
  @incoming_traffic_logfile = options[:incoming_traffic_logfile] || "log/incoming_traffic.json"
  @outgoing_traffic_logfile = options[:outgoing_traffic_logfile] || "log/outgoing_traffic.json"
  @event_handlers = {}
end

Instance Attribute Details

#debugObject (readonly)

Whether to print debugging messages - can be set to nil/false, or to an Integer

Since:

  • 0.1.0



272
273
274
# File 'lib/pixiurge/app.rb', line 272

def debug
  @debug
end

#incoming_traffic_logfileObject (readonly)

Path to logfile for incoming Websocket messages

Since:

  • 0.1.0



274
275
276
# File 'lib/pixiurge/app.rb', line 274

def incoming_traffic_logfile
  @incoming_traffic_logfile
end

#outgoing_traffic_logfileObject (readonly)

Path to logfile for outgoing Websocket messages

Since:

  • 0.1.0



276
277
278
# File 'lib/pixiurge/app.rb', line 276

def outgoing_traffic_logfile
  @outgoing_traffic_logfile
end

#record_trafficObject (readonly)

Whether the app is currently recording Websocket traffic to logfiles; useful for development, bad in production

Since:

  • 0.1.0



270
271
272
# File 'lib/pixiurge/app.rb', line 270

def record_traffic
  @record_traffic
end

Instance Method Details

#coffeescript_dirs(*dirs) ⇒ Object

Call this to add coffeescript directories for your own app, if you're using CoffeeScript.

Parameters:

  • dirs (String, Array<String>)

    The directory name or array of directory names, located under the web root you passed to Pixiurge.root_dir

Since:

  • 0.1.0



44
45
46
47
48
# File 'lib/pixiurge/config_ru.rb', line 44

def coffeescript_dirs *dirs
  raise "Please set Pixiurge.root_dir before using Pixiurge.static_dirs!" unless @root_dir
  dirs = [*dirs].flatten
  @rack_builder.use Rack::Coffee, :root => (@root_dir + "/"), :urls => dirs.map { |d| "/" + d }
end

#handle_message(ws, data) ⇒ Object

This handler can be used to handle certain messages before dispatching them to one or more other events or handlers.

Parameters:

  • ws (Websocket)

    The Ruby-Websocket-Driver websocket object

  • data (Object)

    Deserialized JSON data sent from the client

Since:

  • 0.1.0



378
379
380
# File 'lib/pixiurge/app.rb', line 378

def handle_message(ws, data)
  return send_event "message", ws, data
end

#handlerObject

Get the final, built Rack handler from Pixiurge with all the specified middleware and websocket handling.

See Also:

  • Pixiurge.root_dir
  • Pixiurge.static_dirs
  • Pixiurge.coffeescript_dirs

Since:

  • 0.1.0



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/pixiurge/config_ru.rb', line 156

def handler
  @static_files ||= []
  static_files = @static_files.map { |f| "/" + f }
  lambda do |env|
    if Faye::WebSocket.websocket? env
      ws = Faye::WebSocket.new(env)
      return websocket_handler(ws).rack_response
    else
      if @root_redirect && env["PATH_INFO"] == "/"
        return [302, { 'Location' => @root_redirect }, [] ]
      end
      if @root_dir && static_files.include?(env["PATH_INFO"])
        file = env["PATH_INFO"]
        path = File.join(@root_dir, file)
        # @todo Figure out how to do this with Rack::File instead of File.read
        return [200, {'Content-Type' => 'text/html'}, [File.read(path)]]
      else
        return [404, {}, [""]]
      end
    end
  end
end

#on_event(event) { ... } ⇒ void

This method returns an undefined value.

This method allows you to handle one or more events of your choice using a handler of your choice. For the list of events, see Pixiurge::AppInterface. The handler will be called whenever the event occurs, and the block will receive the same arguments that a child method would receive for that event type.

Parameters:

  • event (String)

    The name of the event, such as "close" or "player_login"

Yields:

  • Your handler for the event in question, which takes event-dependent arguments

Yield Returns:

  • (void)

See Also:

Since:

  • 0.1.0



351
352
353
354
355
# File 'lib/pixiurge/app.rb', line 351

def on_event(event, &block)
  raise("Can't subscribe to unrecognized event #{event.inspect}! Only #{EVENTS.inspect}!") unless EVENTS.include?(event)
  @event_handlers[event] ||= []
  @event_handlers[event].push(block)
end

#rack_builder(builder, options = {}) ⇒ Object

In config.ru, call this as "Pixiurge.rack_builder self" to allow Pixiurge to add middleware to your Rack stack. Pixiurge will add its own /pixiurge directory in order to provide access to the Pixiurge debug or release Javascript.

Parameters:

  • builder (Rack::Builder)

    The top level of your config.ru Rack Builder

  • options (Hash) (defaults to: {})

    Options about Rack middleware

Options Hash (options):

  • :no_dev_pixiurge (Boolean)

    Don't automatically host the Pixiurge unminified Javascript at /pixiurge

  • :no_vendor_pixiurge (Boolean)

    Don't automatically host the Pixiurge vendor scripts at /vendor

Since:

  • 0.1.0



28
29
30
31
32
33
34
35
36
37
# File 'lib/pixiurge/config_ru.rb', line 28

def rack_builder builder, options = {}
  illegal_opts = options.keys - [ :no_dev_pixiurge, :no_vendor_pixiurge ]
  raise("Unknown option(s) passed to rack_builder: #{illegal_opts.inspect}!") unless illegal_opts.empty?
  @rack_builder = builder

  coffee_root = File.expand_path File.join(__dir__, "..", "..")
  vendor_root = File.join(coffee_root, "vendor")
  @rack_builder.use(Rack::Coffee, :root => coffee_root, :urls => ["/pixiurge"]) unless options[:no_dev_pixiurge]
  @rack_builder.use(Rack::Static, :root => coffee_root, :urls => ["/vendor", "/pixiurge"]) unless options[:no_vendor_pixiurge]
end

#root_dir(dir) ⇒ Object

Call this with your application's web root directory. This is useful for finding your application's assets, such as graphics, images and maps. Later calls such as static_dirs and coffeescript_dirs are relative to this root.

Parameters:

  • dir (String)

    The path to your app's web root.

Since:

  • 0.1.0



15
16
17
# File 'lib/pixiurge/config_ru.rb', line 15

def root_dir dir
  @root_dir = File.absolute_path(dir)
end

#root_redirect(url) ⇒ Object

To serve some specific file from the root, a redirect is one possibility. This will redirect from "/" to the given URL with an HTTP status 302.

Parameters:

  • url (String)

    The URL to redirect to

Since:

  • 0.1.0



84
85
86
# File 'lib/pixiurge/config_ru.rb', line 84

def root_redirect url
  @root_redirect = url
end

#static_dirs(*dirs) ⇒ Object

To have Pixiurge serve static directories of Javascript or assets, call this with the appropriate list of directory names. All directory names are relative to the web root you passed to Pixiurge.root_dir. It's possible to call this multiple times, but then you'll get a tiny inefficiency where two different Rack middlewares get checked. Not a big deal if you have a reason, but it's slightly better to call it once with a list of directories.

Parameters:

  • dirs (String, Array<String>)

    The directory name or array of directory names, located under the web root you passed to Pixiurge.root_dir

Since:

  • 0.1.0



60
61
62
63
64
65
# File 'lib/pixiurge/config_ru.rb', line 60

def static_dirs *dirs
  dirs = [*dirs].flatten

  raise "Please set Pixiurge.root_dir before using Pixiurge.static_dirs!" unless @root_dir
  @rack_builder.use Rack::Static, :root => @root_dir, :urls => dirs.map { |d| "/" + d }
end

#static_files(*files) ⇒ Object

To have Pixiurge serve individual static files such as index.html, call this with the appropriate list of file paths. All paths are relative to the web root you passed to Pixiurge.root_dir.

Parameters:

  • files (String, Array<String>)

    The file path or array of file paths, located under the web root you passed to Pixiurge.root_dir

See Also:

  • Pixiurge.static_dirs

Since:

  • 0.1.0



73
74
75
76
# File 'lib/pixiurge/config_ru.rb', line 73

def static_files *files
  @static_files ||= []
  @static_files.concat [*files].flatten
end

#tilt_dirs(*dirs) ⇒ void

This method returns an undefined value.

To serve various template file types, the Tilt middleware can be used. This is for things like Erb or Haml that can generate HTML, but can also work for CoffeeScript and other file types.

Parameters:

  • dirs (String, Array<String>)

    One or more paths to treat as tilt dirs; relative to Pixiurge.root_dir

  • options (Hash)

    Optional final Hash with which to supply options to the Tilt middleware

Since:

  • 0.1.0



97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/pixiurge/config_ru.rb', line 97

def tilt_dirs *dirs
  options = {}
  options = dirs.pop if dirs[-1].respond_to?(:has_key?)
  dirs = [*dirs].flatten
  raise "Please set Pixiurge.root_dir before using Pixiurge.tilt_dirs!" unless @root_dir
  @rack_builder.use Pixiurge::Middleware::Tilt,
    :root => @root_dir,
    :urls => dirs.map { |d| "/" + d },
    :scope => Pixiurge::TemplateView.new,
    :engines => options[:engines] || ["erubis"]
  nil
end

#tmx_dirs(*dirs) ⇒ void

This method returns an undefined value.

The Tiled map editor strongly prefers keeping its map data in TMX, an XML-based format. Unfortunately, JSON is much better for use by Javascript. Since TMX has a standard JSON export format, we preconvert the TMX to JSON on the Ruby side to keep the structures and field names the same. TMX and TMX-JSON have some unfortunate structure and naming differences, so it's not trivial to convert between them.

It's possible to use Tiled to export from TMX to JSON on the command line, but TMX is a graphical program and can be hard to build on a server - we'd rather not have it as a runtime dependency.

This middleware uses the Ruby tmx gem to on-the-fly convert between XML-based TMX data and an approximation of Tiled's JSON TMX format. The current converter is somewhat limited - if we need a closer match in the future, the plan is to submit pull requests to the tmx gem until its approximation of Tiled's behavior is good enough.

If the final argument appears to be a Hash, it will be treated as an options hash. If it contains the key :cache, the value of that key should be a Demiurge::Tmx::TileCache to use when querying TMX data. It defaults to TmxLocation.default_cache.

Parameters:

  • dirs (String, Array<String>)

    One or more directories to serve TMX files from

Since:

  • 0.1.0



136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/pixiurge/config_ru.rb', line 136

def tmx_dirs *dirs
  options = {}
  if dirs[-1].respond_to?(:has_key?)
    options = dirs.pop
  end
  dirs = [*dirs].flatten
  raise "Please set Pixiurge.root_dir before using Pixiurge.static_dirs!" unless @root_dir
  @rack_builder.use Pixiurge::Middleware::TmxJson,
    :root => @root_dir,
    :urls => dirs.map { |d| "/" + d },
    :cache => Demiurge::Tmx::TmxLocation.default_cache  # By default, use the same cache TmxLocations do
  nil
end

#websocket_handler(ws) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The websocket handler for the Pixiurge app.

Parameters:

  • ws (Websocket)

    A Websocket-Driver socket object or object with matching interface

Since:

  • 0.1.0



314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/pixiurge/app.rb', line 314

def websocket_handler(ws)
  ws.on :open do |event|
    puts "Socket open" if @debug
    send_event "open", ws
  end

  ws.on :message do |event|
    File.open(@incoming_traffic_logfile, "a") { |f| f.write event.data + "\n" } if @record_traffic
    data = MultiJson.load event.data
    handle_message ws, data
  end

  ws.on :error do |event|
    send_event "error", event.message
  end

  ws.on :close do |event|
    send_event "close", ws, event.code, event.reason
    ws = nil
  end

  # Return async Rack response
  ws
end