motel 0.2 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.rdoc +35 -0
  2. data/Rakefile +48 -0
  3. data/bin/clients/simple/main.rb +131 -0
  4. data/bin/server/main.rb +61 -0
  5. data/conf/motel-schema.xml +72 -0
  6. data/lib/motel/common.rb +35 -11
  7. data/lib/motel/dsl.rb +12 -0
  8. data/lib/motel/exceptions.rb +14 -0
  9. data/lib/motel/location.rb +100 -0
  10. data/lib/motel/{models → movement_strategies}/elliptical.rb +51 -89
  11. data/lib/motel/movement_strategies/linear.rb +54 -0
  12. data/lib/motel/movement_strategies/stopped.rb +18 -0
  13. data/lib/motel/movement_strategy.rb +29 -0
  14. data/lib/motel/runner.rb +94 -108
  15. data/lib/motel/simrpc.rb +131 -0
  16. data/lib/motel/thread_pool.rb +51 -0
  17. data/lib/motel.rb +9 -5
  18. data/spec/common_spec.rb +77 -0
  19. data/spec/dsl_spec.rb +27 -0
  20. data/spec/location_spec.rb +109 -0
  21. data/spec/movement_strategies/elliptical_spec.rb +90 -0
  22. data/spec/movement_strategies/linear_spec.rb +51 -0
  23. data/spec/movement_strategies/stopped_spec.rb +39 -0
  24. data/spec/movement_strategy_spec.rb +24 -0
  25. data/spec/runner_spec.rb +45 -0
  26. data/spec/simrpc_spec.rb +85 -0
  27. data/spec/spec_helper.rb +26 -0
  28. metadata +36 -34
  29. data/README +0 -0
  30. data/conf/amqp.yml +0 -13
  31. data/conf/database.yml +0 -41
  32. data/db/migrate/001_create_locations_and_movement_strategies.rb +0 -45
  33. data/db/migrate/002_create_linear_movement_strategy.rb +0 -23
  34. data/db/migrate/003_create_elliptical_movement_strategy.rb +0 -52
  35. data/lib/motel/loader.rb +0 -47
  36. data/lib/motel/models/linear.rb +0 -76
  37. data/lib/motel/models/location.rb +0 -109
  38. data/lib/motel/models/movement_strategy.rb +0 -82
  39. data/lib/motel/models/stopped.rb +0 -16
@@ -1,13 +1,13 @@
1
1
  # The Elliptcial MovementStrategy model definition
2
2
  #
3
- # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
4
- # See COPYING for the License of this software
3
+ # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
+ # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
5
5
 
6
6
  require 'motel/common'
7
- require 'motel/models/movement_strategy'
7
+ require 'motel/movement_strategy'
8
8
 
9
9
  module Motel
10
- module Models
10
+ module MovementStrategies
11
11
 
12
12
  # The Elliptical MovementStrategy moves a location
13
13
  # on an elliptical path described by major/minor
@@ -19,41 +19,44 @@ module Models
19
19
  # Lastly a speed value is required indicating the
20
20
  # angular velocity of the location.
21
21
  class Elliptical < MovementStrategy
22
+ attr_accessor :relative_to, :speed
22
23
 
