tef-animation 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3bc7c194c8425278ad0369c293684b65008f293f
4
- data.tar.gz: b59ce88b52067a8c5b2b8c2070e7abf408d073e5
3
+ metadata.gz: 92421ddea4eb5a58abffa7fdb3ec987f645281d7
4
+ data.tar.gz: c2fc1d82fdffc6ecc435ed47554f3dca0bb5834b
5
5
  SHA512:
6
- metadata.gz: 9e1c4ed9339a1054e69a5b2074e6f1741da37c3490e7cac055c1f696ad3e6365ecc2b2dda615265df8d3ebd072f1a9b9665d3ca9ef9e31bb9a8fd42c53a5c02a
7
- data.tar.gz: 13d28689ecdd5bba1fbb8028076b39ba3623b62b0f7b8f7e38cafdf108941395907790509bef2d340939eb4b1e817561f12d5a01185e95466d20fb2b5e417b74
6
+ metadata.gz: df1fd4e30609e37ee34281ccad04a759af87155757af7886d163c7082caa3e9427854b09353d042342ebd2e8e5d00c4ec3b79289bb7c7ecbff21353d1abb9e10
7
+ data.tar.gz: 96069806358729bff76ecebe28a925caf52d8587acc9ba05dc7950595e9ad1f3b5980b32a16ca84a074fd5f9ef896eda0c5d496061ab1c8c08f42806051cead0
@@ -3,24 +3,80 @@ require_relative 'Value.rb'
3
3
  require_relative 'Color.rb'
4
4
  require_relative 'Coordinate.rb'
5
5
 
6
+ # TheElectricFursuits module.
7
+ # @see https://github.com/TheElectricFursuits
6
8
  module TEF
9
+ # Animation-Related Module
10
+ #
11
+ # This module wraps all classes related to TEF 'Synth'-Line animation.
12
+ # They are meant to provide an abstraction layer over the hardware-implemented
13
+ # animations that run on slave devices, such as the FurComs-Connected Synth Bit,
14
+ # and give the user full access to high-level functions such as configuring
15
+ # named parameters, setting up value smoothing and transitions, and
16
+ # creating and deleting objects.
7
17
  module Animation
18
+ # Animatable base class.
19
+ #
20
+ # This class implements all necessary functions to write a custom
21
+ # animatable object with ease. It provides a DSL to easily
22
+ # register new animatable colours, values and coordinates, and handles
23
+ # updating and configuring them.
24
+ #
25
+ # By inheriting from this base class, the user must only define
26
+ # the animatable properties of their object by using:
27
+ # - {Animatable#animatable_attr}
28
+ # - {Animatable#animatable_color}
29
+ # - {Animatable#animatable_coordinate}
30
+ #
31
+ # The object must also be passed to the Animation handler, by calling:
32
+ # handler['S123M123'] = your_instance;
8
33
  class Animatable
34
+ # @return [String, nil] Module ID of this object as in SxxMxx, or nil.
9
35
  attr_reader :module_id
36
+
37
+ # @return [Time, nil] If set, returns the time this object will
38
+ # auto-delete. This will not delete the Ruby object, but it will
39
+ # send a delete request to the animation slaves.
10
40
  attr_reader :death_time
11
41
 
42
+ # @return [Numeric, nil] If set, returns the time (in s) that this
43
+ # object will live for. If the object is currently a live animation,
44
+ # setting this will make the object die in the given number of
45
+ # seconds. If it is not currently animated, it will make the object
46
+ # die after the given number of seconds, starting from when it
47
+ # was registered with the handler.
12
48
  attr_reader :death_delay
13
49
 
50
+ # @private
51
+ # @return [Array<{Symbol, Integer>] List of registered animatable attributes
14
52
  def self.get_attr_list
15
53
  @class_attribute_list ||= {}
16
54
  end
55
+ # @private
56
+ # @return [Array<Symbol, Integer>] List of registered animatable colours
17
57
  def self.get_color_list
