MCPcopy Create free account
hub / github.com/PlotJuggler/PlotJuggler / installTransform

Method installTransform

pj_runtime/src/DataProcessorService.cpp:489–614  ·  view source on GitHub ↗

Source from the content-addressed store, hash-verified

487} // namespace
488
489Expected<DataProcessorService::TransformRecipe> DataProcessorService::installTransform(TransformRecipe recipe) {
490 // 1. Shape: N inputs -> M outputs (SISO is the 1->1 case). Field-level binding
491 // (a column within a multi-column input) is still a follow-up: inputs resolve
492 // to whole scalar topics here.
493 if (recipe.inputs.empty() || recipe.outputs.empty()) {
494 return PJ::unexpected("pj.data_processors: transform requires at least one input and one output");
495 }
496 for (const std::string& out_name : recipe.outputs) {
497 if (out_name.empty()) {
498 return PJ::unexpected("pj.data_processors: transform output name must be non-empty");
499 }
500 }
501 // 2. Backend (also the missing/unavailable-backend diagnostic on restore).
502 auto backend = inferTransformBackend(recipe.script);
503 if (!backend.has_value()) {
504 return PJ::unexpected(backend.error());
505 }
506 recipe.backend = std::move(backend).value();
507 // 3. Output-name collision, per output (excludes this transform's own outputs, so a replace is allowed).
508 for (const std::string& out_name : recipe.outputs) {
509 if (outputNameInUse(out_name, recipe.key)) {
510 return PJ::unexpected("pj.data_processors: output topic name already in use: " + out_name);
511 }
512 }
513 // 4. Resolve every input by name; the output topics are created in the first input's dataset.
514 std::vector<TopicId> input_topic_ids;
515 std::vector<std::size_t> input_columns; // leaf column per input (field-level binding)
516 input_topic_ids.reserve(recipe.inputs.size());
517 input_columns.reserve(recipe.inputs.size());
518 for (const std::string& in_name : recipe.inputs) {
519 const auto resolved = resolveInputField(in_name);
520 if (!resolved.has_value()) {
521 return PJ::unexpected("pj.data_processors: input topic/field not found: " + in_name);
522 }
523 if (input_topic_ids.empty()) {
524 recipe.dataset_id = resolved->dataset_id;
525 }
526 input_topic_ids.push_back(resolved->topic_id);
527 input_columns.push_back(resolved->column);
528 }
529 // Grouped output: a single structured "topic:f1,f2,..." entry collapses into ONE
530 // multi-column topic (parsed host-side, explicit — no prefix guessing). A grouped
531 // output is MIMO even when it is the only output entry, so it must not be mistaken
532 // for SISO, and its arity (M) is the count of declared fields.
533 const auto [mimo_group, mimo_fields] = parseGroupedOutput(recipe.outputs);
534 const bool grouped = !mimo_group.empty();
535 const bool is_siso = recipe.inputs.size() == 1 && recipe.outputs.size() == 1 && !grouped;
536 const std::size_t mimo_num_outputs = grouped ? mimo_fields.size() : recipe.outputs.size();
537 // 5. Compile BEFORE tearing down any old node, so a bad script never destroys a
538 // working transform. Use the catalogue's engine when installed, else a transient
539 // one (the built transform shares ownership of the engine, so it outlives it).
540 std::shared_ptr<proc::DataProcessor> siso_op; // set iff is_siso
541 std::unique_ptr<scripting::LuaMimoTransform> mimo_op; // set iff !is_siso
542 // Python transforms compile through a transient Python catalogue (the embedded
543 // interpreter is process-global). Luau reuses the installed catalogue.
544 const bool is_python = recipe.backend == "python";
545 if (is_siso) {
546 auto compiled = is_python ? scripting::FilterCatalogue(scripting::makePythonEngine())

Callers

nothing calls this directly

Calls 15

inferTransformBackendFunction · 0.85
parseGroupedOutputFunction · 0.85
makePythonEngineFunction · 0.85
makeLuauEngineFunction · 0.85
reserveMethod · 0.80
makeMimoFromSourceMethod · 0.80
addSisoTransformMethod · 0.80
addMimoTransformMethod · 0.80
onSourceCommittedMethod · 0.80
scheduleAllMethod · 0.80
outputTopicsMethod · 0.80

Tested by

no test coverage detected