MayaFlux 0.4.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 337 of file Decoder.cpp.

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