18
58
  @class_color_list ||= {}
19
59
  end
60
+ # @private
61
+ # @return [Array<Symbol, Integer>] List of registered animatable coordinates
20
62
  def self.get_coordinate_list
21
63
  @class_coordinate_list ||= {}
22
64
  end
23
65
 
66
+ # Defines a new animatable attribute.
67
+ #
68
+ # The defined attribute will become accessible via a getter and
69
+ # convenience setter function, giving the user access to the
70
+ # created {Value} instance.
71
+ #
72
+ # @param name [Symbol] Name of the attribute, as symbol. Will
73
+ # create getter and setter functions.
74
+ # @param id [Numeric] Address of the animatable attribute. Must match
75
+ # the address defined in the animation C++ code!
76
+ #
77
+ # @!macro [attach] anim.attribute
78
+ # @!attribute [rw] $1
79
+ # @return [Value] Animated value '$1' (ID $2)
24
80
  def self.animatable_attr(name, id)
25
81
  get_attr_list()[name] = id
26
82
 
@@ -37,6 +93,19 @@ module TEF
37
93
  end
38
94
  end
39
95
 
96
+ # Defines a new animatable color
97
+ #
98
+ # The defined color will become accessible via a getter and
99
+ # convenience setter function, giving the user access to the
100
+ # created {Color} instance.
101
+ #
102
+ # @param name [Symbol] Name of the color, as symbol. Will
103
+ # create getter and setter functions.
104
+ # @param id [Numeric] Address of the animatable color. Must match
105
+ # the address defined in the animation C++ code!
106
+ # @!macro [attach] anim.color
107
+ # @!attribute [rw] $1
108
+ # @return [Color] Animated color '$1' (ID $2)
40
109
  def self.animatable_color(name, id)
41
110
  get_color_list()[name] = id
42
111
 
@@ -49,6 +118,20 @@ module TEF
49
118
  end
50
119
  end
51
120
 
121
+ # Defines a new animatable coordinate.
122
+ #
123
+ # The defined coordinate will become accessible via a getter and
124
+ # convenience setter function, giving the user access to the
125
+ # created {Value} instance.
126
+ #
127
+ # @param name [Symbol] Name of the coordinate, as symbol. Will
128
+ # create getter and setter functions.
129
+ # @param id [Numeric] Starting address of the coordinates. Expects
130
+ # the coordinate parameters to be sequential!
131
+ #
132
+ # @!macro [attach] anim.attribute
133
+ # @!attribute [rw] $1
134
+ # @return [Coordinate] Coordinate for '$1' (ID $2)
52
135
  def self.animatable_coordinate(name, start)
53
136
  get_coordinate_list()[name] = start
54
137
 
@@ -57,6 +140,10 @@ module TEF
57
140
  end
58
141
  end
59
142
 
143
+ # Initialize a generic animatable object.
144
+ #
145
+ # This will initialize the necessary internal hashes
146
+ # that contain the animatable attributes, colors and coordinates.
60
147
  def initialize()
61
148
  @animatable_attributes = {}
62
149
  @animatable_colors = {}
@@ -84,6 +171,21 @@ module TEF
84
171
  @death_time_changed = true
85
172
  end
86
173
 
174
+ # Make this object die in a given number of seconds.
175
+ # After the timer set here expires, the object will automatically
176
+ # be deleted. As the slaves will do it automatically, it is good
177
+ # practice to give any temporary object a death time, if possible, to
178
+ # have them automatically cleaned up.
179
+ #
180
+ # It is possible to remove a death time or extend it, by calling
181
+ # this function another time.
182
+ #
183
+ # @note This will not delete the Ruby object, but it will be
184
+ # unregistered from the {Animation::Handler}. It can safely
185
+ # be re-registered to re-create a new object.
186
+ #
187
+ # @param t [Numeric, nil] The time, in seconds until this object will
188
+ # be killed.
87
189
  def die_in(t)