23
- # Elliptical MovementStrategy must specify x,y,z components of
24
- # the major and minor axis direction vectors
25
- validates_presence_of [:direction_major_x,
26
- :direction_major_y,
27
- :direction_major_z,
28
- :direction_minor_x,
29
- :direction_minor_y,
30
- :direction_minor_z]
31
-
32
- # make sure the unit direction vectors are normal
33
- before_validation :normalize_direction_vectors
34
- def normalize_direction_vectors
35
- dx, dy, dz =
36
- normalize(direction_major_x, direction_major_y, direction_major_z)
37
- self.direction_major_x, self.direction_major_y, self.direction_major_z = dx, dy, dz
38
-
39
- dx, dy, dz =
40
- normalize(direction_minor_x, direction_minor_y, direction_minor_z)
41
- self.direction_minor_x, self.direction_minor_y, self.direction_minor_z = dx, dy, dz
42
- end
24
+ attr_accessor :eccentricity, :semi_latus_rectum
25
+
26
+ attr_accessor :direction_major_x, :direction_major_y, :direction_major_z,
27
+ :direction_minor_x, :direction_minor_y, :direction_minor_z
28
+
29
+ def initialize(args = {})
30
+ @relative_to = args[:relative_to] if args.has_key? :relative_to
31
+ @speed = args[:speed] if args.has_key? :speed
32
+ @eccentricity = args[:eccentricity] if args.has_key? :eccentricity
33
+ @semi_latus_rectum = args[:semi_latus_rectum] if args.has_key? :semi_latus_rectum
34
+
35
+ @direction_major_x = args[:direction_major_x] if args.has_key? :direction_major_x
36
+ @direction_major_y = args[:direction_major_y] if args.has_key? :direction_major_y
37
+ @direction_major_z = args[:direction_major_z] if args.has_key? :direction_major_z
43
38
 
39
+ @direction_minor_x = args[:direction_minor_x] if args.has_key? :direction_minor_x
40
+ @direction_minor_y = args[:direction_minor_y] if args.has_key? :direction_minor_y
41
+ @direction_minor_z = args[:direction_minor_z] if args.has_key? :direction_minor_z
44
42
 
45
- # Elliptical MovementStrategy must specify the angular velocity
46
- # at which the location is moving
47
- validates_presence_of :speed
48
- validates_numericality_of :speed,
49
- :greater_than_or_equal_to => 0,
50
- :less_than_or_equal_to => 2 * Math::PI
43
+ @direction_major_x = 1 if @direction_major_x.nil?
44
+ @direction_major_y = 0 if @direction_major_y.nil?
45
+ @direction_major_z = 0 if @direction_major_z.nil?
46
+ @direction_minor_x = 0 if @direction_minor_x.nil?
47
+ @direction_minor_y = 1 if @direction_minor_y.nil?
48
+ @direction_minor_z = 0 if @direction_minor_z.nil?
51
49
 
52
- # the eccentricity of the ellipse
53
- validates_presence_of :eccentricity
54
- validates_numericality_of :eccentricity,
55
- :greater_than_or_equal_to => 0,
56
- :less_than_or_equal_to => 1
50
+ @direction_major_x, @direction_major_y, @direction_major_z =
51
+ normalize(@direction_major_x, @direction_major_y, @direction_major_z)
52
+
53
+ @direction_minor_x, @direction_minor_y, @direction_minor_z =
54
+ normalize(@direction_minor_x, @direction_minor_y, @direction_minor_z)
55
+
56
+ unless orthogonal?(@direction_major_x, @direction_major_y, @direction_major_z, @direction_minor_x, @direction_minor_y, @direction_minor_z)
57
+ raise InvalidMovementStrategy.new("elliptical direction vectors not orthogonal")
58
+ end
59
+ end
57
60
 
58
61
  def e
59
62
  eccentricity
@@ -62,11 +65,6 @@ class Elliptical < MovementStrategy
62
65
  eccentricity= v
63
66
  end
64
67
 
65
- # the semi_latus_rectum of the ellipse
66
- validates_presence_of :semi_latus_rectum
67
- validates_numericality_of :semi_latus_rectum,
68
- :greater_than_or_equal_to => 0
69
-
70
68
  def p
71
69
  semi_latus_rectum
72
70
  end
@@ -78,56 +76,22 @@ class Elliptical < MovementStrategy
78
76
  RELATIVE_TO_CENTER = "center"
79
77
  RELATIVE_TO_FOCI = "foci"
80
78
 
