tef-animation 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3bc7c194c8425278ad0369c293684b65008f293f
4
+ data.tar.gz: b59ce88b52067a8c5b2b8c2070e7abf408d073e5
5
+ SHA512:
6
+ metadata.gz: 9e1c4ed9339a1054e69a5b2074e6f1741da37c3490e7cac055c1f696ad3e6365ecc2b2dda615265df8d3ebd072f1a9b9665d3ca9ef9e31bb9a8fd42c53a5c02a
7
+ data.tar.gz: 13d28689ecdd5bba1fbb8028076b39ba3623b62b0f7b8f7e38cafdf108941395907790509bef2d340939eb4b1e817561f12d5a01185e95466d20fb2b5e417b74
data/README.md ADDED
@@ -0,0 +1,6 @@
1
+
2
+ # TheElectricFursuit Animation and Sequencing
3
+
4
+ This... Is a stub for now. Stay tuned for documentation!
5
+ OR!
6
+ Head to github.com/Xasin/system-synth-suit/Ruby/VoiceControl and see how this works.
@@ -0,0 +1,213 @@
1
+
2
+ require_relative 'Value.rb'
3
+ require_relative 'Color.rb'
4
+ require_relative 'Coordinate.rb'
5
+
6
+ module TEF
7
+ module Animation
8
+ class Animatable
9
+ attr_reader :module_id
10
+ attr_reader :death_time
11
+
12
+ attr_reader :death_delay
13
+
14
+ def self.get_attr_list
15
+ @class_attribute_list ||= {}
16
+ end
17
+ def self.get_color_list
18
+ @class_color_list ||= {}
19
+ end
20
+ def self.get_coordinate_list
21
+ @class_coordinate_list ||= {}
22
+ end
23
+
24
+ def self.animatable_attr(name, id)
25
+ get_attr_list()[name] = id
26
+
27
+ define_method(name.to_s) do
28
+ @animatable_attributes[name]
29
+ end
30
+
31
+ define_method("#{name}=") do |arg|
32
+ if arg.is_a? Numeric
33
+ @animatable_attributes[name].add = arg
34
+ else
35
+ @animatable_attributes[name].from = arg
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.animatable_color(name, id)
41
+ get_color_list()[name] = id
42
+
43
+ define_method(name.to_s) do
44
+ @animatable_colors[name]
45
+ end
46
+
47
+ define_method("#{name}=") do |arg|
48
+ @animatable_colors[name].target = arg
49
+ end
50
+ end
51
+
52
+ def self.animatable_coordinate(name, start)
53
+ get_coordinate_list()[name] = start
54
+
55
+ define_method(name.to_s) do
56
+ @animatable_coordinates[name]
57
+ end
58
+ end
59
+
60
+ def initialize()
61
+ @animatable_attributes = {}
62
+ @animatable_colors = {}
63
+ @animatable_coordinates = {}
64
+
65
+ self.class.get_attr_list.each do |key, val|
66
+ @animatable_attributes[key] = Value.new(val)
67
+ end
68
+
69
+ self.class.get_color_list.each do |key, val|
70
+ @animatable_colors[key] = Color.new(val)
71
+ end
72
+
73
+ self.class.get_coordinate_list.each do |key, offset|
74
+ @animatable_coordinates[key] = Coordinate.new(offset)
75
+ end
76
+ end
77
+
78
+ def death_time=(n_time)
79
+ raise ArgumentError, 'Must be a Time!' unless n_time.is_a? Time || n_time.nil?
80
+
81
+ return if n_time == @death_time
82
+
83
+ @death_time = n_time
84
+ @death_time_changed = true
85
+ end
86
+
87
+ def die_in(t)
88
+ if t.nil?
89
+ self.death_time = nil
90
+ @death_delay = nil
91
+
92
+ return
93
+ end
94
+
95
+ raise ArgumentError, "Time must be num!" unless t.is_a? Numeric
96
+
97
+ self.death_time = Time.now() + t
98
+ @death_delay = t
99
+ end
100
+
101
+ def die!
102
+ self.death_time = Time.at(0)
103
+ end
104
+
105
+ def configure(h = nil, **opts)
106
+ h ||= opts;
107
+
108
+ raise ArgumentError, 'Config must be a hash!' unless h.is_a? Hash
109
+
110
+ h.each do |key, data|
111
+ value = @animatable_attributes[key] ||
112
+ @animatable_colors[key] ||
113
+ @animatable_coordinates[key]
114
+
115
+ raise ArgumentError, "Parameter #{key} does not exist!" unless value
116
+
117
+ value.configure(data);
118
+ end
119
+ end
120
+
121
+ def creation_string
122
+ ""
123
+ end
124
+
125
+ def all_animatable_attributes
126
+ out = @animatable_attributes.values
127
+ out += @animatable_coordinates.values.map(&:animatable_attributes)
128
+
129
+ out.flatten
130
+ end
131
+
132
+ def module_id=(new_str)
133
+ unless new_str =~ /^S[\d]{1,3}M[\d]{1,3}$/ || new_str.nil?
134
+ raise ArgumentError, 'Target must be a valid Animation Value'
135
+ end
136
+
137
+ all_animatable_attributes.each do |value|
138
+ value.module_id = new_str
139
+ end
140
+
141
+ die_in @death_delay if @death_delay
142
+
143
+ @module_id = new_str
144
+ end
145
+
146
+ def death_time_string
147
+ return nil unless @death_time_changed
148
+
149
+ @death_time_changed = false
150
+
151
+ return "#{@module_id} N;" if @death_time.nil?
152
+
153
+ remaining_time = (@death_time - Time.now()).round(2)
154
+
155
+ "#{@module_id} #{remaining_time};"
156
+ end
157
+
158
+ def get_set_strings()
159
+ return [] unless @module_id
160
+
161
+ out_elements = []
162
+
163
+ all_animatable_attributes.each do |val|
164
+ o_str = val.set_string
165
+ next if o_str.nil?
166
+
167
+ out_elements << { module: @module_id, value: val.ID, str: o_str };
168
+ end
169
+
170
+ out_elements
171
+ end
172
+
173
+ def get_setc_strings()
174
+ return [] unless @module_id
175
+
176
+ out_elements = []
177
+
178
+ @animatable_colors.values.each do |val|
179
+ o_str = val.set_string
180
+ next if o_str.nil?
181
+
182
+ out_elements << "#{@module_id}#{o_str}"
183
+ end
184
+
185
+ out_elements
186
+ end
187
+ end
188
+
189
+ class Box < Animatable
190
+ animatable_color :color, 0
191
+
192
+ animatable_attr :rotation, 0
193
+ animatable_attr :up, 1
194
+ animatable_attr :down, 2
195
+ animatable_attr :left, 3
196
+ animatable_attr :right, 4
197
+
198
+ animatable_coordinate :x_dir, 0xC000
199
+ animatable_coordinate :y_dir, 0xC100
200
+ animatable_coordinate :center, 0xC200
201
+
202
+ def initialize(layer_no = 0)
203
+ super();
204
+
205
+ @layer = layer_no;
206
+ end
207
+
208
+ def creation_string
209
+ "BOX #{@module_id} #{@layer}"
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,180 @@
1
+
2
+ require 'xasin_logger'
3
+
4
+ require_relative 'Animatable.rb'
5
+
6
+ module TEF
7
+ module Animation
8
+ class Handler
9
+ include XasLogger::Mix
10
+
11
+ def initialize(furcoms_bus)
12
+ @furcoms = furcoms_bus
13
+
14
+ @animation_mutex = Mutex.new
15
+ @active_animations = {}
16
+
17
+ @pending_deletions = {}
18
+ @pending_creations = {}
19
+
20
+ init_x_log('Animation Handler')
21
+ end
22
+
23
+ def clean_key(key)
24
+ key = 'S%<S>dM%<M>d' % key if key.is_a? Hash
25
+
26
+ unless key =~ /^S[\d]{1,3}M[\d]{1,3}$/
27
+ raise ArgumentError, 'Target must be a valid Animation Value'
28
+ end
29
+
30
+ key
31
+ end
32
+
33
+ def [](key)
34
+ @active_animations[clean_key key]
35
+ end
36
+
37
+ def []=(key, new_obj)
38
+ @animation_mutex.synchronize {
39
+ key = clean_key key
40
+
41
+ @active_animations[key]&.module_id = nil;
42
+
43
+ if new_obj.nil?
44
+ @pending_deletions[key] = true
45
+
46
+ elsif new_obj.is_a? Animatable
47
+ new_obj.module_id = key
48
+
49
+ @active_animations[key] = new_obj
50
+ @pending_creations[key] = new_obj.creation_string
51
+ else
52
+ raise ArgumentError, 'New animation object is of invalid type'
53
+ end
54
+ }
55
+ end
56
+
57
+ private def join_and_send(topic, data)
58
+ return if data.empty?
59
+
60
+ out_str = '';
61
+ data.each do |str|
62
+ if(out_str.length + str.length > 200)
63
+ @furcoms.send_message topic, out_str
64
+ out_str = ''
65
+ end
66
+
67
+ out_str += str
68
+ end
69
+
70
+ @furcoms.send_message topic, out_str
71
+ end
72
+
73
+ # TODO Replace this with a time-synched system
74
+ # using the main synch time
75
+ private def update_deaths
76
+ death_reconfigs = [];
77
+
78
+ @animation_mutex.synchronize {
79
+ @active_animations.each do |key, animation|
80
+ if (!animation.death_time.nil?) && animation.death_time < Time.now()
81
+ @pending_deletions[key] = :silent
82
+ end
83
+
84
+ new_death = animation.death_time_string
85
+ next if new_death.nil?
86
+
87
+ death_reconfigs << new_death
88
+ end
89
+ }
90
+
91
+ deletions = []
92
+ @pending_deletions.each do |key, val|
93
+ deletions << "#{key};" unless val == :silent
94
+ @active_animations.delete key
95
+ end
96
+ @pending_deletions = {}
97
+
98
+ join_and_send('DTIME', death_reconfigs)
99
+ join_and_send('DELETE', deletions)
100
+ end
101
+
102
+ private def update_creations
103
+ @pending_creations.each do |key, val|
104
+ @furcoms.send_message('NEW', val)
105
+ end
106
+
107
+ @pending_creations = {}
108
+ end
109
+
110
+ private def optimize_and_send(messages)
111
+ last_change = {}
112
+ out_str = '';
113
+ can_optimize = false
114
+
115
+ messages.each do |change|
116
+ opt_string = change[:str]
117
+
118
+ can_optimize = false if last_change[:module] != change[:module]
119
+ can_optimize = false if last_change[:value] != (change[:value] - 1)
120
+ can_optimize = false if (out_str.length + opt_string.length) > 200
121
+ can_optimize = false if opt_string[0] == 'S'
122
+
123
+ opt_string = "#{change[:module]}V#{change[:value].to_s(16)} #{opt_string}" unless can_optimize
124
+
125
+ if opt_string.length + out_str.length > 200
126
+ @furcoms.send_message 'SET', out_str
127
+ out_str = ''
128
+ end
129
+
130
+ out_str += opt_string
131
+
132
+
133
+ can_optimize = true
134
+ last_change = change
135
+ end
136
+
137
+ @furcoms.send_message 'SET', out_str
138
+ end
139
+
140
+ private def update_values()
141
+ pending_changes = []
142
+
143
+ @animation_mutex.synchronize {
144
+ @active_animations.each do |key, anim|
145
+ pending_changes += anim.get_set_strings
146
+ end
147
+ }
148
+
149
+ return if pending_changes.empty?
150
+ x_logd "Pending changes are #{pending_changes}"
151
+
152
+ optimize_and_send pending_changes
153
+ end
154
+
155
+ private def update_colors
156
+ pending_changes = []
157
+
158
+ @animation_mutex.synchronize {
159
+ @active_animations.each do |key, anim|
160
+ pending_changes += anim.get_setc_strings
161
+ end
162
+ }
163
+
164
+ return if pending_changes.empty?
165
+ x_logd "Pending changes are #{pending_changes}"
166
+
167
+ join_and_send 'CSET', pending_changes
168
+ end
169
+
170
+ def update_tick()
171
+ update_creations
172
+
173
+ update_deaths
174
+
175
+ update_values
176
+ update_colors
177
+ end
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,112 @@
1
+
2
+ module TEF
3
+ module Animation
4
+ class Color
5
+ PARAM_TYPES = [:jump, :velocity, :target, :delay_a, :delay_b];
6
+
7
+ attr_reader :ID
8
+
9
+ attr_reader :module_id
10
+
11
+ def initialize(value_num)
12
+ @ID = value_num;
13
+
14
+ @current = Hash.new(0);
15
+ @changes = {}
16
+
17
+ @is_animated = false;
18
+ end
19
+
20
+ def module_id=(new_id)
21
+ @module_id = new_id
22
+
23
+ PARAM_TYPES.each do |key|
24
+ @changes[:key] = true if @current[key] != 0
25
+ end
26
+ end
27
+
28
+ def total_id()
29
+ "#{@module_id}V#{@ID}"
30
+ end
31
+
32
+ def generic_set(key, value)
33
+ raise ArgumentError, 'Key does not exist!' unless PARAM_TYPES.include? key
34
+ raise ArgumentError, "Input must be numeric!" unless value.is_a? Numeric
35
+
36
+ return if ![:jump, :velocity].include?(key) && value == @current[key]
37
+
38
+ if [:delay_a, :delay_b].include? key
39
+ @is_animated = true
40
+ end
41
+
42
+ @current[key] = value
43
+ @changes[key] = true
44
+ end
45
+
46
+ PARAM_TYPES.each do |key|
47
+ define_method(key.to_s) do
48
+ @current[key]
49
+ end
50
+
51
+ define_method("#{key}=") do |input|
52
+ generic_set key, input
53
+ end
54
+ end
55
+
56
+ def configure(data)
57
+ if data.is_a? Numeric
58
+ self.target = data
59
+ elsif data.is_a? Hash
60
+ data.each do |key, value|
61
+ generic_set key, value
62
+ end
63
+ else
64
+ raise ArgumentError, 'Config data must be Hash or Numeric'
65
+ end
66
+ end
67
+
68
+ def has_changes?
69
+ return !@changes.empty?
70
+ end
71
+
72
+ private def rcut(value)
73
+ value.to_s.gsub(/(\.)0+$/, '')
74
+ end
75
+
76
+ def set_string()
77
+ return nil unless has_changes?
78
+
79
+ if !@is_animated
80
+ return nil unless @changes[:target]
81
+
82
+ out_str = "V#{@ID} J#{@current[:target].to_s(16)};"
83
+
84
+ @changes = {}
85
+
86
+ return out_str
87
+ end
88
+
89
+ out_str = ["V#{@ID}"];
90
+
91
+ out_str << "J#{@current[:jump].to_s(16)}" if @changes[:jump]
92
+ out_str << "V#{@current[:velocity].to_s(16)}" if @changes[:velocity]
93
+
94
+ config_strs = [];
95
+ config_strs_out = [];
96
+ [:target, :delay_a, :delay_b].each do |k|
97
+ if k == :target
98
+ config_strs << @current[k].to_s(16)
99
+ else
100
+ config_strs << rcut(@current[k])
101
+ end
102
+
103
+ config_strs_out = config_strs.dup if @changes[k]
104
+ end
105
+
106
+ @changes = {}
107
+
108
+ (out_str + config_strs_out).join(' ') + ';'
109
+ end
110
+ end
111
+ end
112
+ end