tef-animation 0.1.0 → 0.1.1

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.
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