1 #include <limits>
2 #include <cassert>
3 #include <cstring>
4 #include <algorithm>
5 #include <set>
6
7 #include "../util.h"
8 #include <rtosc/arg-val-cmp.h>
9 #include <rtosc/pretty-format.h>
10 #include <rtosc/bundle-foreach.h>
11 #include <rtosc/ports.h>
12 #include <rtosc/ports-runtime.h>
13 #include <rtosc/default-value.h>
14 #include <rtosc/savefile.h>
15
16 namespace rtosc {
17
18 namespace {
19 constexpr std::size_t buffersize = 8192;
20 constexpr size_t max_arg_vals = 2048;
21 }
22
23 // this basically does:
24 // walk over all ports and for each port:
25 // * get default value
26 // * get current value
27 // * compare: if values are different -> write to savefile
get_changed_values(const Ports & ports,void * runtime)28 std::string get_changed_values(const Ports& ports, void* runtime)
29 {
30 char port_buffer[buffersize];
31 memset(port_buffer, 0, buffersize); // requirement for walk_ports
32
33 struct data_t
34 {
35 std::string res;
36 std::set<std::string> written;
37 } data;
38
39 auto on_reach_port =
40 [](const Port* p, const char* port_buffer,
41 const char* port_from_base, const Ports& base,
42 void* data, void* runtime)
43 {
44 assert(runtime);
45 const Port::MetaContainer meta = p->meta();
46 #if 0
47 // practical for debugging if a parameter was changed, but not saved
48 const char* cmp = "/part15/kit0/adpars/GlobalPar/Reson/Prespoints";
49 if(!strncmp(port_buffer, cmp, strlen(cmp)))
50 {
51 puts("break here");
52 }
53 #endif
54
55 if((p->name[strlen(p->name)-1] != ':' && !strstr(p->name, "::"))
56 || meta.find("parameter") == meta.end())
57 {
58 // runtime information can not be retrieved,
59 // thus, it can not be compared with the default value
60 return;
61 }
62 else
63 { // TODO: duplicate to above? (colon[1])
64 const char* colon = strchr(p->name, ':');
65 if(!colon || !colon[1])
66 {
67 // runtime information can not be loaded, so don't save it
68 // a possible FEATURE would be to save it anyways
69 return;
70 }
71 }
72
73 if(meta.find("alias") != meta.end())
74 {
75 // this param is already saved in another port
76 return;
77 }
78
79 {
80 std::set<std::string>& written = ((data_t*)data)->written;
81 if(written.find(port_buffer) != written.end()) { return; }
82 else { written.insert(port_buffer); }
83 }
84
85 char loc[buffersize] = ""; // buffer to hold the dispatched path
86 rtosc_arg_val_t arg_vals_default[max_arg_vals];
87 rtosc_arg_val_t arg_vals_runtime[max_arg_vals];
88 // buffer to hold the message (i.e. /port ..., without port's bases)
89 char buffer_with_port[buffersize];
90 char strbuf[buffersize]; // temporary string buffer for pretty-printing
91
92 std::string* res = &((data_t*)data)->res;
93 assert(strlen(port_buffer) + 1 < buffersize);
94 // copy the path until before the message
95 fast_strcpy(loc, port_buffer, std::min((ptrdiff_t)buffersize,
96 port_from_base - port_buffer + 1
97 ));
98 char* loc_end = loc + (port_from_base - port_buffer);
99 size_t loc_remain_size = buffersize - (port_from_base - port_buffer);
100 *loc_end = 0;
101
102 const char* portargs = strchr(p->name, ':');
103 if(!portargs)
104 portargs = p->name + strlen(p->name);
105
106 #if 0 // debugging stuff
107 if(!strncmp(port_buffer, "/part1/Penabled", 5) &&
108 !strncmp(port_buffer+6, "/Penabled", 9))
109 {
110 printf("runtime: %ld\n", (long int)runtime);
111 }
112 #endif
113 // TODO: p->name: duplicate to p
114 int nargs_default = get_default_value(p->name,
115 portargs,
116 base,
117 runtime,
118 p,
119 -1,
120 max_arg_vals,
121 arg_vals_default,
122 strbuf,
123 buffersize);
124
125 if(nargs_default > 0)
126 {
127 size_t nargs_runtime = 0;
128
129 auto ftor = [&](const Port* p, const char* ,
130 const char* old_end,
131 const Ports& ,void* ,void* runtime)
132 {
133 fast_strcpy(buffer_with_port, p->name, buffersize);
134
135 // the caller of ftor (in some cases bundle_foreach) has
136 // already filled old_end correctly, but we have to copy this
137 // over to loc_end
138 fast_strcpy(loc_end, old_end, loc_remain_size);
139
140 size_t nargs_runtime_cur =
141 helpers::get_value_from_runtime(runtime, *p,
142 buffersize, loc, old_end,
143 buffer_with_port,
144 buffersize,
145 max_arg_vals,
146 arg_vals_runtime +
147 nargs_runtime);
148 nargs_runtime += nargs_runtime_cur;
149 };
150
151 auto refix_old_end = [&base](const Port* _p, char* _old_end)
152 { // TODO: remove base capture
153 bundle_foreach(*_p, _p->name, _old_end, NULL,
154 base, NULL, NULL, bundle_foreach_do_nothing,
155 false, false);
156 };
157
158 if(strchr(p->name, '#'))
159 {
160 // idea:
161 // p/a/b
162 // bundle_foreach => p/a#0/b, p/a#1/b, ... p/a#n/b, p
163 // bundle_foreach => p/a/b
164 // => justification for const_cast
165
166 // Skip the array element (type 'a') for now...
167 ++nargs_runtime;
168
169 // Start filling at arg_vals_runtime + 1
170 char* old_end_noconst = const_cast<char*>(port_from_base);
171 bundle_foreach(*p, p->name, old_end_noconst, port_buffer + 1,
172 base, data, runtime,
173 ftor, true);
174
175 // glue the old end behind old_end_noconst again
176 refix_old_end(p, old_end_noconst);
177
178 // "Go back" to fill arg_vals_runtime + 0
179 arg_vals_runtime[0].type = 'a';
180 arg_vals_runtime[0].val.a.len = nargs_runtime-1;
181 arg_vals_runtime[0].val.a.type = arg_vals_runtime[1].type;
182 }
183 else
184 ftor(p, port_buffer, port_from_base, base, NULL, runtime);
185
186 #if 0
187 // practical for debugging if a parameter was changed, but not saved
188 const char* cmp = "/part15/kit0/adpars/GlobalPar/Reson/Prespoints";
189 if(!strncmp(port_buffer, cmp, strlen(cmp)))
190 {
191 puts("break here");
192 }
193 #endif
194 canonicalize_arg_vals(arg_vals_default, nargs_default,
195 strchr(p->name, ':'), meta);
196
197 auto write_msg = [&res, &meta, &port_buffer]
198 (const rtosc_arg_val_t* arg_vals_default,
199 rtosc_arg_val_t* arg_vals_runtime,
200 int nargs_default, size_t nargs_runtime)
201 {
202 if(!rtosc_arg_vals_eq(arg_vals_default, arg_vals_runtime,
203 nargs_default, nargs_runtime, nullptr))
204 {
205 char cur_value_pretty[buffersize] = " ";
206
207 map_arg_vals(arg_vals_runtime, nargs_runtime, meta);
208
209 rtosc_print_arg_vals(arg_vals_runtime, nargs_runtime,
210 cur_value_pretty + 1, buffersize - 1,
211 NULL, strlen(port_buffer) + 1);
212 *res += port_buffer;
213 *res += cur_value_pretty;
214 *res += "\n";
215 }
216 }; // functor write_msg
217
218 if(arg_vals_runtime[0].type == 'a' && strchr(port_from_base, '/'))
219 {
220 // These are grouped as an array, but the port structure
221 // implicits that they shall be handled as single values
222 // inside their subtrees
223 // => We don't print this as an array
224 // => All arrays in savefiles have their numbers after
225 // the last port separator ('/')
226
227 // used if the value of lhs or rhs is range-computed:
228 rtosc_arg_val_t rlhs, rrhs;
229
230 rtosc_arg_val_itr litr, ritr;
231 rtosc_arg_val_itr_init(&litr, arg_vals_default+1);
232 rtosc_arg_val_itr_init(&ritr, arg_vals_runtime+1);
233
234 auto write_msg_adaptor = [&litr, &ritr,&rlhs,&rrhs,&write_msg](
235 const Port* p,
236 const char* port_buffer, const char* old_end,
237 const Ports&, void*, void*)
238 {
239 const rtosc_arg_val_t
240 * lcur = rtosc_arg_val_itr_get(&litr, &rlhs),
241 * rcur = rtosc_arg_val_itr_get(&ritr, &rrhs);
242
243 if(!rtosc_arg_vals_eq_single(
244 rtosc_arg_val_itr_get(&litr, &rlhs),
245 rtosc_arg_val_itr_get(&ritr, &rrhs), nullptr))
246 {
247 auto get_sz = [](const rtosc_arg_val_t* a) {
248 return a->type == 'a' ? (a->val.a.len + 1) : 1; };
249 // the const-ness does not matter
250 write_msg(lcur,
251 const_cast<rtosc_arg_val_t*>(rcur),
252 get_sz(lcur), get_sz(rcur));
253 }
254
255 rtosc_arg_val_itr_next(&litr);
256 rtosc_arg_val_itr_next(&ritr);
257 };
258
259 char* old_end_noconst = const_cast<char*>(port_from_base);
260
261 // iterate over the whole array
262 bundle_foreach(*p, p->name, old_end_noconst, port_buffer,
263 base, NULL, NULL,
264 write_msg_adaptor, true);
265
266 // glue the old end behind old_end_noconst again
267 refix_old_end(p, old_end_noconst);
268
269 }
270 else
271 {
272 write_msg(arg_vals_default, arg_vals_runtime,
273 nargs_default, nargs_runtime);
274 }
275 }
276 };
277
278 walk_ports(&ports, port_buffer, buffersize, &data, on_reach_port, false,
279 runtime);
280
281 if(data.res.length()) // remove trailing newline
282 data.res.resize(data.res.length()-1);
283 return data.res;
284 }
285
do_dispatch(const char * msg)286 bool savefile_dispatcher_t::do_dispatch(const char* msg)
287 {
288 *loc = 0;
289 RtData d;
290 d.obj = runtime;
291 d.loc = loc; // we're always dispatching at the base
292 d.loc_size = 1024;
293 ports->dispatch(msg, d, true);
294 return !!d.matches;
295 }
296
default_response(size_t nargs,bool first_round,savefile_dispatcher_t::dependency_t dependency)297 int savefile_dispatcher_t::default_response(size_t nargs,
298 bool first_round,
299 savefile_dispatcher_t::dependency_t
300 dependency)
301 {
302 // default implementation:
303 // no dependencies => round 0,
304 // has dependencies => round 1,
305 // not specified => both rounds
306 return (dependency == not_specified
307 || !(dependency ^ first_round))
308 ? nargs // argument number is not changed
309 : (int)discard;
310 }
311
on_dispatch(size_t,char *,size_t,size_t nargs,rtosc_arg_val_t *,bool round2,dependency_t dependency)312 int savefile_dispatcher_t::on_dispatch(size_t, char *,
313 size_t, size_t nargs,
314 rtosc_arg_val_t *,
315 bool round2,
316 dependency_t dependency)
317 {
318 return default_response(nargs, round2, dependency);
319 }
320
dispatch_printed_messages(const char * messages,const Ports & ports,void * runtime,savefile_dispatcher_t * dispatcher)321 int dispatch_printed_messages(const char* messages,
322 const Ports& ports, void* runtime,
323 savefile_dispatcher_t* dispatcher)
324 {
325 constexpr std::size_t buffersize = 8192;
326 char portname[buffersize], message[buffersize], strbuf[buffersize];
327 int rd, rd_total = 0;
328 int nargs;
329 int msgs_read = 0;
330 bool ok = true;
331
332 savefile_dispatcher_t dummy_dispatcher;
333 if(!dispatcher)
334 dispatcher = &dummy_dispatcher;
335 dispatcher->ports = &ports;
336 dispatcher->runtime = runtime;
337
338 // scan all messages twice:
339 // * in the second round, only dispatch those with ports that depend on
340 // other ports
341 // * in the first round, only dispatch all others
342 for(int round = 0; round < 2 && ok; ++round)
343 {
344 msgs_read = 0;
345 rd_total = 0;
346 const char* msg_ptr = messages;
347 while(*msg_ptr && ok)
348 {
349 nargs = rtosc_count_printed_arg_vals_of_msg(msg_ptr);
350 if(nargs >= 0)
351 {
352 // nargs << 1 is usually too much, but it allows the user to use
353 // these values (using on_dispatch())
354 size_t maxargs = std::max(nargs << 1, 16);
355 STACKALLOC(rtosc_arg_val_t, arg_vals, maxargs);
356 rd = rtosc_scan_message(msg_ptr, portname, buffersize,
357 arg_vals, nargs, strbuf, buffersize);
358 rd_total += rd;
359
360 const Port* port = ports.apropos(portname);
361 savefile_dispatcher_t::dependency_t dependency =
362 (savefile_dispatcher_t::dependency_t)
363 (port
364 ? !!port->meta()["default depends"]
365 : (int)savefile_dispatcher_t::not_specified);
366
367 // let the user modify the message and the args
368 // the argument number may have changed, or the user
369 // wants to discard the message or abort the savefile loading
370 nargs = dispatcher->on_dispatch(buffersize, portname,
371 maxargs, nargs, arg_vals,
372 round, dependency);
373
374 if(nargs == savefile_dispatcher_t::abort)
375 ok = false;
376 else
377 {
378 if(nargs != savefile_dispatcher_t::discard)
379 {
380 const rtosc_arg_val_t* arg_val_ptr;
381 bool is_array;
382 if(nargs && arg_vals[0].type == 'a')
383 {
384 is_array = true;
385 // arrays of arrays are not yet supported -
386 // neither by rtosc_*message, nor by the inner for
387 // loop below.
388 // arrays will probably have an 'a' (or #)
389 assert(arg_vals[0].val.a.type != 'a' &&
390 arg_vals[0].val.a.type != '#');
391 // we won't read the array arg val anymore
392 --nargs;
393 arg_val_ptr = arg_vals + 1;
394 }
395 else {
396 is_array = false;
397 arg_val_ptr = arg_vals;
398 }
399
400 char* portname_end = portname + strlen(portname);
401
402 rtosc_arg_val_itr itr;
403 rtosc_arg_val_t buffer;
404 const rtosc_arg_val_t* cur;
405
406 rtosc_arg_val_itr_init(&itr, arg_val_ptr);
407
408 // for bundles, send each element separately
409 // for non-bundles, send all elements at once
410 for(size_t arr_idx = 0;
411 itr.i < (size_t)std::max(nargs,1) && ok; ++arr_idx)
412 {
413 // this will fail for arrays of arrays,
414 // since it only copies one arg val
415 // (arrays are not yet specified)
416 size_t i;
417 const size_t last_pos = itr.i;
418 const size_t elem_limit = is_array
419 ? 1 : std::numeric_limits<int>::max();
420
421 // equivalent to the for loop below, in order to
422 // find out the array size
423 size_t val_max = 0;
424 {
425 rtosc_arg_val_itr itr2 = itr;
426 for(val_max = 0;
427 itr2.i - last_pos < (size_t)nargs &&
428 val_max < elem_limit;
429 ++val_max)
430 {
431 rtosc_arg_val_itr_next(&itr2);
432 }
433 }
434 STACKALLOC(rtosc_arg_t, vals, val_max);
435 STACKALLOC(char, argstr, val_max+1);
436
437 for(i = 0;
438 itr.i - last_pos < (size_t)nargs &&
439 i < elem_limit;
440 ++i)
441 {
442 cur = rtosc_arg_val_itr_get(&itr, &buffer);
443 vals[i] = cur->val;
444 argstr[i] = cur->type;
445 rtosc_arg_val_itr_next(&itr);
446 }
447
448 argstr[i] = 0;
449
450 if(is_array)
451 snprintf(portname_end, 8, "%d", (int)arr_idx);
452
453 rtosc_amessage(message, buffersize, portname,
454 argstr, vals);
455
456 ok = (*dispatcher)(message);
457 }
458 }
459 }
460
461 msg_ptr += rd;
462 ++msgs_read;
463 }
464 else if(nargs == std::numeric_limits<int>::min())
465 {
466 // this means the (rest of the) file is whitespace only
467 // => don't increase msgs_read
468 while(*++msg_ptr) ;
469 }
470 else {
471 ok = false;
472 }
473 }
474 }
475 return ok ? msgs_read : -rd_total-1;
476 }
477
save_to_file(const Ports & ports,void * runtime,const char * appname,rtosc_version appver,std::string file_str)478 std::string save_to_file(const Ports &ports, void *runtime,
479 const char *appname, rtosc_version appver,
480 std::string file_str)
481 {
482 char rtosc_vbuf[12], app_vbuf[12];
483
484 if(file_str.empty())
485 {
486 {
487 rtosc_version rtoscver = rtosc_current_version();
488 rtosc_version_print_to_12byte_str(&rtoscver, rtosc_vbuf);
489 rtosc_version_print_to_12byte_str(&appver, app_vbuf);
490 }
491
492 file_str += "% RT OSC v"; file_str += rtosc_vbuf; file_str += " savefile\n"
493 "% "; file_str += appname; file_str += " v"; file_str += app_vbuf; file_str += "\n";
494 }
495 else
496 {
497 // append mode - no header
498 }
499 file_str += get_changed_values(ports, runtime);
500
501 return file_str;
502 }
503
load_from_file(const char * file_content,const Ports & ports,void * runtime,const char * appname,rtosc_version appver,savefile_dispatcher_t * dispatcher)504 int load_from_file(const char* file_content,
505 const Ports& ports, void* runtime,
506 const char* appname,
507 rtosc_version appver,
508 savefile_dispatcher_t* dispatcher)
509 {
510 char appbuf[128];
511 int bytes_read = 0;
512
513 if(dispatcher)
514 {
515 dispatcher->app_curver = appver;
516 dispatcher->rtosc_curver = rtosc_current_version();
517 }
518
519 unsigned vma, vmi, vre;
520 int n = 0;
521
522 sscanf(file_content,
523 "%% RT OSC v%u.%u.%u savefile%n ", &vma, &vmi, &vre, &n);
524 if(n <= 0 || vma > 255 || vmi > 255 || vre > 255)
525 return -bytes_read-1;
526 if(dispatcher)
527 {
528 dispatcher->rtosc_filever.major = vma;
529 dispatcher->rtosc_filever.minor = vmi;
530 dispatcher->rtosc_filever.revision = vre;
531 }
532 file_content += n;
533 bytes_read += n;
534 n = 0;
535
536 sscanf(file_content,
537 "%% %128s v%u.%u.%u%n ", appbuf, &vma, &vmi, &vre, &n);
538 if(n <= 0 || strcmp(appbuf, appname) || vma > 255 || vmi > 255 || vre > 255)
539 return -bytes_read-1;
540
541 if(dispatcher)
542 {
543 dispatcher->app_filever.major = vma;
544 dispatcher->app_filever.minor = vmi;
545 dispatcher->app_filever.revision = vre;
546 }
547 file_content += n;
548 bytes_read += n;
549 n = 0;
550
551 int rval = dispatch_printed_messages(file_content,
552 ports, runtime, dispatcher);
553 return (rval < 0) ? (rval-bytes_read) : rval;
554 }
555
556 }
557
558