Class: Pixiurge::EngineConnector

Inherits:
Object
  • Object
show all
Defined in:
lib/pixiurge/engine_connector.rb

Overview

A single EngineConnector runs on the server, sending messages about the game world to the various player connections. An EngineConnector subscribes to events on a Demiurge engine for the gameworld and a Pixiurge App to connect to browsers.

The EngineConnector acts, among other things, as a mapping between Demiurge objects, websockets, Player objects and Displayable objects.

Since:

  • 0.1.0

Constant Summary

SIMULATION_OPTIONS =

Legal simulation-related hash options for #initialize

Since:

  • 0.1.0

[ :engine, :engine_text, :engine_files, :engine_dsl_dir, :engine_restore_statefile, :ms_per_tick, :autosave_ticks, :autosave_path ]
CONSTRUCTOR_OPTIONS =

Legal hash options for #initialize

Since:

  • 0.1.0

SIMULATION_OPTIONS + [ :default_width, :default_height, :no_start_simulation ]
IGNORED_NOTIFICATION_TYPES =

This is the internal constant to exit early from the notification routine if the notification is of any of these types. Anything here requires no direct response from the EngineConnector, for a variety of different reasons.

Since:

  • 0.1.0

{
  # Tick finished? Great, no change.
  Demiurge::Notifications::TickFinished => true,

  # MoveFrom? We handle the corresponding MoveTo instead.
  Demiurge::Notifications::MoveFrom => true,

  # LoadStateStart/Verify or LoadWorldStart notifications? We'll make changes when they complete
  Demiurge::Notifications::LoadStateStart => true,
  Demiurge::Notifications::LoadWorldStart => true,
  Demiurge::Notifications::LoadWorldVerify => true,

  # Player logout or reconnect? Already handled. This EngineConnector was the object that sent this notification anyway.
  Pixiurge::Notifications::PlayerLogout => true,
  Pixiurge::Notifications::PlayerReconnect => true,
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pixi_app, options = {}) ⇒ EngineConnector

Constructor. Create the EngineConnector, which will act as a gateway between the simulation engine and the network and authorization interfaces (a.k.a. "the app".) The engine and any additional settings will be configured after the object is allocated.

Unless the :no_start_simulation option is true, #start_simulation will be called with the appropriate simulation-related options to create the simulation engine.

Parameters:

  • pixi_app (Pixiurge::App)

    The Pixiurge App for assets and network interactions

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

    Options for the EngineConnector

Options Hash (options):

  • :default_width (Integer)

    Default width for display if unspecified

  • :default_height (Integer)

    Default height for display if unspecified

  • :no_start_simulation (Boolean)

    Don't allocate an engine or start the simulation - that will be handled later or outside Pixiurge

  • :engine (Demiurge::Engine)

    Demiurge simulation engine; if this option is passed, use it instead of creating a new one

  • :engine_text (Array)

    Array of 2-element arrays, each with a filename followed by World File DSL; passed to Demiurge::DSL.engine_from_dsl_text to create engine

  • :engine_files (Array)

    Array of filenames of World File DSL code; passed to Demiurge::DSL.engine_from_dsl_files to create engine.

  • :engine_dsl_dir (String)

    Path to a directory containing DSL files and (optionally) DSL Ruby extensions

  • :engine_restore_statefile (String)

    Path to most recent statedump file, which will be restored to the engine state

  • :ms_per_tick (Integer)

    How many milliseconds should occur between simulation ticks

  • :autosave_ticks (Integer)

    Dump state automatically every time this many ticks occur; set to 0 for no automatic state-dump; defaults to 600

  • :autosave_path (String)

    Write the autosave to this path; you can use %TICKS% in the path for a number of ticks completed; defaults to "state/autosave_%TICKS%.json"

See Also:

Since:

  • 0.1.0



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/pixiurge/engine_connector.rb', line 49