81
- # must be relative to a parent center or foci
82
- validates_presence_of :relative_to
83
- validates_inclusion_of :relative_to,
84
- :in => [ RELATIVE_TO_CENTER, RELATIVE_TO_FOCI ]
85
-
86
- # ActiveRecord::Base::validate
87
- def validate
88
- errors.add("direction vectors must be orthogonal") unless orthogonal?(direction_major_x, direction_major_y, direction_major_z,
89
- direction_minor_x, direction_minor_y, direction_minor_z)
90
- end
91
-
92
- # convert non-nil elliptical movement strategy attributes to a hash
93
- def to_h
94
- result = {}
95
- result[:speed] = speed unless speed.nil?
96
- result[:eccentricity] = eccentricity unless eccentricity.nil?
97
- result[:semi_latus_rectum] = semi_latus_rectum unless semi_latus_rectum.nil?
98
- result[:relative_to] = relative_to unless relative_to.nil?
99
- result[:direction_major_x] = direction_major_x unless direction_major_x.nil?
100
- result[:direction_major_y] = direction_major_y unless direction_major_y.nil?
101
- result[:direction_major_z] = direction_major_z unless direction_major_z.nil?
102
- result[:direction_minor_x] = direction_minor_x unless direction_minor_x.nil?
103
- result[:direction_minor_y] = direction_minor_y unless direction_minor_y.nil?
104
- result[:direction_minor_z] = direction_minor_z unless direction_minor_z.nil?
105
- return result
106
- end
107
-
108
- # convert elliptical movement strategy to a string
109
- def to_s
110
- super + "; speed:#{speed}; eccentricity:#{eccentricity}; semi_latus_rectum:#{semi_latus_rectum}; relative_to:#{relative_to}; " +
111
- "direction_major_x:#{direction_major_x}; direction_major_y:#{direction_major_y}; direction_major_z:#{direction_major_z}; " +
112
- "direction_minor_x:#{direction_minor_x}; direction_minor_y:#{direction_minor_y}; direction_minor_z:#{direction_minor_z}"
113
- end
114
-
115
79
  # Motel::Models::MovementStrategy::move
116
80
  def move(location, elapsed_seconds)
117
- # make sure this movement strategy is valid
118
- unless valid?
119
- Logger.warn "elliptical movement strategy not valid, not proceeding with move"
120
- return
121
- end
81
+ # FIXME make sure this movement strategy is valid
82
+ #unless valid?
83
+ # Logger.warn "elliptical movement strategy not valid, not proceeding with move"
84
+ # return
85
+ #end
122
86
 
123
- # make sure location is on ellipse
124
- unless location_valid? location
125
- cx,cy,cz = closest_coordinates location
126
- Logger.warn "location #{location} not on ellipse, the closest location is #{cl}, not proceeding with move"
127
- return
128
- end
87
+ ## FIXME make sure location is on ellipse
88
+ #unless location_valid? location
89
+ # cx,cy,cz = closest_coordinates location
90
+ # Logger.warn "location #{location} not on ellipse, the closest location is #{cl}, not proceeding with move"
91
+ # return
92
+ #end
129
93
 
130
- Logger.debug "moving location #{location} via elliptical movement strategy"
94
+ Logger.debug "moving location #{location.id} via elliptical movement strategy"
131
95
 
132
96
  # calculate distance moved and update x,y,z accordingly
133
97
  distance = speed * elapsed_seconds
@@ -136,8 +100,6 @@ class Elliptical < MovementStrategy
136
100
  location.x = nX
137
101
  location.y = nY
138
102
  location.z = nZ
139
-
140
- Logger.debug "moved location #{location} via elliptical movement strategy"
141
103
  end
142
104
 
143
105
  private
@@ -255,5 +217,5 @@ class Elliptical < MovementStrategy
255
217
 
256
218
  end
257
219
 
258
- end # module Models
220
+ end # module MovementStrategies
259
221
  end # module Motel