88
190
  if t.nil?
89
191
  self.death_time = nil
@@ -98,10 +200,25 @@ module TEF
98
200
  @death_delay = t
99
201
  end
100
202
 
203
+ # Instantly deletes this object from the animation slaves.
204
+ # @see #die_in
101
205
  def die!
102
206
  self.death_time = Time.at(0)
103
207
  end
104
208
 
209
+ # Quickly configure this object.
210
+ #
211
+ # This is a convenience function to very quickly and easily
212
+ # (re)configure this animatable object. It will take a hash
213
+ # or named options, and will pass the values of the hash to
214
+ # the matching {Value}, {Color} or {Coordinate}
215
+ #
216
+ # @see Value#configure
217
+ # @see Color#configure
218
+ # @see Coordinate#configure
219
+ #
220
+ # @example
221
+ # my_box.configure up: 10, down: 5, left: { dampen: 0.4, add: 2 }
105
222
  def configure(h = nil, **opts)
106
223
  h ||= opts;
107
224
 
@@ -118,17 +235,29 @@ module TEF
118
235
  end
119
236
  end
120
237
 
238
+ # Return a default creation string.
239
+ #
240
+ # This function MUST be overwritten by the user to provide a proper
241
+ # creation string! It will be sent over FurComs via the 'NEW' topic
242
+ # and must contain all necessary information for the slaves to
243
+ # construct the matching object.
121
244
  def creation_string
122
- ""
245
+ ''
123
246
  end
124
247
 
125
- def all_animatable_attributes
248
+ private def all_animatable_attributes
126
249
  out = @animatable_attributes.values
127
250
  out += @animatable_coordinates.values.map(&:animatable_attributes)
128
251
 
129
252
  out.flatten
130
253
  end
131
254
 
255
+ # @private
256
+ # Set the module ID of this object manually.
257
+ #
258
+ # @note Do not call this function, it is purely internal.
259
+ # Only the {Handler} may set the module ID, otherwise undefined
260
+ # behavior may occour!
132
261
  def module_id=(new_str)
133
262
  unless new_str =~ /^S[\d]{1,3}M[\d]{1,3}$/ || new_str.nil?
134
263
  raise ArgumentError, 'Target must be a valid Animation Value'
@@ -143,6 +272,10 @@ module TEF
143
272
  @module_id = new_str
144
273
  end
145
274
 
275
+ # @private
276
+ #
277
+ # Returns a String to be sent via FurComs to configure the object
278
+ # to die in a certain number of seconds.
146
279
  def death_time_string
147
280
  return nil unless @death_time_changed
148
281
 
@@ -155,6 +288,23 @@ module TEF
155
288
  "#{@module_id} #{remaining_time};"
156
289
  end
157
290
 
291
+ # @private
292
+ #
293
+ # Returns an array of 'SET' Hashes, to be sent over the FurComs
294
+ # bus, 'SET' topic. They represent raw value configurations of the
295
+ # animatable values.
296
+ #
297
+ # The {Handler} that called this function has the duty of packing
298
+ # them into complete commands, as the initial module ID can be
299
+ # left out for sequential value access, saving a few bytes
300
+ # each transfer.
301
+ #
302
+ # @note Never call this unless you are the {Handler}! It will
303
+ # mark the changes as already transmitted, so manually calling
304
+ # this will cause data loss!
305
+ #
306
+ # @return [Array<Hash>] An array containing Hashes outlining each
307
+ # value's SET string.
158
308
  def get_set_strings()
159
309
  return [] unless @module_id
160
310
 
@@ -170,6 +320,9 @@ module TEF
170
320
  out_elements
171
321
  end
172
322
 
323
+ # @private
324
+ #
325
+ # @see #get_set_strings
173
326
  def get_setc_strings()
174
327
  return [] unless @module_id
175
328
 
@@ -3,11 +3,36 @@ require 'xasin_logger'
3
3
 
4
4
  require_relative 'Animatable.rb'