def initialize(pixi_app, options = {})
  illegal_options = options.keys - CONSTRUCTOR_OPTIONS
  raise("Illegal options passed to EngineConnector#initialize: #{illegal_options.inspect}!") unless illegal_options.empty?

  @app = pixi_app
  @players = {}      # Mapping of player name strings to Player objects (not Displayable objects or Demiurge items)
  @displayables = {} # Mapping of item names to the original registered source, and display objects such as TileAnimatedSprites
  @default_width = options[:default_width] || 640
  @default_height = options[:default_height] || 480
  @ms_per_tick = 500

  unless options[:no_start_simulation]
    # @todo Replace this with Hash#slice and require a higher minimum Ruby version
    sim_options = {}
    SIMULATION_OPTIONS.each { |opt| options.has_key?(opt) && sim_options[opt] = options[opt] }
    start_simulation(sim_options)
  end
end

Instance Attribute Details

#appObject (readonly)

Since:

  • 0.1.0



16
17
18
# File 'lib/pixiurge/engine_connector.rb', line 16

def app
  @app
end

#engineObject (readonly)

Since:

  • 0.1.0



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

def engine
  @engine
end

Instance Method Details

#add_player(player) ⇒ Object

This must be called on the Demiurge timeline, not immediately. So we wait for the PlayerLogin notification.

Since:

  • 0.1.0



434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
# File 'lib/pixiurge/engine_connector.rb', line 434

def add_player(player)
  @players[player.name] = player
  unless player.displayable
    raise "Set the Player's Displayable before this!"
  end
  loc_name = player.displayable.location_name
  player_position = player.displayable.position
  loc_do = @displayables[loc_name][:displayable]

  # Do we have a display object for that player's location?
  unless loc_do
    STDERR.puts "This player doesn't seem to be in a known displayable location, instead is in #{loc_name.inspect}!"
    return
  end

  set_player_backdrop(player, player_position, loc_do)
end

#displayable_by_name(item_name) ⇒ Pixiurge::Displayable?

Query for a Displayable object by Demiurge item name. This is useful when trying to map a Demiurge location into a displayable tilemapped area, for instance. It can also query for agents and other non-location Demiurge items - anything with a Displayable presence.

Parameters:

  • item_name (String)

    The Demiurge item name to query

Returns:

Since:

  • 0.1.0



269
270
271
# File 'lib/pixiurge/engine_connector.rb', line 269

def displayable_by_name(item_name)
  @displayables[item_name][:displayable]
end

#displayable_for_item(item) ⇒ Object

Since:

  • 0.1.0



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/pixiurge/engine_connector.rb', line 295

def displayable_for_item(item)
  return if item.is_a?(Demiurge::InertStateItem) # Nothing needed for InertStateItems

  # See if the item uses a custom Displayable set via the "display"
  # block in the World Files. If so, use it.
  disp_action = item.get_action("$display")
  if disp_action && disp_action["block"] # This special action is used to pass the Display info through to a Display library.
    builder = Pixiurge::Display::DisplayBuilder.new(item, engine_connector: self, &(disp_action["block"]))
    displayables = builder.built_objects
    raise("Only display one object per agent right now for item #{item.name.inspect}!") if displayables.size > 1
    raise("No display objects declared for item #{item.name.inspect}!") if displayables.size == 0
    return displayables[0]  # Exactly one display object. Perfect.
  end

  if item.is_a?(::Demiurge::Tmx::TmxLocation)
    return ::Pixiurge::Display::TmxMap.new item.tile_cache_entry, name: item.name, engine_connector: self  # Build a Pixiurge location
  elsif item.agent?
    # No Display information? Default to generic guy in a hat.
    layers = [ "male", "kettle_hat_male", "robe_male" ]
    raise "Nope! Haven't implemented a default displayable for agents yet!"
  end

  if item.zone?
    return ::Pixiurge::Display::Invisible.new name: item.name, engine_connector: self
  end

  # If we got here, we have no idea how to display this.
  nil
end

#each_displayable_for_location_name(location_name, &block) ⇒ Object

Since:

  • 0.1.0



381
382
383
384
385
386
387
388
# File 'lib/pixiurge/engine_connector.rb', line 381

def each_displayable_for_location_name(location_name, &block)
  @displayables.each do |disp_name, disp_hash|
    disp = disp_hash[:displayable]
    if disp.location_name == location_name
      yield(disp)
    end
  end
end

#each_player_for_location_name(location_name, options = { :except => [] }, &block) ⇒ Object

