MayaFlux 0.5.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches

◆ reconstruct() [1/2]

StateDecoder::ReconstructionResult MayaFlux::Nexus::StateDecoder::reconstruct ( Fabric fabric,
const std::string &  base_path 
)

Patch existing entities and construct missing ones from schema.

Entities whose id exists in the fabric are patched in place. Missing entities are constructed, their callables resolved via the fabric's function registry (no-op fallback with warning if absent), and wired. Supported wiring kinds for v2: every, move_to, commit_driven. Unsupported kinds (on, use, bind) fall back to commit_driven + warning. Hard failure (bad schema, unreadable EXR, dimension mismatch) sets last_error() and returns a zeroed result.

Parameters
fabricTarget fabric. May be empty, partial, or full.
base_pathPath stem without extension.
Returns
Counts and per-entity warnings.

Definition at line 344 of file Decoder.cpp.

345{
346 ReconstructionResult result;
347 m_last_error.clear();
348
349 const std::string json_path = base_path + ".json";
350 const std::string exr_path = base_path + ".exr";
351
352 IO::JSONSerializer ser;
353 auto schema_opt = ser.read<State::FabricSchema>(json_path);
354 if (!schema_opt) {
355 m_last_error = "Failed to load schema: " + ser.last_error();
357 return result;
358 }
359 const auto& schema = *schema_opt;
360
361 if (schema.version != State::k_schema_version) {
362 m_last_error = "Unsupported schema version: " + std::to_string(schema.version)
363 + " (expected " + std::to_string(State::k_schema_version) + ")";
365 return result;
366 }
367
368 if (schema.entities.empty()) {
369 m_last_error = "Schema contains no entities: " + json_path;
371 return result;
372 }
373
374 const uint32_t expected_rows = State::k_exr_rows;
375 auto pv_opt = load_exr(exr_path, static_cast<uint32_t>(schema.entities.size()), expected_rows, m_last_error);
376 if (!pv_opt) {
378 return result;
379 }
380 auto& pv = *pv_opt;
381 const auto* pixels = pv.image.as_float();
382 const uint32_t width = pv.width;
383 const auto& r = schema.ranges;
384
385 const auto existing_ids = fabric.all_ids();
386 const std::unordered_set<uint32_t> existing(existing_ids.begin(), existing_ids.end());
387
388 for (size_t i = 0; i < schema.entities.size(); ++i) {
389 const auto& entry = schema.entities[i];
390
391 if (!State::kind_known(entry.kind)) {
392 result.warnings.push_back("Unknown kind '" + entry.kind
393 + "' for id " + std::to_string(entry.id) + ", skipping");
394 ++result.skipped;
395 continue;
396 }
397
398 const size_t row0 = (static_cast<size_t>(0) * width + i) * State::k_channels;
399 const size_t row1 = (static_cast<size_t>(1) * width + i) * State::k_channels;
400 const size_t row2 = (static_cast<size_t>(2) * width + i) * State::k_channels;
401
402 const glm::vec3 position {
403 denormalize((*pixels)[row0 + 0], r.pos_x),
404 denormalize((*pixels)[row0 + 1], r.pos_y),
405 denormalize((*pixels)[row0 + 2], r.pos_z),
406 };
407 const float intensity = denormalize((*pixels)[row0 + 3], r.intensity);
408 const float radius = denormalize((*pixels)[row2 + 0], r.radius);
409 const float query_radius = denormalize((*pixels)[row2 + 1], r.query_radius);
410
411 auto read_color = [&]() -> glm::vec3 {
412 return {
413 denormalize((*pixels)[row1 + 0], r.color_r),
414 denormalize((*pixels)[row1 + 1], r.color_g),
415 denormalize((*pixels)[row1 + 2], r.color_b),
416 };
417 };
418 auto read_size = [&]() {
419 return denormalize((*pixels)[row1 + 3], r.size);
420 };
421
422 if (existing.count(entry.id)) {
423 // -----------------------------------------------------------------
424 // Patch existing entity.
425 // -----------------------------------------------------------------
426 switch (State::parse_kind(entry.kind)) {
428 auto e = fabric.get_emitter(entry.id);
429 if (!e) {
430 ++result.skipped;
431 continue;
432 }
433 if (!entry.influence_fn_name.empty() && e->fn_name() != entry.influence_fn_name) {
434 result.warnings.push_back("Emitter " + std::to_string(entry.id)
435 + " fn_name mismatch: schema='" + entry.influence_fn_name
436 + "' live='" + e->fn_name() + "'");
437 }
438 e->set_position(position);
439 e->set_intensity(intensity);
440 if (entry.color) {
441 e->set_color(read_color());
442 }
443 if (entry.size) {
444 e->set_size(read_size());
445 }
446 e->set_radius(radius);
447 break;
448 }
450 auto s = fabric.get_sensor(entry.id);
451 if (!s) {
452 ++result.skipped;
453 continue;
454 }
455 if (!entry.perception_fn_name.empty() && s->fn_name() != entry.perception_fn_name) {
456 result.warnings.push_back("Sensor " + std::to_string(entry.id)
457 + " fn_name mismatch: schema='" + entry.perception_fn_name
458 + "' live='" + s->fn_name() + "'");
459 }
460 s->set_position(position);
461 s->set_query_radius(query_radius);
462 break;
463 }
464 case Fabric::Kind::Agent: {
465 auto a = fabric.get_agent(entry.id);
466 if (!a) {
467 ++result.skipped;
468 continue;
469 }
470 if (!entry.perception_fn_name.empty() && a->perception_fn_name() != entry.perception_fn_name) {
471 result.warnings.push_back("Agent " + std::to_string(entry.id)
472 + " perception_fn mismatch: schema='" + entry.perception_fn_name + "'");
473 }
474 if (!entry.influence_fn_name.empty() && a->influence_fn_name() != entry.influence_fn_name) {
475 result.warnings.push_back("Agent " + std::to_string(entry.id)
476 + " influence_fn mismatch: schema='" + entry.influence_fn_name + "'");
477 }
478 a->set_position(position);
479 a->set_intensity(intensity);
480 if (entry.color) {
481 a->set_color(read_color());
482 }
483 if (entry.size) {
484 a->set_size(read_size());
485 }
486 a->set_radius(radius);
487 a->set_query_radius(query_radius);
488 break;
489 }
490 }
491 ++result.patched;
492
493 } else {
494 // -----------------------------------------------------------------
495 // Construct missing entity.
496 // -----------------------------------------------------------------
497 switch (State::parse_kind(entry.kind)) {
499 auto fn_ptr = fabric.resolve_influence_fn(entry.influence_fn_name);
501 if (!fn_ptr || !*fn_ptr) {
502 result.warnings.push_back("Emitter: unknown influence_fn '"
503 + entry.influence_fn_name + "', using no-op");
504 fn = [](const InfluenceContext&) { };
505 } else {
506 fn = *fn_ptr;
507 }
508 auto emitter = std::make_shared<Emitter>(entry.influence_fn_name, std::move(fn));
509 emitter->set_position(position);
510 emitter->set_intensity(intensity);
511 emitter->set_radius(radius);
512 if (entry.color) {
513 emitter->set_color(read_color());
514 }
515 if (entry.size) {
516 emitter->set_size(read_size());
517 }
518 auto wiring = fabric.wire(emitter);
519 if (emitter->id() != entry.id) {
520 result.warnings.push_back("Emitter schema_id=" + std::to_string(entry.id)
521 + " reconstructed as runtime_id=" + std::to_string(emitter->id()));
522 }
523 apply_wiring(std::move(wiring), entry.wiring, result.warnings);
524 break;
525 }
527 auto fn_ptr = fabric.resolve_perception_fn(entry.perception_fn_name);
529 if (!fn_ptr || !*fn_ptr) {
530 result.warnings.push_back("Sensor: unknown perception_fn '"
531 + entry.perception_fn_name + "', using no-op");
532 fn = [](const PerceptionContext&) { };
533 } else {
534 fn = *fn_ptr;
535 }
536 auto sensor = std::make_shared<Sensor>(query_radius,
537 entry.perception_fn_name, std::move(fn));
538 sensor->set_position(position);
539 auto wiring = fabric.wire(sensor);
540 if (sensor->id() != entry.id) {
541 result.warnings.push_back("Sensor schema_id=" + std::to_string(entry.id)
542 + " reconstructed as runtime_id=" + std::to_string(sensor->id()));
543 }
544 apply_wiring(std::move(wiring), entry.wiring, result.warnings);
545 break;
546 }
547 case Fabric::Kind::Agent: {
548 auto pfn_ptr = fabric.resolve_perception_fn(entry.perception_fn_name);
550 if (!pfn_ptr || !*pfn_ptr) {
551 result.warnings.push_back("Agent: unknown perception_fn '"
552 + entry.perception_fn_name + "', using no-op");
553 pfn = [](const PerceptionContext&) { };
554 } else {
555 pfn = *pfn_ptr;
556 }
557 auto ifn_ptr = fabric.resolve_influence_fn(entry.influence_fn_name);
559 if (!ifn_ptr || !*ifn_ptr) {
560 result.warnings.push_back("Agent: unknown influence_fn '"
561 + entry.influence_fn_name + "', using no-op");
562 ifn = [](const InfluenceContext&) { };
563 } else {
564 ifn = *ifn_ptr;
565 }
566 std::shared_ptr<Agent> agent;
567 if (entry.subkind == "locus" && entry.locus_nav) {
568 const auto& nav = *entry.locus_nav;
569 Kinesis::NavigationConfig cfg {
570 .initial_eye = nav.eye,
571 .initial_target = nav.target,
572 .fov_radians = nav.fov,
573 .near_plane = nav.near_plane,
574 .far_plane = nav.far_plane,
575 .move_speed = nav.speed,
576 };
577 agent = std::make_shared<Locus>(cfg, query_radius,
578 entry.perception_fn_name, std::move(pfn),
579 entry.influence_fn_name, std::move(ifn));
580 result.warnings.push_back("Locus " + std::to_string(entry.id)
581 + ": view_targets must be reconnected by caller");
582
583 } else if (entry.subkind == "presence") {
584 auto rfn_ptr = fabric.resolve_radiate_fn(entry.radiate_fn_name);
586 if (!rfn_ptr || !*rfn_ptr) {
587 result.warnings.push_back("Presence: unknown radiate_fn '"
588 + entry.radiate_fn_name + "', using no-op");
589 rfn = [](uint32_t, float) { };
590 } else {
591 rfn = *rfn_ptr;
592 }
593 auto presence = std::make_shared<Presence>(query_radius,
594 entry.perception_fn_name, std::move(pfn),
595 entry.influence_fn_name, std::move(ifn),
596 entry.radiate_fn_name, std::move(rfn));
597 if (!entry.falloff_curve_name.empty()) {
598 if (auto fc = Reflect::string_to_enum_case_insensitive<Presence::FalloffCurve>(entry.falloff_curve_name))
599 presence->set_falloff_curve(*fc);
600 }
601 if (entry.falloff_radius)
602 presence->set_falloff_radius(*entry.falloff_radius);
603 agent = std::move(presence);
604
605 } else {
606 if (entry.subkind == "locus") {
607 result.warnings.push_back("Locus " + std::to_string(entry.id)
608 + ": no locus_nav in schema, reconstructed as plain Agent");
609 }
610 agent = std::make_shared<Agent>(query_radius,
611 entry.perception_fn_name, std::move(pfn),
612 entry.influence_fn_name, std::move(ifn));
613 }
614 agent->set_position(position);
615 agent->set_intensity(intensity);
616 agent->set_radius(radius);
617 agent->set_query_radius(query_radius);
618 if (entry.color) {
619 agent->set_color(read_color());
620 }
621 if (entry.size) {
622 agent->set_size(read_size());
623 }
624 auto wiring = fabric.wire(agent);
625 if (agent->id() != entry.id) {
626 result.warnings.push_back("Agent schema_id=" + std::to_string(entry.id)
627 + " reconstructed as runtime_id=" + std::to_string(agent->id()));
628 }
629 apply_wiring(std::move(wiring), entry.wiring, result.warnings);
630 break;
631 }
632 }
633 ++result.constructed;
634 }
635 }
636
637 for (const auto& xrec : schema.expanses) {
638 if (xrec.fn_name.empty()) {
639 result.warnings.push_back("Expanse " + std::to_string(xrec.id)
640 + ": no fn_name, skipping");
641 continue;
642 }
643 auto contains_fn = fabric.resolve_expanse_fn(xrec.fn_name);
644 if (!contains_fn || !*contains_fn) {
645 result.warnings.push_back("Expanse " + std::to_string(xrec.id)
646 + ": fn '" + xrec.fn_name + "' not in registry, skipping");
647 continue;
648 }
649 auto on_enter_fn = xrec.on_enter_fn_name.empty()
651 : [ptr = fabric.resolve_crossing_fn(xrec.on_enter_fn_name)](uint32_t id) {
652 if (ptr && *ptr)
653 (*ptr)(id);
654 };
655 auto on_exit_fn = xrec.on_exit_fn_name.empty()
657 : [ptr = fabric.resolve_crossing_fn(xrec.on_exit_fn_name)](uint32_t id) {
658 if (ptr && *ptr)
659 (*ptr)(id);
660 };
661 auto expanse = std::make_shared<Expanse>(
662 xrec.fn_name,
663 xrec.on_enter_fn_name,
664 xrec.on_exit_fn_name,
665 *contains_fn,
666 std::move(on_enter_fn),
667 std::move(on_exit_fn));
668 fabric.add_expanse(std::move(expanse));
669 ++result.constructed;
670 }
671
673 "StateDecoder::reconstruct: constructed={} patched={} skipped={} warnings={}",
674 result.constructed, result.patched, result.skipped, result.warnings.size());
675
676 return result;
677}
#define MF_INFO(comp, ctx,...)
#define MF_ERROR(comp, ctx,...)
uint32_t width
Definition Decoder.cpp:66
const std::vector< float > * pixels
Definition Decoder.cpp:65
size_t a
const uint8_t * ptr
uint32_t radius
Cycle Behavior: The for_cycles(N) configuration controls how many times the capture operation execute...
std::function< void(const PerceptionContext &)> PerceptionFn
Definition Agent.hpp:31
std::function< void(const InfluenceContext &)> InfluenceFn
Definition Agent.hpp:30
std::function< void(const InfluenceContext &)> InfluenceFn
Definition Emitter.hpp:26
std::function< void(uint32_t)> CrossingFn
Definition Expanse.hpp:34
std::function< void(uint32_t id, float weight)> RadiateFn
Per-neighbor radiation callable.
Definition Presence.hpp:66
std::function< void(const PerceptionContext &)> PerceptionFn
Definition Sensor.hpp:21
@ FileIO
Filesystem I/O operations.
@ Nexus
Spatial indexing and scheduling for user-defined behaviour.
constexpr T denormalize(T t, T lo, T hi) noexcept
Denormalize t from [0, 1] to [lo, hi].
Definition Scalar.hpp:61
constexpr uint32_t k_schema_version
Current schema version written by StateEncoder and accepted by StateDecoder.
Definition Schema.hpp:20
bool kind_known(std::string_view s)
Return true if s maps to a known Fabric::Kind token.
Definition Schema.hpp:368
Fabric::Kind parse_kind(std::string_view s)
Parse a JSON kind token to Fabric::Kind (case-insensitive).
Definition Schema.hpp:379
constexpr uint32_t k_exr_rows
RGBA32F EXR layout constants shared between encoder and decoder.
Definition Schema.hpp:31
constexpr uint32_t k_channels
Definition Schema.hpp:32