@@ -0,0 +1,54 @@
1
+ # The Linear MovementStrategy model definition
2
+ #
3
+ # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
+ # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
5
+
6
+ require 'motel/common'
7
+ require 'motel/movement_strategy'
8
+
9
+ module Motel
10
+ module MovementStrategies
11
+
12
+ # The Linear MovementStrategy moves a location
13
+ # in a linear manner as defined by a
14
+ # unit direction vector and a floating point
15
+ # speed
16
+ class Linear < MovementStrategy
17
+ attr_accessor :direction_vector_x, :direction_vector_y, :direction_vector_z
18
+
19
+ attr_accessor :speed
20
+
21
+ def initialize(args = {})
22
+ @direction_vector_x = args[:direction_vector_x] if args.has_key? :direction_vector_x
23
+ @direction_vector_y = args[:direction_vector_y] if args.has_key? :direction_vector_y
24
+ @direction_vector_z = args[:direction_vector_z] if args.has_key? :direction_vector_z
25
+ @speed = args[:speed] if args.has_key? :speed
26
+
27
+ # normalize direction vector
28
+ @direction_vector_x, @direction_vector_y, @direction_vector_z =
29
+ normalize(@direction_vector_x, @direction_vector_y, @direction_vector_z)
30
+ end
31
+
32
+
33
+ # Motel::Models::MovementStrategy::move
34
+ def move(location, elapsed_seconds)
35
+ #unless valid?
36
+ # Logger.warn "linear movement strategy not valid, not proceeding with move"
37
+ # return
38
+ #end
39
+
40
+ Logger.debug "moving location #{location.id} via linear movement strategy " +
41
+ "#{speed} #{direction_vector_x}/#{direction_vector_y}/#{direction_vector_z}"
42
+
43
+ # calculate distance and update x,y,z accordingly
44
+ distance = speed * elapsed_seconds
45
+
46
+ location.x += distance * direction_vector_x
47
+ location.y += distance * direction_vector_y
48
+ location.z += distance * direction_vector_z
49
+ end
50
+
51
+ end
52
+
53
+ end # module Models
54
+ end # module Motel
@@ -0,0 +1,18 @@
1
+ # The Stopped MovementStrategy model definition
2
+ #
3
+ # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
+ # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
5
+
6
+ require 'singleton'
7
+ require 'motel/movement_strategy'
8
+
9
+ module Motel
10
+ module MovementStrategies
11
+
12
+ # Stopped is the default MovementStrategy which does nothing
13
+ class Stopped < MovementStrategy
14
+ include Singleton
15
+ end
16
+
17
+ end # module MovementStrategies
18
+ end # module Motel
@@ -0,0 +1,29 @@
1
+ # The MovementStrategy entity
2
+ #
3
+ # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
+ # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
5
+
6
+ require 'motel/common'
7
+ require 'motel/location'
8
+
9
+ module Motel
10
+
11
+ # MovementStrategy subclasses define the rules and params which
12
+ # a location changes its position.
13
+ class MovementStrategy
14
+ attr_accessor :step_delay
15
+
16
+ def initialize(args = {})
17
+ @step_delay = 5
18
+ @movement_callbacks = []
19
+
20
+ @step_delay = args[:step_delay] if args.has_key? :step_delay
21
+ end
22
+
23
+ # default movement strategy is to do nothing
24
+ def move(location, elapsed_seconds)
25
+ end
26
+
27
+ end
28
+
29
+ end # module Motel
data/lib/motel/runner.rb CHANGED
@@ -2,133 +2,119 @@
2
2
  # and is responsible for managing locations and moving them according to
3
3
  # their corresponding movement_strategies
4
4
  #
5
- # Copyright (C) 2009 Mohammed Morsi <movitto@yahoo.com>
6
- # See COPYING for the License of this software
5
+ # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
6
+ # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
7
7
 
8
- module Motel
8
+ require 'singleton'
9
+ require 'motel/thread_pool'
9
10
 
10
- # A LocationRunner runs a Location by moving it via
11
- # its associated MovementStrategy.
12
- class LocationRunner
13
- attr_reader :location
14
- attr_reader :run_thread
15
-
16
- def initialize(location)
17
- return if location.nil? || location.id.nil? || location.class != Location
18
-
19
- @terminate = false
20
- @location = location
21
- @location_lock = Mutex.new
22
- Logger.info " running location " + location.to_s
23
-
24
- # TODO at some point use a thread pool approach
25
- @run_thread = Thread.new { run_move_cycle(location) }
26
- end
27
-
28
- # Terminate run cycle, stopping location movement.
29
- # After this the runner cannot be used again
30
- def terminate
31
- @terminate = true
32
- @location_lock.synchronize{
33
- unless run_thread.nil?
34
- @run_thread.join
35
- @run_thread = nil
36
- end
37
- }
38
- end
39
-
40
- private
41
-
42
- # Launched in a seperate thread, run_move_cycle
43
- # runs a location according to its associated
44
- # movement strategy with a specified delay between
45
- # runs. Terminated when the Runner.terminate
46
- # method is invoked
47
- def run_move_cycle(location)
48
- # track the time between runs
49
- start_time = Time.now
50
-
51
- # run until we are instructed not to
52
- until(@terminate) do
53
- Logger.debug "runner invoking move on location " + location.to_s + " via " + location.movement_strategy.type.to_s + " movement strategy"
54
-
55
- ## perform the actual move
56
- location.movement_strategy.move location, start_time - Time.now
57
- start_time = Time.now
58
-
59
- # TODO see if we've actually moved b4 invoking callbacks
60
- location.movement_strategy.movement_callbacks.each { |callback|
61
- callback.call(location)
62
- }
11
+ module Motel
63
12
 
64
- ## delay as long as the strategy tells us to
65
- sleep location.movement_strategy.step_delay
66
- end
67
- end
13
+ # Motel::Runner is a singleton class/object which acts as the primary
14
+ # mechanism to run locations in the system. It contains a thread pool
15
+ # which contains a specified number of threads which to move the managed
16
+ # locations in accordance to their location strategies.
17
+ class Runner
18
+ include Singleton
68
19
 
69
- end
20
+ # locations being managed
21
+ attr_accessor :locations
70
22
 
23
+ # for testing purposes
24
+ attr_reader :thread_pool, :terminate, :run_thread
71
25
 
72
- # A Runner manages groups of LocationRunner instances
73
- # Restricts use to a single instance, obtained via the
74
- # 'get' method
75
- class Runner
26
+ def initialize(args = {})
27
+ @terminate = false
28
+ @locations = []
29
+ @locations_lock = Mutex.new
76
30
 
77
- private
78
-
79
- # Default class constructor
80
- # private as runner should be accessed through singleton 'get' method
81
- def initialize
82
- # set to true to terminate the runner
83
- @terminate = false
84
-
85
- # locations is a list of instances of LocationRunner to manage
86
- @location_runners = []
87
- @runners_lock = Mutex.new
31
+ @run_thread = nil
32
+ @run_delay = 2 # FIXME scale delay (only needed if locations is empty or has very few simple elements)
88
33
  end
89
34
 
90
- public
35
+ # Empty the list of locations being managed/tracked
36
+ def clear
37
+ @locations_lock.synchronize {
38
+ @locations.clear
39
+ }
40
+ end
91
41
 
92
- # singleton getter
93
- def self.get
94
- @@singleton_instance = Runner.new if !defined? @@singleton_instance || @@singleton_instance.nil?
95
- return @@singleton_instance
42
+ # add location to runner to be managed, after this is called, the location's
43
+ # movement strategy's move method will be invoked periodically
44
+ def run(location)
45
+ @locations_lock.synchronize {
46
+ Logger.debug "adding location #{location.id} to run queue"
47
+ @locations.push location
48
+ }
96
49
  end
97
50
 