Since:

  • 0.1.0



372
373
374
375
376
377
378
379
# File 'lib/pixiurge/engine_connector.rb', line 372

def each_player_for_location_name(location_name, options = { :except => [] }, &block)
  @players.each do |player_name, player|
    next if options[:except].include?(player) || options[:except].include?(player_name)
    if player.displayable && player.displayable.location_name == location_name
      yield(player)
    end
  end
end

#hide_displayable_from_players(displayable, position) ⇒ Object

Since:

  • 0.1.0



398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/pixiurge/engine_connector.rb', line 398

def hide_displayable_from_players(displayable, position)
  position ||= displayable.demi_item.position
  return unless position

  loc_name = position.split("#")[0]
  loc = @engine.item_by_name(loc_name)
  if loc.is_a?(::Demiurge::Tmx::TmxLocation)
    each_player_for_location_name(loc_name) do |player|
      player.destroy_displayable(displayable.name)
    end
  end
end

#notified(data) ⇒ Object

When new data comes in about things in the engine changing, this is what receives that notification.

Since:

  • 0.1.0



479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
# File 'lib/pixiurge/engine_connector.rb', line 479

def notified(data)
  # First, ignore a bunch of notifications we don't care about
  return if IGNORED_NOTIFICATION_TYPES[data["type"]]

  # We subscribe to all events in all locations, and the move-from
  # and move-to have the same fields except location, zone and
  # type. So only pay attention to the move_to.
  if data["type"] == Demiurge::Notifications::MoveTo
    return notified_of_move_to(data)
  end

  if data["type"] == Demiurge::Notifications::LoadStateEnd
    # What to do here? Displayables don't care about this any more. Does anything in Pixiurge?
    return
  end

  # On Login, we let the Player object know it can start showing all
  # update messages, not just the special early ones. We've caught
  # up to the point in the notification stream where this player
  # logged in, so after this it's all actually new.
  if data["type"] == Pixiurge::Notifications::PlayerLogin
    username = data["player"]
    # We touch (and/or create) the body Demiurge item on the assumption that nobody else is using it yet...
    demi_item = @engine.item_by_name(username)
    if demi_item
      raise("There is already a body with reserved name #{username} not marked for this player!") unless demi_item.state["$player_body"] == username
    else
      # No body yet? Send a signal to indicate that we need one.
      @app.send("send_event", "player_create_body", username)
      demi_item = @engine.item_by_name(username)
      unless demi_item
        # Still no body? Either there was no signal handler, or it did nothing.
        STDERR.puts "No player body was created in Demiurge for #{username.inspect}! No login for you!"
        return
      end
      demi_item.state["$player_body"] = username
      demi_item.run_action("create") if demi_item.get_action("create")
      # Now register the player's body with the EngineConnector to make sure we have a Displayable for it
      register_engine_item(demi_item)
      displayable = @displayables[username][:displayable]
      unless(displayable)
        raise "No displayable item was created for user #{username}'s body!"
      end
    end
    demi_item.run_action("login") if demi_item.get_action("login")
    ws = @app.websocket_for_username username
    displayable = @displayables[username][:displayable]
    player = Pixiurge::Player.new websocket: ws, name: username, displayable: displayable, display_settings: display_settings, engine_connector: self

    # Add_player sets the player's backdrop manually.
    add_player(player)
    return
  end

  if data["type"] == Demiurge::Notifications::LoadWorldEnd
    # Not entirely clear what we do here. Demiurge has just reloaded
    # the World files, which may result in a bunch of changes...
    # Though if objects get created, destroyed or moved, that should
    # get separate notifications which should finish before this
    # happens. We don't care that they're part of a World Reload --
    # if we did, we'd track the LoadWorldStart.
    return
  end

  if data["type"] == Demiurge::Notifications::IntentionCancelled
    acting_item = data["actor"]
    if @players[acting_item]
      # This was a player action that was cancelled
      player = @players[acting_item]
      displayable = Pixiurge::Display::TextEffect.new(data["reason"], style: { fill: "orange" }, duration: 3000, name: "", engine_connector: self)
      displayable.position = player.displayable.position
      player.show_displayable(displayable)
      return
    end
    return
  end

  if data["type"] == Demiurge::Notifications::IntentionApplied
    # For right now we don't need any kind of confirmations. But
    # when we do, this is where they come from.
    return
  end

  # This notification will catch new player bodies, instantiated agents and whatnot.
  if data["type"] == Demiurge::Notifications::NewItem
    item = @engine.item_by_name data["actor"]
    register_engine_item(item)
    return
  end

  STDERR.puts "Unhandled notification of type #{data["type"].inspect}...\n#{data.inspect}"