5
5
 
6
+ # TheElectricFursuits module.
7
+ # @see https://github.com/TheElectricFursuits
6
8
  module TEF
7
9
  module Animation
10
+ # Animation object handler.
11
+ #
12
+ # This class is the handler for one coherent animation system.
13
+ # Its main purpose is to (de)register animatable objects, distributing
14
+ # IDs and handing out packed update messages onto a FurComs bus.
8
15
  class Handler
9
16
  include XasLogger::Mix
10
17
 
18
+ # Initialize a Handler.
19
+ #
20
+ # This will initialize a handler and connect it to the passed FurComs
21
+ # bus.
22
+ # The user may immediately start registering objects after creating
23
+ # this class, though if the FurComs bus is not connected yet this may
24
+ # cause very early messages to be lost!
25
+ #
26
+ # @param [FurComs::Base] furcoms_bus The FurComs Bus connecting
27
+ # instance. Must support send_message(topic, data), nothing else.
28
+ #
29
+ # @note {#update_tick} MUST be called after performing any
30
+ # changes, be it adding a new {Animatable} or changing values
31
+ # of a known animatable. It is recommended to call this function
32
+ # only after all changes for a given tick have been performed, so
33
+ # that they can be sent over as a batch.
34
+ # {Sequencing::Player#after_exec} can be used to register a callback
35
+ # to call {#update_tick}.
11
36
  def initialize(furcoms_bus)
12
37
  @furcoms = furcoms_bus
13
38
 
@@ -20,6 +45,13 @@ module TEF
20
45
  init_x_log('Animation Handler')
21
46
  end
22
47
 
48
+ # Create a String key representation.
49
+ #
50
+ # This will take either a hash or a String,
51
+ # convert it to a standard String key representation, and then
52
+ # verify it for correctness.
53
+ # @param [String, Hash] key the key to clean up.
54
+ # @return [String] Cleaned string
23
55
  def clean_key(key)
24
56
  key = 'S%<S>dM%<M>d' % key if key.is_a? Hash
25
57
 
@@ -30,10 +62,20 @@ module TEF
30
62
  key
31
63
  end
32
64
 
65
+ # @return [Animatable] Returns the Animatable with matching key.
33
66
  def [](key)
34
67
  @active_animations[clean_key key]
35
68
  end
36
69
 
70
+ # Register or replace a {Animatable}.
71
+ #
72
+ # This lets the user register a new {Animatable} object with given
73
+ # key, or replace or delete a pre-existing animation object.
74
+ #
75
+ # @param [String, Hash] key The key to write into.
76
+ # @param [Animatable, nil] Will delete any pre-existing animation, then
77
+ # either replace it with the given {Animatable}, or, if nil was
78
+ # given, will delete the animation entry.
37
79
  def []=(key, new_obj)
38
80
  @animation_mutex.synchronize {
39
81
  key = clean_key key
@@ -42,18 +84,23 @@ module TEF
42
84
 
43
85
  if new_obj.nil?
44
86
  @pending_deletions[key] = true
87
+ @pending_creations.delete key
45
88
 
46
89
  elsif new_obj.is_a? Animatable
47
90
  new_obj.module_id = key
48
91
 
49
92
  @active_animations[key] = new_obj
50
93
  @pending_creations[key] = new_obj.creation_string
94
+ @pending_deletions.delete key
51
95
  else
52
96
  raise ArgumentError, 'New animation object is of invalid type'
53
97
  end
54
98
  }
55
99
  end
56
100
 
101
+ # Internal function to join an Array of strings and send it onto
102
+ # the FurComs bus on a given topic.
103
+ # Useful to batch-send, which is a bit more efficient.
57
104
  private def join_and_send(topic, data)
58
105
  return if data.empty?
59
106
 
@@ -70,8 +117,12 @@ module TEF
70
117
  @furcoms.send_message topic, out_str
71
118
  end
72
119
 