References a, MayaFlux::Nexus::Fabric::add_expanse(), MayaFlux::Nexus::Fabric::Agent, MayaFlux::Nexus::Fabric::all_ids(), MayaFlux::Nexus::StateDecoder::ReconstructionResult::constructed, MayaFlux::Nexus::Fabric::Emitter, MayaFlux::Journal::FileIO, MayaFlux::Nexus::Fabric::get_agent(), MayaFlux::Nexus::Fabric::get_emitter(), MayaFlux::Nexus::Fabric::get_sensor(), MayaFlux::Kinesis::NavigationConfig::initial_eye, MayaFlux::Nexus::State::k_channels, MayaFlux::Nexus::State::k_exr_rows, MayaFlux::Nexus::State::k_schema_version, MayaFlux::Nexus::State::kind_known(), MayaFlux::IO::JSONSerializer::last_error(), m_last_error, MF_ERROR, MF_INFO, MayaFlux::Journal::Nexus, MayaFlux::Nexus::State::parse_kind(), MayaFlux::Nexus::StateDecoder::ReconstructionResult::patched, pixels, ptr, radius, MayaFlux::IO::JSONSerializer::read(), MayaFlux::Nexus::Fabric::resolve_crossing_fn(), MayaFlux::Nexus::Fabric::resolve_expanse_fn(), MayaFlux::Nexus::Fabric::resolve_influence_fn(), MayaFlux::Nexus::Fabric::resolve_perception_fn(), MayaFlux::Nexus::Fabric::resolve_radiate_fn(), MayaFlux::Nexus::Fabric::Sensor, MayaFlux::Nexus::StateDecoder::ReconstructionResult::skipped, MayaFlux::Nexus::StateDecoder::ReconstructionResult::warnings, width, and MayaFlux::Nexus::Fabric::wire().

Referenced by reconstruct().

+ Here is the call graph for this function:
+ Here is the caller graph for this function: