motel 0.2 → 0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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