73
- # TODO Replace this with a time-synched system
74
- # using the main synch time
120
+ # Internal function to send out death updates.
121
+ # This will send out to topic DTIME, updating the Animatable's
122
+ # death_time, as well as sending to DELETE for deleted animations.
123
+ #
124
+ # @todo Replace this with a time-synched system
125
+ # using the main synch time, rather than using relative time.
75
126
  private def update_deaths
76
127
  death_reconfigs = [];
77
128
 
@@ -99,6 +150,9 @@ module TEF
99
150
  join_and_send('DELETE', deletions)
100
151
  end
101
152
 
153
+ # Private function to send out creation strings.
154
+ # Will simply send a string per new object, as we do not often
155
+ # need to initialize objects.
102
156
  private def update_creations
103
157
  @pending_creations.each do |key, val|
104
158
  @furcoms.send_message('NEW', val)
@@ -107,6 +161,15 @@ module TEF
107
161
  @pending_creations = {}
108
162
  end
109
163
 
164
+ # Internal function to optimize sending values.
165
+ #
166
+ # Updating values can be optimized in certain ways. The initial
167
+ # value specifier can be left out if the next value to update
168
+ # has an ID one after the former message.
169
+ #
170
+ # This can significantly reduce message length, for example after
171
+ # creating a new object and initializing it. Being able to leave out the
172
+ # Value ID saves about 10 bytes per value!
110
173
  private def optimize_and_send(messages)
111
174
  last_change = {}
112
175
  out_str = '';
@@ -137,11 +200,13 @@ module TEF
137
200
  @furcoms.send_message 'SET', out_str
138
201
  end
139
202
 
203
+ # Internal function, will merely collect
204
+ # the change strings from all known animations.
140
205
  private def update_values()
141
206
  pending_changes = []
142
207
 
143
208
  @animation_mutex.synchronize {
144
- @active_animations.each do |key, anim|
209
+ @active_animations.each do |_, anim|
145
210
  pending_changes += anim.get_set_strings
146
211
  end
147
212
  }
@@ -152,6 +217,8 @@ module TEF
152
217
  optimize_and_send pending_changes
153
218
  end
154
219
 
220
+ # Internal function, will collect all color change strings from
221
+ # active animations and send it over FurComs
155
222
  private def update_colors
156
223
  pending_changes = []
157
224
 
@@ -167,6 +234,17 @@ module TEF
167
234
  join_and_send 'CSET', pending_changes
168
235
  end
169
236
 
237
+ # Update tick.
238
+ #
239
+ # Calling this function will send all updates and changes over the
240
+ # FurComs bus. It is ensured that they occour in the following order:
241
+ #
242
+ # - All modules that have actively been deleted in Ruby will be
243
+ # deleted.
244
+ # - Newly created and registered {Animatable}s will be created
245
+ # on the animation slaves.
246
+ # - All {Value} changes of all {Animatable}s will be sent.
247
+ # - All {Color} changes will be sent.
170
248
  def update_tick()
171
249
  update_creations
172
250
 
@@ -1,13 +1,64 @@
1
1
 
2
+
3
+ # TheElectricFursuits module.
4
+ # @see https://github.com/TheElectricFursuits
2
5
  module TEF
6
+ # Animation-Related Module
7
+ #
8
+ # This module wraps all classes related to TEF 'Synth'-Line animation.
9
+ # They are meant to provide an abstraction layer over the hardware-implemented
10
+ # animations that run on slave devices, such as the FurComs-Connected Synth Bit,
11
+ # and give the user full access to high-level functions such as configuring
12
+ # named parameters, setting up value smoothing and transitions, and
13
+ # creating and deleting objects.
3
14
  module Animation
4
15
  class Color
5
16
  PARAM_TYPES = [:jump, :velocity, :target, :delay_a, :delay_b];
6
17
 
18
+ # @return [Integer] Hardware-Number of this Color
7
19
  attr_reader :ID
8
20
 