98
- attr_reader :location_runners
51
+ # Start moving the locations. If :async => true is passed in, this will immediately
52
+ # return, else this will block until stop is called.
53
+ def start(args = {})
54
+ num_threads = 5
55
+ num_threads = args[:num_threads] if args.has_key? :num_threads
56
+ @terminate = false
57
+ @thread_pool = ThreadPool.new(num_threads)
58
+
59
+ if args.has_key?(:async) && args[:async]
60
+ Logger.debug "starting async motel runner"
61
+ @run_thread = Thread.new { run_cycle }
62
+ else
63
+ Logger.debug "starting motel runner"
64
+ run_cycle
65
+ end
99
66
 
100
- # helper method, return all locations associated w/ runners
101
- def locations
102
- [] if @location_runners.nil? || @location_runners.size == 0
103
- @location_runners.collect { |runner| runner.location }
104
67
  end
105
68
 
106
- # clear all location_runners
107
- def clear
108
- @location_runners.clear
69
+ # Stop locations movement
70
+ def stop
71
+ Logger.debug "stopping motel runner"
72
+ @terminate = true
73
+ @thread_pool.shutdown
74
+ join
75
+ Logger.debug "motel runner stopped"
109
76
  end
110
77
 
111
- # Terminate all run cycles, stopping all location movements.
112
- # After this the runner cannot be used again
113
- def terminate
114
- @terminate = true
115
- @runners_lock.synchronize{
116
- @location_runners.each { |runner|
117
- runner.terminate
118
- }
119
- }
78
+ # Block until runner is shutdown before returning
79
+ def join
80
+ @run_thread.join unless @run_thread.nil?
81
+ @run_thread = nil
120
82
  end
121
83
 
122
- # Run a location using this runner. If the location
123
- # is already being run by this runner, do nothing.
124
- # A new thread will be launched to run the actual move cycle
125
- def run(location)
126
- @runners_lock.synchronize{
127
- unless @location_runners.find { |l| l.location.id == location.id}
128
- @location_runners.push LocationRunner.new(location)
129
- end
130
- }
131
- end
84
+ private
85
+
86
+ # Internal helper method performing main runner operations
87
+ def run_cycle
88
+ # track time between runs
89
+ start_time = Time.now
90
+
91
+ until @terminate
92
+ # copy locations into temp 2nd array so we're not holding up lock on locations array
93
+ tlocations = []
94
+ @locations_lock.synchronize {
95
+ @locations.each { |loc| tlocations.push loc }
96
+ }
97
+
98
+ tlocations.each { |loc|
99
+ @thread_pool.dispatch(loc) { |loc|
100
+ Logger.debug "runner moving location #{loc.id} via #{loc.movement_strategy.class.to_s}"
101
+
102
+ loc.movement_strategy.move loc, start_time - Time.now
103
+ start_time = Time.now
104
+
105
+ # TODO see if loc coordinates changed b4 doing this
106
+ loc.movement_callbacks.each { |callback|
107
+ callback.call(loc)
108
+ }
109
+
110
+ ## delay as long as the strategy tells us to
111
+ sleep loc.movement_strategy.step_delay
112
+ }
113
+ }
114
+
115
+ sleep @run_delay
116
+ end
117
+ end
132
118
 
133
119
  end
134
120
 