end

#notified_of_move_to(data) ⇒ Object

Since:

  • 0.1.0



572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
# File 'lib/pixiurge/engine_connector.rb', line 572

def notified_of_move_to(data)
  actor_do = @displayables[data["actor"]][:displayable]
  x, y = ::Demiurge::TiledLocation.position_to_coords(data["new_position"])
  loc_name = data["new_location"]
  loc_do = @displayables[loc_name] ? @displayables[loc_name][:displayable] : nil
  unless loc_do
    STDERR.puts "Moving to a non-displayed location #{loc_name.inspect}, no display object found..."
    return
  end

  actor_do.position = data["new_position"]

  # Is it a player that just moved? If so, update them specifically.
  acting_player = @players[data["actor"]]
  if acting_player
    if data["old_location"] != data["new_location"]
      ## Hide the old location's Displayables and show the new
      ## location's Displayables to the player who is moving
      set_player_backdrop(acting_player, data["new_position"], @displayables[loc_name][:displayable])
    else
      # Player moved in same location, pan to new position
      acting_player.move_displayable(actor_do, data["old_position"], data["new_position"])
      acting_player.pan_to_coordinates(x, y)
    end
  end

  # Whether it's a player moving or something else, update all the
  # players who just saw the item move, disappear or appear.
  @players.each do |player_name, player|
    next if player_name == data["actor"]      # Already handled it if this player is the one moving.
    player_loc_name = player.displayable.location_name

    # First case: moving player remained in the same room - update movement for anybody *in* that room
    if data["old_location"] == data["new_location"]
      next unless player_loc_name == data["new_location"]
      actor_do.move_for_player(player, data["old_position"], data["new_position"], {})

      # Second case: moving player changed rooms and we're in the old one
    elsif player_loc_name == data["old_location"]
      # The item changed rooms and the player is in the old
      # location. Hide the item.
      player.destroy_displayable(actor_do)

      # Third case: moving player changed rooms and we're in the new one
    elsif player_loc_name == data["new_location"]
      # The item changed rooms and the player is in the new
      # location. Show the item, if it moved to a displayable
      # location.
      player.show_displayable(actor_do)
    end
  end
end

#player_by_username(username) ⇒ Pixiurge::Player?

Query for a Player object by account name, which should match the Demiurge username for that player's body if there is one.

Parameters:

  • username (String)

    The account name to query

Returns:

Since:

  • 0.1.0



279
280
281
# File 'lib/pixiurge/engine_connector.rb', line 279

def player_by_username(username)
  @players[username]
end

#register_displayable_object(object) ⇒ void

This method returns an undefined value.