21
+ # @return [String, nil] Module-ID of the {Animatable} that this
22
+ # color belongs to.
9
23
  attr_reader :module_id
10
24
 
25
+ # @!attribute [rw] jump
26
+ # Immediately set the color of this Color to the jump value,
27
+ # skipping animation. If animation is set, the Color will then
28
+ # fade back to the value defined by {#target}
29
+ # @return [Numeric] Current RGB color code.
30
+
31
+ # @!attribute [rw] velocity
32
+ # Set the animation buffer to the given color. Has no effect
33
+ # unless {#delay_b} was configued, in which case the animation will
34
+ # temporarily fade to velocity, then fade back to {#target}
35
+ # @return [Numeric] Current RGB color code.
36
+
37
+ # @!attribute [rw] target
38
+ # Target of the color animation. Set this as hexadecimal RGB color
39
+ # code, i.e. 0xRRGGBB, with an optional alpha channel (i.e. 0xFF000000)
40
+ # for a fully transparent black.
41
+
42
+ # @!attribute [rw] delay_a
43
+ # Smoothing delay for color transition. If {#delay_b} is zero and
44
+ # delay_a is nonzero, setting {#target} will cause the actual
45
+ # color to slowly transition to {#target}. Larger values cause
46
+ # *faster* transitions!
47
+ #
48
+ # If delay_b is set, delay_a defines the smoothing speed between the
49
+ # 'velocity' color and the actual color.
50
+
51
+ # @!attribute [rw] delay_b
52
+ # Smoothing delay for color transition. If both {#delay_a} and delay_b
53
+ # are nonzero, setting {#target} will cause a smooth transition of the
54
+ # output color.
55
+ #
56
+ # delay_b defines the transition speed of {#target} to an internal,
57
+ # non-visible 'velocity' color.
58
+
59
+ # Initialize a new color.
60
+ # @param [Integer] value_num The hardware ID of this Color.
61
+ # must match the ID defined in the animation slaves.
11
62
  def initialize(value_num)
12
63
  @ID = value_num;
13
64
 
@@ -25,10 +76,16 @@ module TEF
25
76
  end
26
77
  end
27
78
 
79
+ # @return [String] Total ID of this Color, in the form
80
+ # 'SxxMxxVxx'
28
81
  def total_id()
29
82
  "#{@module_id}V#{@ID}"
30
83
  end
31
84
 
85
+ # Internal function to set any of the Color's parameters.
86
+ #
87
+ # This can be called by the user, but it is preferrable to use
88
+ # {#configure} or the matching parameter setter functions.
32
89
  def generic_set(key, value)
33
90
  raise ArgumentError, 'Key does not exist!' unless PARAM_TYPES.include? key
34
91
  raise ArgumentError, "Input must be numeric!" unless value.is_a? Numeric
@@ -53,6 +110,13 @@ module TEF
53
110
  end
54
111
  end
55
112
 
113
+ # Configure the color with a hash.
114
+ #
115
+ # This lets the user configure the color by passing a hash.
116
+ # The data will be passed into the five attributes of this color
117
+ # according to their key.
118
+ # @example
119
+ # a_color.configure({ delay_a: 10, target: 0xFF0000 })
56
120
  def configure(data)
57
121
  if data.is_a? Numeric
58
122
  self.target = data
@@ -69,10 +133,16 @@ module TEF
69
133
  return !@changes.empty?
70
134
  end
71
135
 
136
+ # Internal function to strip trailing zeroes for floats
72
137
  private def rcut(value)
73
138
  value.to_s.gsub(/(\.)0+$/, '')
74
139
  end
75
140
 
141
+ # @private
142
+ # Internal function to retrieve the list of changes for this color.
143
+ # @note Do not call this as user unless you know what you are doing!
144
+ # This will delete the retrieved changes, which may cause loss of
145
+ # data if they are not properly sent to the animation slaves!
76
146
  def set_string()
77
147
  return nil unless has_changes?
78
148