@@ -0,0 +1,131 @@
1
+ # Motel simrpc adapter
2
+ #
3
+ # Copyright (C) 2010 Mohammed Morsi <movitto@yahoo.com>
4
+ # Licensed under the AGPLv3+ http://www.gnu.org/licenses/agpl.txt
5
+
6
+ require 'simrpc'
7
+
8
+ module Motel
9
+
10
+ # Motel::Server defines a server endpoint which manages locations
11
+ # and responds to simrpc requests
12
+ class Server
13
+ def initialize(args = {})
14
+ simrpc_args = args
15
+ simrpc_args[:id] = "location-server"
16
+
17
+ # create a simprc node
18
+ @simrpc_node = Simrpc::Node.new(simrpc_args)
19
+
20
+ # register handlers for the various motel simrpc methods
21
+ @simrpc_node.handle_method("get_location") { |location_id|
22
+ Logger.info "received get location #{location_id} request"
23
+ loc = nil
24
+ begin
25
+ loc = Runner.instance.locations.find { |loc| loc.id == location_id }
26
+ # FIXME traverse all of loc's descendants, and if remote location
27
+ # server is specified, send request to get child location, swapping
28
+ # it in for the one thats there
29
+ rescue Exception => e
30
+ Logger.warn "get location #{location_id} failed w/ exception #{e}"
31
+ end
32
+ Logger.info "get location #{location_id} request returning #{loc}"
33
+ loc
34
+ }
35
+
36
+ @simrpc_node.handle_method("create_location") { |location_id|
37
+ Logger.info "received create location #{location_id} request"
38
+ success = true
39
+ begin
40
+ Runner.instance.run Location.new(:id => location_id)
41
+ # TODO decendants support w/ remote option (create additional locations on other servers)
42
+ rescue Exception => e
43
+ Logger.warn "create location #{location_id} failed w/ exception #{e}"
44
+ success = false
45
+ end
46
+ Logger.info "create location #{location_id} request returning #{success}"
47
+ success
48
+ }
49
+
50
+ @simrpc_node.handle_method("update_location") { |location|
51
+ Logger.info "received update location #{location.id} request"
52
+ success = true
53
+ if location.nil?
54
+ success = false
55
+ else
56
+ rloc = Runner.instance.locations.find { |loc| loc.id == location.id }
57
+ begin
58
+ Logger.info "updating location #{location.id} with #{location}/#{location.movement_strategy}"
59
+ rloc.update(location)
60
+ rescue Exception => e
61
+ Logger.warn "update location #{location.id} failed w/ exception #{e}"
62
+ success = false
63
+ end
64
+ end
65
+ Logger.info "update location #{location.id} returning #{success}"
66
+ success
67
+ }
68
+
69
+ @simrpc_node.handle_method("subscribe_to_location") { |location_id, client_id|
70
+ Logger.info "subscribe client #{client_id} to location #{location_id} request received"
71
+ loc = Runner.instance.locations.find { |loc| loc.id == location_id }
72
+ success = true
73
+ if loc.nil?
74
+ success = false
75
+ else
76
+ loc.movement_callbacks.push lambda { |location|
77
+ # send location to client
78
+ @simrpc_node.send_method("location_moved", client_id, location)
79
+ }
80
+ end
81
+ Logger.info "subscribe client #{client_id} to location #{location_id} returning #{success}"
82
+ success
83
+ }
84
+ end
85
+
86
+ def join
87
+ @simrpc_node.join
88
+ end
89
+ end
90
+
91
+ # Client defines a client endpoint that performs
92
+ # a request against a Motel Server
93
+ class Client
94
+ # should be a callable object that takes a location to be
95
+ # invoked when the server sends a location to the client
96
+ attr_writer :on_location_received
97
+
98
+ # Initialize the client with various args, all of which are passed onto Simrpc::Node constructor
99
+ def initialize(args = {})
100
+ simrpc_args = args
101
+ simrpc_args[:destination] = "location-server"
102
+
103
+ @simrpc_node = Simrpc::Node.new(simrpc_args)
104
+ end
105
+
106
+ def join
107
+ @simrpc_node.join
108
+ end
109
+
110
+ def request(target, *args)
111
+ method_missing(target, *args)
112
+ end
113
+
114
+ # pass simrpc method requests right onto the simrpc node
115
+ def method_missing(method_id, *args)
116
+ # special case for subsscribe_to_location,
117
+ if method_id == :subscribe_to_location
118
+ # add simrpc node id onto args list
119
+ args.push @simrpc_node.id
120
+
121
+ # handle location updates from the server, & issue subscribe request
122
+ @simrpc_node.handle_method("location_moved") { |location|
123
+ Logger.info "location #{location.id} moved"
124
+ @on_location_received.call(location) unless @on_location_received.nil?
125
+ }
126
+ end
127
+ @simrpc_node.method_missing(method_id, *args)
128
+ end
129
+ end
130
+
131
+ end