This method adds a new Displayable object which does not correspond to a Demiurge item. This is useful for Displayables for non-Demiurge objects such as dialog boxes or title screens, and for components of larger objects (e.g. a player's shadow) which are attached to a Demiure item but don't really represent a Demiurge item.

Among other things this reserves a name for the Displayable in the EngineConnector. A Displayable isn't allowed to share a name with any other Displayable nor with any item in the Demiurge engine. For this reason, a common convention for "sub-Displayables" is to use the parent Displayable's name followed by an at-sign and a name or number. The at-sign isn't a legal character for Demiurge item names, so this makes it clear what the "attached" item is and guarantees uniqueness.

Parameters:

Since:

  • 0.1.0



364
365
366
367
368
369
370
# File 'lib/pixiurge/engine_connector.rb', line 364

def register_displayable_object(object)
  if @displayables[object.name] # Already have this one
    return if @displayables[object.name][:source] == object  # Duplicate registration, which is fine
    raise "Already have a Displayable named #{object.name.inspect} registered by #{@displayables[object.name][:source].inspect}! Can't re-register as Displayable #{object.inspect}!"
  end
  @displayables[object.name] = { displayable: object, source: object }
end

#register_engine_item(item) ⇒ Object

Since:

  • 0.1.0



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
# File 'lib/pixiurge/engine_connector.rb', line 325

def register_engine_item(item)
  if @displayables[item.name] # Already have this one
    return if @displayables[item.name][:source] == :demiurge  # Duplicate registration, it's fine
    raise "Displayable name #{item.name.inspect} is already used by source object #{@displayables[item.name][:source].inspect}! Can't re-register as #{item.inspect}!"
  end

  displayable = displayable_for_item(item)
  if displayable
    @displayables[item.name] = { displayable: displayable, source: :demiurge }
    displayable.position = item.position if item.position
    show_displayable_to_players(displayable)
    return
  end

  # What if there's no Displayable for this item? That might be okay or might not.
  return if item.is_a?(::Demiurge::InertStateItem) # Nothing needed for InertStateItems

  STDERR.puts "Don't know how to register or display this item: #{item.name.inspect}"
end

#remove_player(player) ⇒ Object

The logout action happens before this does, which may affect what's where.

Since:

  • 0.1.0



453
454
455
# File 'lib/pixiurge/engine_connector.rb', line 453

def remove_player(player)
  @players.delete(player.name)
end

#set_player_backdrop(player, player_position, location_do) ⇒ Object

This is going to wind up as a complicated interaction at some point between the player and the zone/location. There need to be different areas where different levels of other-player activity are visible or invisible; that doesn't take things like sharding into account either...

Since:

  • 0.1.0



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
# File 'lib/pixiurge/engine_connector.rb', line 416

def set_player_backdrop(player, player_position, location_do)
  loc_name = location_do.name

  # Show the location's sprites
  player.destroy_all_displayables
  player.show_displayable(location_do)
  x, y = ::Demiurge::TiledLocation.position_to_coords(player_position)
  x ||= 0
  y ||= 0
  player.send_instant_pan_to_pixel_offset location_do.block_width * x, location_do.block_height * y

  # Anybody or anything else there? Show them to this player.
  each_displayable_for_location_name(location_do.name) do |displayable|
    player.show_displayable(displayable)
  end
end

#show_displayable_to_players(displayable, options = { :except => []}) ⇒ Object

Since:

  • 0.1.0



390
391
392
393
394
395
396
# File 'lib/pixiurge/engine_connector.rb', line 390

def show_displayable_to_players(displayable, options = { :except => []})
  return unless displayable.position # Agents and some other items are allowed to have no position and just be instantiable
  loc_name = displayable.position.split("#")[0]
  each_player_for_location_name(loc_name, options) do |player|
    player.show_displayable(displayable)
  end
end

#start_simulation(options = {}) ⇒ void

This method returns an undefined value.

Make sure the simulation is running. Supply parameters about how exactly that should happen. You can pass in a constructed engine, or parameters for how to create one.

You should pass in up to one of :engine, :engine_text, :engine_files or :engine_dsl_dir to create the Engine or use a created Engine. If you don't pass one, Pixiurge will assume an :engine_dsl_dir of "world" under the App#root_dir.

A DSL directory, if one is included, will assume that any Ruby file under an extensions directory or subdirectory will be loaded directly as Ruby code. Any Ruby file not under an extensions directory or subdirectory will be loaded as Demiurge World File DSL.

By default the Demiurge Engine will run at 2 ticks per second, or 500 milliseconds per tick. You can alter this with the :ms_per_tick option.

By default, Pixiurge will save state automatically every 600 ticks into a timestamped file in the "state" subdirectory. The :autosave_ticks option can be set to a number of ticks for how often to save, or 0 for no autosave. You can set it to 0 and configure an autosave yourself by subscribing to the Demiurge::Notifications::TickFinished notification as well. The :autosave_path option says where to put the autosave file when it occurs. The substring "%TICKS%" will be replaced with the number of ticks completed if it is part of the :autosave_path.

Parameters:

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

    Options for how to run the Demiurge engine.

Options Hash (options):

  • :engine (Demiurge::Engine)

    Demiurge simulation engine; if this option is passed, use it instead of creating a new one

  • :engine_text (Array)

    Array of 2-element arrays, each with a filename followed by World File DSL; passed to Demiurge::DSL.engine_from_dsl_text to create engine

  • :engine_files (Array)

    Array of filenames of World File DSL code; passed to Demiurge::DSL.engine_from_dsl_files to create engine.

  • :engine_dsl_dir (String)

    Path to a directory containing DSL files and (optionally) DSL Ruby extensions

  • :engine_restore_statefile (String)

    Path to most recent statedump file, which will be restored to the engine state

  • :ms_per_tick (Integer)

    How many milliseconds should occur between simulation ticks

  • :autosave_ticks (Integer)

    Dump state automatically every time this many ticks occur; set to 0 for no automatic state-dump; defaults to 600

  • :autosave_path (String)

    Write the autosave to this path; you can use %TICKS% in the path for a number of ticks completed; defaults to "state/autosave_%TICKS%.json"

Since:

  • 0.1.0



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/pixiurge/engine_connector.rb', line 108

def start_simulation(options = {})
  raise("Simulation is already configured!") if @engine_configured || @engine_started

  illegal_options = options.keys - SIMULATION_OPTIONS
  raise "Passed illegal options to #start_simulation! #{illegal_options.inspect}" unless illegal_options.empty?

  if options[:engine]
    engine = options[:engine]
  elsif options[:engine_text]
    engine = Demiurge::DSL.engine_from_dsl_text options[:engine_text]
  elsif options[:engine_files]
    engine = Demiurge::DSL.engine_from_dsl_files options[:engine_files]
  else
    dsl_dir = options[:engine_dsl_dir] || File.join(@root_dir, "world")

    # Require all Ruby extensions under the World dir
    ruby_extensions = Dir["#{dsl_dir}/**/extensions/**/*.rb"]
    ruby_extensions.each { |filename| require filename }

    # Load all Worldfile non-Ruby-extension files as World File DSL
    dsl_files = Dir["#{dsl_dir}/**/*.rb"] - ruby_extensions
    engine = Demiurge::DSL.engine_from_dsl_files *dsl_files
  end

  @ms_per_tick = options[:ms_per_tick] || 500
  @autosave_ticks = options[:autosave_ticks] || 600
  @autosave_path = options[:autosave_path] || "state/autosave_%TICKS%.json"

  # Subscribe to notifications and sync up with all existing engine
  # objects before we actually start the simulation.
  hook_up_engine(engine)

  if options[:engine_restore_statefile]
    last_statefile = options[:engine_restore_statefile]
    puts "Restoring state data from #{last_statefile.inspect}."
    state_data = MultiJson.load File.read(last_statefile)
    @engine.load_state_from_dump(state_data)
  end
  @engine_configured = true

  #start_engine_periodic_timer
end

#thin_eventmachine_loop(rack_app, port, ssl_key_path, ssl_cert_path) ⇒ 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.

Start the EventMachine loop to run Thin, the periodic timer and so on.

Since:

  • 0.1.0



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/pixiurge/engine_connector.rb', line 192

def thin_eventmachine_loop(rack_app, port, ssl_key_path, ssl_cert_path)
  # No luck with Puma - for now, hardcode using Thin
  Faye::WebSocket.load_adapter('thin')

  EventMachine.run {
    thin = Rack::Handler.get('thin')
    thin.run(rack_app, :Port => port) do |server|
      server.ssl_options = {
        # Supported options: http://www.rubydoc.info/github/eventmachine/eventmachine/EventMachine/Connection:start_tls
        :private_key_file => ssl_key_path,
        :cert_chain_file  => ssl_cert_path,
        :verify_peer => false,
      }
      server.ssl = true
    end
    Signal.trap("INT")  { EventMachine.stop }
    Signal.trap("TERM") { EventMachine.stop }

    # And now, the purpose of this whole loop - allowing WebSockets,
    # the Thin server *and* a single periodic timer to all run at
    # once without multiple threads, and still have the timer start
    # when the server does. *sigh*
    start_engine_periodic_timer
  }
  STDERR.puts "Killed by SIGINT or SIGTERM... Exiting!"
end