-
Notifications
You must be signed in to change notification settings - Fork 0
/
schedule.h
373 lines (311 loc) · 13.4 KB
/
schedule.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
//Define all types and functions to encode schedules
#pragma once
#include <ArduinoSTL.h>
#include <pb_encode.h>
#include <pb_decode.h>
namespace pb{
#include "iris.pb.h"
}
namespace freilite{
namespace iris{
/* Logically, this is the structure we want to represent:
struct Period{
uint8_t cue_id;
uint16_t delays[];
}
struct Schedule{
uint16_t duration;
Period periods[];
}
*/
//However, the unknown size of Schedules and Periods makes byte structure tricky
//(the overhead of an std::vector is 7 bytes, too much for our liking)
//So they are implemented as an array of unions, which can look something like this
//(each non-whitespace character is equivalent to one byte):
// SC DD || || PC || || SC SC PC SC DD ||
// P := start of Period
// S := start of Schedule (and of the first period of that Schedule)
// D := Duration of schedule
// C := cue ID
// | := delay
//When SC is followed by another SC or DC, this schedules duration is not specified, see below
//When SC is followed by DD, that is interpreted to be its duration (16 bits)
//When SC is followed by 0, the duration is not specified and the cues will loop on their own
//SC can not be followed by ||, it would be interpreted as DD.
const uint8_t MAXIMUM_CUE_ID = 0xFE;
const uint8_t INVALID_CUE_ID = 0xFF;
const uint16_t MAXIMUM_DELAY = 0xFDFE;
const uint16_t INVALID_DELAY = 0xFDFF;
//The delay values could be a little larger,
//but the first two bytes being FD guarantees even INVALID_DELAY
//to not be ambiguous with a period or schedule delimiter
enum class delimiter_flag_t : uint8_t{
period = 0xFE,
schedule = 0xFF
};
struct delay_t{
private:
union{
struct{
delimiter_flag_t flag;
uint8_t cue_id;
} delimiter;
uint16_t delay; //Duration of schedule if previous element was a schedule delimiter
} _value;
public:
bool is_schedule_delimiter() const{
return _value.delimiter.flag == delimiter_flag_t::schedule;
}
bool is_period_delimiter() const{
return _value.delimiter.flag == delimiter_flag_t::period;
}
bool is_delimiter() const{
return is_period_delimiter() || is_schedule_delimiter();
}
bool is_delay() const{
return !is_delimiter();
}
uint8_t cue_id() const{
return is_delimiter() ? _value.delimiter.cue_id : INVALID_CUE_ID;
}
uint16_t delay() const{
return is_delay() ? _value.delay : INVALID_DELAY;
}
//Constructor for delimiter. Calling with no arguments yields
//a special delimiter that acts like a null-terminator
delay_t(
delimiter_flag_t delimiter_flag = delimiter_flag_t::schedule,
uint8_t cue_id = INVALID_CUE_ID
){
_value.delimiter.flag = delimiter_flag;
_value.delimiter.cue_id = cue_id <= MAXIMUM_CUE_ID ? cue_id : MAXIMUM_CUE_ID;
}
//Constructor for delay
delay_t(uint16_t delay){
_value.delay = delay;
}
};
//Storage for schedules
namespace Schedules{
namespace{
//Storage for all schedules currently loaded
std::vector<delay_t> loaded_schedules;
//Index map for schedules
//For a schedule_id it stores the index where that schedule starts in loaded_schedules
std::vector<uint8_t> schedule_indices;
}
//Return const iterator to starting schedule delimiter of schedule with ID schedule_id
//Will return iterator to end of loaded_schedules if schedule_id is too large
static std::vector<delay_t>::const_iterator begin_by_id(size_t schedule_id){
if(schedule_id >= schedule_indices.size()){
return loaded_schedules.end();
}
return loaded_schedules.begin() + schedule_indices[schedule_id];
}
//Return const iterator pointing directly after end of schedule with ID schedule_id
static std::vector<delay_t>::const_iterator end_by_id(size_t schedule_id){
//All schedules end before the beginning of the next schedule
return begin_by_id(schedule_id + 1);
}
//Load a schedule element
static void push_element(delay_t schedule_element){
//If a schedule delimiter is pushed, its index is added to the index map
if (schedule_element.is_schedule_delimiter()){
schedule_indices.push_back(loaded_schedules.size());
}
loaded_schedules.push_back(schedule_element);
}
//Unload all schedules
static void clear(){
loaded_schedules.clear();
schedule_indices.clear();
}
//Return number of loaded schedules
static size_t count(){
return schedule_indices.size();
}
//Return number of schedule elements
static size_t element_count(){
return loaded_schedules.size();
}
//Calculate size of actual information stored for schedules
static size_t size_in_bytes(){
return loaded_schedules.size() *
sizeof(decltype(loaded_schedules)::value_type);
}
//Calculate overhead in bytes of schedules when stored in memory
static size_t memory_overhead(){
return sizeof(loaded_schedules) +
sizeof(schedule_indices) +
schedule_indices.size() *
sizeof(decltype(schedule_indices)::value_type);
}
}
//Structure describing a single schedule
struct Schedule{
private:
size_t id;
//Holds begin and end iterator for one Period
struct period_range_t{
std::vector<delay_t>::const_iterator begin;
std::vector<delay_t>::const_iterator end;
};
//Encode Periods inside a schedule as a nanopb callback
static bool encode_periods(pb_ostream_t* stream,
const pb_field_t* field,
void* const* arg){
using namespace pb;
const Schedule* schedule = static_cast<const Schedule*>(*arg);
auto iter = schedule->begin();
auto end = schedule->end();
//Prepare period for encoding delays
pb::Schedule_Period pb_period = Schedule_Period_init_default;
pb_period.cue_id = iter->cue_id();
//Advance iterator
if (schedule->duration() == INVALID_DELAY){
//Jump over Schedule delimiter
iter += 1;
} else {
//Jump over Schedule delimiter and duration
iter += 2;
}
//Create range
period_range_t period_range = {iter, iter};
pb_period.delays.funcs.encode = &encode_delays;
pb_period.delays.arg = &period_range;
while(true){
//Reached end of this period
if(iter >= end || iter->is_delimiter()){
//Send period as message
period_range.end = iter;
if(!pb_encode_tag_for_field(stream, field))
return false;
if(!pb_encode_submessage(stream, pb::Schedule_Period_fields, &pb_period))
return false;
//Stop when schedule is over
if(iter >= end || iter->is_schedule_delimiter()){
break;
}
//Reset range for next period
period_range.begin = iter + 1; //Skip delimiter
pb_period.cue_id = iter->cue_id();
}
//Increment iterator
++iter;
}
return true;
}
//Encode Periods inside a period as a nanopb callback
static bool encode_delays(pb_ostream_t* stream,
const pb_field_t* field,
void* const* arg){
const period_range_t* period_range = static_cast<const period_range_t*>(*arg);
for(auto iter = period_range->begin; iter < period_range->end; ++iter){
//Use non-packed repeated field for now
if(!pb_encode_tag_for_field(stream, field))
return false;
//Encode actual delay
if(!pb_encode_varint(stream, iter->delay()))
return false;
}
}
public: //non-static
Schedule(size_t id) : id(id){}
//Return duration of this schedule
uint16_t duration() const{
//If there is a duration specified, it's directly after
//the schedule delimiter
auto iter = begin() + 1;
if (iter != end() && iter->is_delay()){
return iter->delay();
}
//It is important to know whether the duration was
//explicitly 0 or not set at all.
else return INVALID_DELAY;
}
//Return true if this schedule is loaded
inline bool exists() const{
return id < Schedules::count();
}
//Return const iterator to starting schedule delimiter of schedule
inline std::vector<delay_t>::const_iterator begin() const{
return Schedules::begin_by_id(id);
}
//Return const iterator pointing directly after end of schedule
inline std::vector<delay_t>::const_iterator end() const{
return Schedules::end_by_id(id);
}
//Return as protobuf defined Schedule
pb::Schedule as_pb_schedule(){
using namespace pb;
pb::Schedule pb_schedule = Schedule_init_default;
auto duration = this->duration();
pb_schedule.duration = duration == INVALID_DELAY ? 0 : duration;
pb_schedule.periods = {};
pb_schedule.periods.funcs.encode = &encode_periods;
pb_schedule.periods.arg = this;
return pb_schedule;
}
typedef void (draw_callback_t)(size_t cue_id, uint32_t time, uint8_t draw_disabled_channels);
void draw(draw_callback_t* draw_cue, uint32_t time) const{
auto iter = this->begin();
auto end_iter = this->end();
size_t current_cue_id = iter->cue_id();
uint32_t current_delay = 0;
bool currently_on = true;
bool relevant_delay_found = false;
//If schedule duration is specified, the effect is looped
uint32_t schedule_duration = this->duration();
if (schedule_duration == INVALID_DELAY){
//Jump over Schedule delimiter
iter += 1;
} else {
//Loop the
if (schedule_duration != 0){
time = time % schedule_duration;
}
//Jump over Schedule delimiter and duration
iter += 2;
}
while (true){
//Prepare for parsing next period's delays
if (iter->is_period_delimiter()){
if (currently_on){
(*draw_cue)(current_cue_id, time, false);
}
current_cue_id = iter->cue_id();
current_delay = 0;
currently_on = true;
relevant_delay_found = false;
}
//Draw and end loop if end of schedule is reached
else if (iter >= end_iter){
if (currently_on){
(*draw_cue)(current_cue_id, time, false);
}
break;
}
//Seek the next delimiter
else if (relevant_delay_found){
continue;
}
//Found a delay
else {
current_delay += iter->delay();
//Found the relevant delay, now we know
//whether cue is on or off at this point in time
if (current_delay > time){
relevant_delay_found = true;
}
else{
//toggle state of cue
currently_on = !currently_on;
}
}
//Increment iterator
++iter;
};
}
};
}
}