node-editor.cpp 110 KB
Newer Older
1
2
3
4
// Copyright (c) 2020 INRA Distributed under the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

Gauthier Quesnel's avatar
Gauthier Quesnel committed
5
6
7
8
9
#ifdef _WIN32
#define NOMINMAX
#define WINDOWS_LEAN_AND_MEAN
#endif

10
11
12
#include "application.hpp"
#include "dialog.hpp"
#include "internal.hpp"
Gauthier Quesnel's avatar
Gauthier Quesnel committed
13

14
#include <cinttypes>
15
#include <fstream>
Gauthier Quesnel's avatar
Gauthier Quesnel committed
16
#include <string>
17

18
#include <fmt/format.h>
19
#include <irritator/core.hpp>
20
#include <irritator/examples.hpp>
Gauthier Quesnel's avatar
Gauthier Quesnel committed
21
#include <irritator/io.hpp>
22
23
24

namespace irt {

Gauthier Quesnel's avatar
Gauthier Quesnel committed
25
26
static ImVec4
operator*(const ImVec4& lhs, const float rhs) noexcept
Gauthier Quesnel's avatar
Gauthier Quesnel committed
27
28
29
30
{
    return ImVec4(lhs.x * rhs, lhs.y * rhs, lhs.z * rhs, lhs.w * rhs);
}

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
inline int
make_input_node_id(const irt::model_id mdl, const int port) noexcept
{
    irt_assert(port >= 0 && port < 8);

    irt::u32 index = irt::get_index(mdl);
    irt_assert(index < 268435456u);

    irt::u32 port_index = static_cast<irt::u32>(port) << 28u;
    index |= port_index;

    return static_cast<int>(index);
}

inline int
make_output_node_id(const irt::model_id mdl, const int port) noexcept
{
    irt_assert(port >= 0 && port < 8);

    irt::u32 index = irt::get_index(mdl);
    irt_assert(index < 268435456u);

    irt::u32 port_index = static_cast<irt::u32>(8u + port) << 28u;

    index |= port_index;

    return static_cast<int>(index);
}

inline std::pair<irt::u32, irt::u32>
get_model_input_port(const int node_id) noexcept
{
    const irt::u32 real_node_id = static_cast<irt::u32>(node_id);

    irt::u32 port = real_node_id >> 28u;
    irt_assert(port < 8u);

    constexpr irt::u32 mask = ~(15u << 28u);
    irt::u32 index = real_node_id & mask;

    return std::make_pair(index, port);
}

inline std::pair<irt::u32, irt::u32>
get_model_output_port(const int node_id) noexcept
{
    const irt::u32 real_node_id = static_cast<irt::u32>(node_id);

    irt::u32 port = real_node_id >> 28u;

    irt_assert(port >= 8u && port < 16u);
    port -= 8u;
    irt_assert(port < 8u);

    constexpr irt::u32 mask = ~(15u << 28u);

    irt::u32 index = real_node_id & mask;

    return std::make_pair(index, port);
}

Gauthier Quesnel's avatar
Gauthier Quesnel committed
92
93
94
95
96
97
98
99
100
101
102
103
104
editor::editor() noexcept
{
    context = ImNodes::EditorContextCreate();
    ImNodes::PushAttributeFlag(
      ImNodesAttributeFlags_EnableLinkDetachWithDragClick);
    ImNodesIO& io = ImNodes::GetIO();
    io.LinkDetachWithModifierClick.Modifier = &ImGui::GetIO().KeyCtrl;

    settings.compute_colors();
}

editor::~editor() noexcept
{
105
106
107
108
109
    if (context) {
        ImNodes::EditorContextSet(context);
        ImNodes::PopAttributeFlag();
        ImNodes::EditorContextFree(context);
    }
Gauthier Quesnel's avatar
Gauthier Quesnel committed
110
111
}

112
113
void
editor::settings_manager::compute_colors() noexcept
Gauthier Quesnel's avatar
Gauthier Quesnel committed
114
115
116
117
118
119
{
    gui_hovered_model_color =
      ImGui::ColorConvertFloat4ToU32(gui_model_color * 1.25f);
    gui_selected_model_color =
      ImGui::ColorConvertFloat4ToU32(gui_model_color * 1.5f);

Gauthier Quesnel's avatar
Gauthier Quesnel committed
120
121
122
123
124
    gui_hovered_model_transition_color =
      ImGui::ColorConvertFloat4ToU32(gui_model_transition_color * 1.25f);
    gui_selected_model_transition_color =
      ImGui::ColorConvertFloat4ToU32(gui_model_transition_color * 1.5f);

Gauthier Quesnel's avatar
Gauthier Quesnel committed
125
126
127
128
129
130
    gui_hovered_cluster_color =
      ImGui::ColorConvertFloat4ToU32(gui_cluster_color * 1.25f);
    gui_selected_cluster_color =
      ImGui::ColorConvertFloat4ToU32(gui_cluster_color * 1.5f);
}

131
132
void
editor::clear() noexcept
133
{
134
135
    clusters.clear();
    sim.clear();
136

137
138
139
    std::fill(std::begin(clusters_mapper),
              std::end(clusters_mapper),
              undefined<cluster_id>());
140

141
142
143
    std::fill(std::begin(models_mapper),
              std::end(models_mapper),
              undefined<cluster_id>());
144

145
146
    top.clear();
}
147

148
149
cluster_id
editor::ancestor(const child_id child) const noexcept
150
{
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
    if (child.index() == 0) {
        const auto mdl_id = std::get<model_id>(child);
        auto parent = models_mapper[get_index(mdl_id)];
        auto ret = parent;

        while (parent != undefined<cluster_id>()) {
            ret = parent;
            parent = clusters_mapper[get_index(parent)];
        }

        return ret;
    } else {
        const auto gp_id = std::get<cluster_id>(child);
        auto parent = clusters_mapper[get_index(gp_id)];
        auto ret = parent;
166

167
168
169
170
171
172
173
        while (parent != undefined<cluster_id>()) {
            ret = parent;
            parent = clusters_mapper[get_index(parent)];
        }

        return ret;
    }
174
}
175

176
177
int
editor::get_top_group_ref(const child_id child) const noexcept
178
{
179
180
181
182
183
    const auto top_ref = ancestor(child);

    return top_ref == undefined<cluster_id>() ? top.get_index(child)
                                              : top.get_index(top_ref);
}
184

185
186
187
188
bool
editor::is_in_hierarchy(const cluster& group,
                        const cluster_id group_to_search) const noexcept
{
189
190
191
    if (clusters.get_id(group) == group_to_search) {
        log_w.log(7, "clusters.get_id(group) == group_to_search\n");
        return true;
192
193
    }

194
195
196
197
    // TODO: Derecursive this part of the function.
    for (const auto elem : group.children) {
        if (elem.index() == 1) {
            const auto id = std::get<cluster_id>(elem);
198

199
200
201
202
            if (id == group_to_search) {
                log_w.log(7, "id == group_to_search\n");
                return true;
            }
203

204
            if (const auto* gp = clusters.try_to_get(id); gp) {
205
                if (is_in_hierarchy(*gp, group_to_search)) {
206
207
208
209
210
                    log_w.log(7, "is_in_hierarchy = true\n");
                    return true;
                }
            }
        }
211
212
    }

213
214
    return false;
}
215

216
217
218
219
220
221
void
editor::group(const ImVector<int>& nodes) noexcept
{
    if (!clusters.can_alloc(1)) {
        log_w.log(5, "Fail to allocate a new group.");
        return;
222
223
    }

224
225
226
227
    auto& new_cluster = clusters.alloc();
    auto new_cluster_id = clusters.get_id(new_cluster);
    format(new_cluster.name, "Group {}", new_cluster_id);
    parent(new_cluster_id, undefined<cluster_id>());
228

229
230
231
    /* First, move children models and groups from the current cluster into the
       newly allocated cluster. */

232
233
    for (int i = 0, e = nodes.size(); i != e; ++i) {
        if (auto index = top.get_index(nodes[i]); index != not_found) {
234
            new_cluster.children.push_back(top.children[index].first);
235
            top.pop(index);
236
237
        }
    }
238

239
    top.emplace_back(new_cluster_id);
240

241
242
243
244
245
246
247
248
249
    for (const auto child : new_cluster.children) {
        if (child.index() == 0) {
            const auto id = std::get<model_id>(child);
            parent(id, new_cluster_id);
        } else {
            const auto id = std::get<cluster_id>(child);
            parent(id, new_cluster_id);
        }
    }
250

251
252
253
254
    /* For all input and output ports of the remaining models in the current
       cluster, we try to detect if the corresponding model is or is not in the
       same cluster. */

Gauthier Quesnel's avatar
Gauthier Quesnel committed
255
    for (const auto& child : top.children) {
256
257
        if (child.first.index() == 0) {
            const auto child_id = std::get<model_id>(child.first);
258
259

            if (auto* model = sim.models.try_to_get(child_id); model) {
260
                sim.dispatch(
261
                  *model,
262
263
264
265
266
267
268
269
270
271
272
273
274
                  [this, &new_cluster]<typename Dynamics>(Dynamics& dyn) {
                      if constexpr (is_detected_v<has_input_port_t, Dynamics>) {
                          for (sz i = 0u, e = std::size(dyn.x); i != e; ++i) {
                              for (const auto& elem : dyn.x[i].connections) {
                                  auto* src = sim.models.try_to_get(elem.model);
                                  if (src &&
                                      is_in_hierarchy(new_cluster,
                                                      this->parent(elem.model)))
                                      new_cluster.output_ports.emplace_back(
                                        make_output_node_id(elem.model,
                                                            elem.port_index));
                              }
                          }
275
276
                      }

277
278
279
280
281
282
283
284
285
286
287
288
289
                      if constexpr (is_detected_v<has_output_port_t,
                                                  Dynamics>) {
                          for (sz i = 0u, e = std::size(dyn.y); i != e; ++i) {
                              for (const auto& elem : dyn.y[i].connections) {
                                  auto* src = sim.models.try_to_get(elem.model);
                                  if (src &&
                                      is_in_hierarchy(new_cluster,
                                                      this->parent(elem.model)))
                                      new_cluster.input_ports.emplace_back(
                                        make_input_node_id(elem.model,
                                                           elem.port_index));
                              }
                          }
290
291
292
293
                      }
                  });
            }
        } else {
294
            const auto child_id = std::get<cluster_id>(child.first);
295
296
297

            if (auto* group = clusters.try_to_get(child_id); group) {
                for (const auto id : group->input_ports) {
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
                    const auto model_port = get_in(id);

                    if (model_port.model) {
                        sim.dispatch(
                          *model_port.model,
                          [this, &new_cluster, &model_port]<typename Dynamics>(
                            Dynamics& dyn) {
                              if constexpr (is_detected_v<has_input_port_t,
                                                          Dynamics>) {
                                  for (sz i = 0u, e = std::size(dyn.x); i != e;
                                       ++i) {
                                      for (const auto& elem :
                                           dyn.x[model_port.port_index]
                                             .connections) {
                                          auto* src =
                                            sim.models.try_to_get(elem.model);
                                          if (src &&
                                              is_in_hierarchy(
                                                new_cluster,
                                                this->parent(elem.model)))
                                              new_cluster.output_ports
                                                .emplace_back(
                                                  make_output_node_id(
                                                    elem.model,
                                                    elem.port_index));
                                      }
                                  }
                              }
                          });
327
328
329
                    }
                }

330
                for (const auto id : group->output_ports) {
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
                    const auto model_port = get_out(id);

                    if (model_port.model) {
                        sim.dispatch(
                          *model_port.model,
                          [this, &new_cluster, &model_port]<typename Dynamics>(
                            Dynamics& dyn) {
                              if constexpr (is_detected_v<has_output_port_t,
                                                          Dynamics>) {
                                  for (sz i = 0u, e = std::size(dyn.y); i != e;
                                       ++i) {
                                      for (const auto& elem :
                                           dyn.y[model_port.port_index]
                                             .connections) {
                                          auto* dst =
                                            sim.models.try_to_get(elem.model);
                                          if (dst &&
                                              is_in_hierarchy(
                                                new_cluster,
                                                this->parent(elem.model)))
                                              new_cluster.input_ports
                                                .emplace_back(
                                                  make_input_node_id(
                                                    elem.model,
                                                    elem.port_index));
                                      }
                                  }
                              }
                          });
360
                    }
361
                }
Gauthier Quesnel's avatar
Gauthier Quesnel committed
362
            }
363
        }
364
365
    }
}
366

367
368
369
370
void
editor::ungroup(const int node) noexcept
{
    const auto index = top.get_index(node);
371

372
373
374
375
    if (index == not_found) {
        log_w.log(5, "ungroup model not in top\n");
        return;
    }
376

377
    if (top.children[index].first.index() == 0) {
378
379
380
        log_w.log(5, "node is not a group\n");
        return;
    }
381

382
    auto* group =
383
      clusters.try_to_get(std::get<cluster_id>(top.children[index].first));
384
385
386
387
    if (!group) {
        log_w.log(5, "group does not exist\n");
        return;
    }
388

389
390
    const auto group_id = clusters.get_id(*group);
    top.pop(index);
391

392
393
394
    for (size_t i = 0, e = group->children.size(); i != e; ++i) {
        if (group->children[i].index() == 0) {
            const auto id = std::get<model_id>(group->children[i]);
395
            if (auto* mdl = sim.models.try_to_get(id); mdl) {
396
397
398
399
400
                parent(id, undefined<cluster_id>());
                top.emplace_back(group->children[i]);
            }
        } else {
            auto id = std::get<cluster_id>(group->children[i]);
401
            if (auto* gp = clusters.try_to_get(id); gp) {
402
403
404
405
406
                parent(id, undefined<cluster_id>());
                top.emplace_back(group->children[i]);
            }
        }
    }
407

408
409
410
    clusters.free(*group);
    parent(group_id, undefined<cluster_id>());
}
411

412
413
414
415
416
417
418
419
420
421
void
editor::free_group(cluster& group) noexcept
{
    const auto group_id = clusters.get_id(group);

    for (const auto child : group.children) {
        if (child.index() == 0) {
            auto id = std::get<model_id>(child);
            models_mapper[get_index(id)] = undefined<cluster_id>();
            if (auto* mdl = sim.models.try_to_get(id); mdl) {
422
                log_w.log(7, "delete model %" PRIu64 "\n", id);
423
                sim.deallocate(id);
424

425
426
427
428
429
                observation_dispatch(
                  get_index(id),
                  [this](auto& outs, auto id) { outs.free(id); });

                observation_outputs[get_index(id)] = std::monostate{};
430
431
432
433
434
            }
        } else {
            auto id = std::get<cluster_id>(child);
            clusters_mapper[get_index(id)] = undefined<cluster_id>();
            if (auto* gp = clusters.try_to_get(id); gp) {
435
                log_w.log(7, "delete group %" PRIu64 "\n", gp);
436
437
438
439
                free_group(*gp);
            }
        }
    }
440

441
442
443
    parent(group_id, undefined<cluster_id>());
    clusters.free(group);
}
444

445
void
446
447
448
449
450
451
452
453
454
editor::free_children(const ImVector<int>& nodes) noexcept
{
    for (int i = 0, e = nodes.size(); i != e; ++i) {
        const auto index = top.get_index(nodes[i]);
        if (index == not_found)
            continue;

        const auto child = top.children[index];

455
456
        if (child.first.index() == 0) {
            const auto id = std::get<model_id>(child.first);
457
458
            if (auto* mdl = sim.models.try_to_get(id); mdl) {
                models_mapper[get_index(id)] = undefined<cluster_id>();
459
                log_w.log(7, "delete %" PRIu64 "\n", id);
460
461
                parent(id, undefined<cluster_id>());
                sim.deallocate(id);
462

463
464
465
466
467
                observation_dispatch(
                  get_index(id),
                  [this](auto& outs, const auto id) { outs.free(id); });

                observation_outputs[get_index(id)] = std::monostate{};
468
469
            }
        } else {
470
            const auto id = std::get<cluster_id>(child.first);
471
472
            if (auto* gp = clusters.try_to_get(id); gp) {
                clusters_mapper[get_index(id)] = undefined<cluster_id>();
473
                log_w.log(7, "delete group %" PRIu64 "\n", id);
474
475
476
                free_group(*gp);
            }
        }
477

478
479
480
        top.pop(index);
    }
}
481

482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
struct copier
{
    struct copy_model
    {
        copy_model() = default;

        copy_model(const model_id src_, const model_id dst_) noexcept
          : src(src_)
          , dst(dst_)
        {}

        model_id src, dst;
    };

    struct copy_cluster
    {
        copy_cluster() = default;

        copy_cluster(const cluster_id src_, const cluster_id dst_) noexcept
          : src(src_)
          , dst(dst_)
        {}

        cluster_id src, dst;
    };

    struct copy_input_port
    {
        copy_input_port() = default;

512
        copy_input_port(const int src_, const int dst_) noexcept
513
514
515
516
          : src(src_)
          , dst(dst_)
        {}

517
        int src, dst;
518
519
520
521
522
523
    };

    struct copy_output_port
    {
        copy_output_port() = default;

524
        copy_output_port(const int src_, const int dst_) noexcept
525
526
527
528
          : src(src_)
          , dst(dst_)
        {}

529
        int src, dst;
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
    };

    std::vector<copy_model> c_models;
    std::vector<copy_cluster> c_clusters;
    std::vector<copy_input_port> c_input_ports;
    std::vector<copy_output_port> c_output_ports;

    void sort() noexcept
    {
        std::sort(std::begin(c_models),
                  std::end(c_models),
                  [](const auto left, const auto right) {
                      return static_cast<u64>(left.src) <
                             static_cast<u64>(right.src);
                  });

        std::sort(std::begin(c_clusters),
                  std::end(c_clusters),
                  [](const auto left, const auto right) {
                      return static_cast<u64>(left.src) <
                             static_cast<u64>(right.src);
                  });

        std::sort(std::begin(c_input_ports),
                  std::end(c_input_ports),
                  [](const auto left, const auto right) {
                      return static_cast<u64>(left.src) <
                             static_cast<u64>(right.src);
                  });

        std::sort(std::begin(c_output_ports),
                  std::end(c_output_ports),
                  [](const auto left, const auto right) {
                      return static_cast<u64>(left.src) <
                             static_cast<u64>(right.src);
                  });
    }

    template<typename Container, typename T>
    static int get(const Container& c, const T src) noexcept
    {
571
        const typename Container::value_type val{};
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596

        auto it = std::lower_bound(std::begin(c),
                                   std::end(c),
                                   val,
                                   [](const auto& left, const auto& right) {
                                       return static_cast<u64>(left.src) <
                                              static_cast<u64>(right.src);
                                   });

        return (it != std::end(c) &&
                static_cast<u64>(src) == static_cast<u64>(it->src))
                 ? static_cast<int>(std::distance(std::begin(c), it))
                 : not_found;
    }

    int get_model(const model_id src) const noexcept
    {
        return get(c_models, src);
    }

    int get_cluster(const cluster_id src) const noexcept
    {
        return get(c_clusters, src);
    }

597
    int get_input_port(const int src) const noexcept
598
599
600
601
    {
        return get(c_input_ports, src);
    }

602
    int get_output_port(const int src) const noexcept
603
604
605
606
607
608
609
610
611
612
613
614
615
616
    {
        return get(c_output_ports, src);
    }

    status copy(editor& ed,
                const size_t models_to_merge_with_top,
                const size_t clusters_to_merge_with_top)
    {
        auto& sim = ed.sim;

        for (size_t i = 0, e = std::size(c_models); i != e; ++i) {
            auto* mdl = sim.models.try_to_get(c_models[i].src);
            auto* mdl_id_dst = &c_models[i].dst;

617
            auto ret = sim.dispatch(
618
619
              *mdl,
              [this, &sim, mdl, &mdl_id_dst]<typename Dynamics>(
Gauthier Quesnel's avatar
Gauthier Quesnel committed
620
                Dynamics& /*dyn*/) -> status {
621
                  irt_return_if_fail(sim.models.can_alloc(1),
Gauthier Quesnel's avatar
Gauthier Quesnel committed
622
                                     status::dynamics_not_enough_memory);
623

624
625
                  auto& new_dyn = sim.alloc<Dynamics>();
                  *mdl_id_dst = sim.get_id(new_dyn);
626

Gauthier Quesnel's avatar
Gauthier Quesnel committed
627
                  if constexpr (is_detected_v<has_input_port_t, Dynamics>)
628
629
630
631
                      for (sz j = 0u, ej = std::size(new_dyn.x); j != ej; ++j)
                          this->c_input_ports.emplace_back(
                            make_input_node_id(sim.models.get_id(mdl), (int)j),
                            make_input_node_id(*mdl_id_dst, (int)j));
632

Gauthier Quesnel's avatar
Gauthier Quesnel committed
633
                  if constexpr (is_detected_v<has_output_port_t, Dynamics>)
634
635
636
637
                      for (sz j = 0, ej = std::size(new_dyn.y); j != ej; ++j)
                          this->c_output_ports.emplace_back(
                            make_output_node_id(sim.models.get_id(mdl), (int)j),
                            make_input_node_id(*mdl_id_dst, (int)j));
638

Gauthier Quesnel's avatar
Gauthier Quesnel committed
639
640
                  return status::success;
              });
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679

            irt_return_if_bad(ret);
        }

        for (size_t i = 0, e = std::size(c_clusters); i != e; ++i) {
            auto* gp_src = ed.clusters.try_to_get(c_clusters[i].src);
            auto& gp_dst = ed.clusters.alloc(*gp_src);
            c_clusters[i].dst = ed.clusters.get_id(gp_dst);
        }

        sort();

        for (size_t i = 0, e = std::size(c_clusters); i != e; ++i) {
            auto* gp_src = ed.clusters.try_to_get(c_clusters[i].src);
            auto* gp_dst = ed.clusters.try_to_get(c_clusters[i].dst);

            for (size_t j = 0, ej = gp_src->children.size(); j != ej; ++j) {
                if (gp_src->children[j].index() == 0) {
                    const auto id = std::get<model_id>(gp_src->children[j]);
                    const auto index = get_model(id);
                    gp_dst->children[j] = c_models[index].dst;
                } else {
                    const auto id = std::get<cluster_id>(gp_src->children[j]);
                    const auto index = get_cluster(id);
                    gp_dst->children[j] = c_clusters[index].dst;
                }
            }

            for (size_t j = 0, ej = gp_src->input_ports.size(); j != ej; ++j) {
                const auto index = get_input_port(gp_src->input_ports[j]);
                gp_dst->input_ports[j] = c_input_ports[index].dst;
            }

            for (size_t j = 0, ej = gp_src->output_ports.size(); j != ej; ++j) {
                const auto index = get_output_port(gp_src->output_ports[j]);
                gp_dst->output_ports[j] = c_output_ports[index].dst;
            }
        }

680
681
682
683
        // for (size_t i = 0, e = std::size(c_input_ports); i != e; ++i) {
        //    const auto* src =
        //    sim.input_ports.try_to_get(c_input_ports[i].src); auto* dst =
        //    sim.input_ports.try_to_get(c_input_ports[i].dst);
684

685
        //    assert(dst->connections.empty());
686

687
688
689
690
691
        //    for (const auto port : src->connections) {
        //        const auto index = get_output_port(port);
        //        dst->connections.emplace_front(c_output_ports[index].dst);
        //    }
        //}
692

693
694
695
696
        // for (size_t i = 0, e = std::size(c_output_ports); i != e; ++i) {
        //    const auto* src =
        //      sim.output_ports.try_to_get(c_output_ports[i].src);
        //    auto* dst = sim.output_ports.try_to_get(c_output_ports[i].dst);
697

698
        //    assert(dst->connections.empty());
699

700
701
702
703
704
        //    for (const auto port : src->connections) {
        //        const auto index = get_input_port(port);
        //        dst->connections.emplace_front(c_input_ports[index].dst);
        //    }
        //}
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742

        for (size_t i = 0, e = std::size(c_models); i != e; ++i) {
            const auto parent_src = ed.parent(c_models[i].src);
            const auto index = get_cluster(parent_src);

            if (index == not_found)
                ed.parent(c_models[i].dst, parent_src);
            else
                ed.parent(c_models[i].dst, c_clusters[index].dst);
        }

        for (size_t i = 0, e = std::size(c_clusters); i != e; ++i) {
            const auto parent_src = ed.parent(c_clusters[i].src);
            const auto index = get_cluster(parent_src);

            if (index == not_found)
                ed.parent(c_models[i].dst, parent_src);
            else
                ed.parent(c_models[i].dst, c_clusters[index].dst);
        }

        /* Finally, merge clusters and models from user selection into the
           editor.top structure. */

        for (size_t i = 0; i != models_to_merge_with_top; ++i) {
            ed.top.emplace_back(c_models[i].dst);
            ed.parent(c_models[i].dst, undefined<cluster_id>());
        }

        for (size_t i = 0; i != clusters_to_merge_with_top; ++i) {
            ed.top.emplace_back(c_clusters[i].dst);
            ed.parent(c_clusters[i].dst, undefined<cluster_id>());
        }

        return status::success;
    }
};

743
static void
744
745
746
747
compute_connection_distance(const child_id src,
                            const child_id dst,
                            editor& ed,
                            const float k)
748
{
749
750
    const auto v = ed.get_top_group_ref(src);
    const auto u = ed.get_top_group_ref(dst);
751

752
753
754
755
756
    const float dx = ed.positions[v].x - ed.positions[u].x;
    const float dy = ed.positions[v].y - ed.positions[u].y;
    if (dx && dy) {
        const float d2 = dx * dx / dy * dy;
        const float coeff = std::sqrt(d2) / k;
757

758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
        ed.displacements[v].x -= dx * coeff;
        ed.displacements[v].y -= dy * coeff;
        ed.displacements[u].x += dx * coeff;
        ed.displacements[u].y += dy * coeff;
    }
}

static void
compute_connection_distance(const model& mdl,
                            const int port,
                            editor& ed,
                            const float k)
{
    ed.sim.dispatch(
      mdl, [&mdl, port, &ed, k]<typename Dynamics>(Dynamics& dyn) -> void {
          if constexpr (is_detected_v<has_output_port_t, Dynamics>) {
              for (auto& dst : dyn.y[port].connections)
                  compute_connection_distance(
                    ed.sim.get_id(mdl), dst.model, ed, k);
          }
      });
}

static void
compute_connection_distance(const model& mdl, editor& ed, const float k)
{
    ed.sim.dispatch(
      mdl, [&mdl, &ed, k]<typename Dynamics>(Dynamics& dyn) -> void {
          if constexpr (is_detected_v<has_output_port_t, Dynamics>) {
              for (sz i = 0, e = std::size(dyn.y); i != e; ++i)
                  for (auto& dst : dyn.y[i].connections)
                      compute_connection_distance(
                        ed.sim.get_id(mdl), dst.model, ed, k);
          }
      });
793
794
795
}

void
796
editor::compute_grid_layout() noexcept
797
798
{
    const auto size = length(top.children);
799
800
801
802

    if (size == 0)
        return;

803
804
805
806
807
808
809
810
811
812
    const auto tmp = std::sqrt(size);
    const auto column = static_cast<int>(tmp);
    auto line = column;
    auto remaining = size - (column * line);

    while (remaining > column) {
        ++line;
        remaining -= column;
    }

Gauthier Quesnel's avatar
Gauthier Quesnel committed
813
    const auto panning = ImNodes::EditorContextGetPanning();
814
815
816
817
818
    auto new_pos = panning;

    int elem = 0;

    for (int i = 0; i < column; ++i) {
819
820
        new_pos.y =
          panning.y + static_cast<float>(i) * settings.grid_layout_y_distance;
821
        for (int j = 0; j < line; ++j) {
822
823
            new_pos.x = panning.x +
                        static_cast<float>(j) * settings.grid_layout_x_distance;
Gauthier Quesnel's avatar
Gauthier Quesnel committed
824
            ImNodes::SetNodeGridSpacePos(top.children[elem].second, new_pos);
825
826
827
828
829
830
831
            positions[elem].x = new_pos.x;
            positions[elem].y = new_pos.y;
            ++elem;
        }
    }

    new_pos.x = panning.x;
832
833
    new_pos.y =
      panning.y + static_cast<float>(column) * settings.grid_layout_y_distance;
834
    for (int j = 0; j < remaining; ++j) {
835
836
        new_pos.x =
          panning.x + static_cast<float>(j) * settings.grid_layout_x_distance;
Gauthier Quesnel's avatar
Gauthier Quesnel committed
837
        ImNodes::SetNodeGridSpacePos(top.children[elem].second, new_pos);
838
839
840
841
        positions[elem].x = new_pos.x;
        positions[elem].y = new_pos.y;
        ++elem;
    }
842

Gauthier Quesnel's avatar
Gauthier Quesnel committed
843
    ImNodes::EditorContextResetPanning(positions[0]);
Gauthier Quesnel's avatar
Gauthier Quesnel committed
844
}
845

Gauthier Quesnel's avatar
Gauthier Quesnel committed
846
void
847
editor::compute_automatic_layout() noexcept
Gauthier Quesnel's avatar
Gauthier Quesnel committed
848
{
849
850
    /* See. Graph drawing by Forced-directed Placement by Thomas M. J.
       Fruchterman and Edward M. Reingold in Software--Pratice and
Gauthier Quesnel's avatar
Gauthier Quesnel committed
851
852
853
854
855
856
857
858
859
860
861
862
863
       Experience, Vol. 21(1 1), 1129-1164 (november 1991).
       */

    const auto size = length(top.children);
    const auto tmp = std::sqrt(size);
    const auto column = static_cast<int>(tmp);
    auto line = column;
    auto remaining = size - (column * line);

    while (remaining > column) {
        ++line;
        remaining -= column;
    }
864

865
866
867
868
    const float W =
      static_cast<float>(column) * settings.automatic_layout_x_distance;
    const float L =
      line + (remaining > 0) ? settings.automatic_layout_y_distance : 0.f;
869
870
871
872
    const float area = W * L;
    const float k_square = area / static_cast<float>(top.children.size());
    const float k = std::sqrt(k_square);

Gauthier Quesnel's avatar
Gauthier Quesnel committed
873
874
875
876
    // float t = 1.f - static_cast<float>(iteration) /
    //                   static_cast<float>(automatic_layout_iteration_limit);
    // t *= t;

877
878
    float t =
      1.f - 1.f / static_cast<float>(settings.automatic_layout_iteration_limit);
879

880
881
    for (int iteration = 0;
         iteration < settings.automatic_layout_iteration_limit;
882
883
884
         ++iteration) {
        for (int i_v = 0; i_v < size; ++i_v) {
            const int v = i_v;
885

886
            displacements[v].x = displacements[v].y = 0.f;
887

888
889
            for (int i_u = 0; i_u < size; ++i_u) {
                const int u = i_u;
890

891
892
893
                if (u != v) {
                    const ImVec2 delta{ positions[v].x - positions[u].x,
                                        positions[v].y - positions[u].y };
894

895
896
897
                    if (delta.x && delta.y) {
                        const float d2 = delta.x * delta.x + delta.y * delta.y;
                        const float coeff = k_square / d2;
898

899
900
901
                        displacements[v].x += coeff * delta.x;
                        displacements[v].y += coeff * delta.y;
                    }
902
903
904
                }
            }
        }
Gauthier Quesnel's avatar
Gauthier Quesnel committed
905

906
907
908
        for (size_t i = 0, e = top.children.size(); i != e; ++i) {
            if (top.children[i].first.index() == 0) {
                const auto id = std::get<model_id>(top.children[i].first);
909
910
                if (const auto* mdl = sim.models.try_to_get(id); mdl)
                    compute_connection_distance(*mdl, *this, k);
911
912
913
            } else {
                const auto id = std::get<cluster_id>(top.children[i].first);
                if (auto* gp = clusters.try_to_get(id); gp) {
914
915
916
917
918
919
920
921
922
                    for (sz i = 0; i < std::size(gp->output_ports); ++i) {
                        auto model_port = get_out(gp->output_ports[i]);
                        if (model_port.model) {
                            compute_connection_distance(*model_port.model,
                                                        model_port.port_index,
                                                        *this,
                                                        k);
                        }
                    }
923
                }
924
925
926
            }
        }

927
928
929
        auto sum = 0.f;
        for (int i_v = 0; i_v < size; ++i_v) {
            const int v = i_v;
930

931
932
933
            const float d2 = displacements[v].x * displacements[v].x +
                             displacements[v].y * displacements[v].y;
            const float d = std::sqrt(d2);
934

935
936
937
938
939
940
941
942
            if (d > t) {
                const float coeff = t / d;
                displacements[v].x *= coeff;
                displacements[v].y *= coeff;
                sum += t;
            } else {
                sum += d;
            }
943

944
945
            positions[v].x += displacements[v].x;
            positions[v].y += displacements[v].y;
946

Gauthier Quesnel's avatar
Gauthier Quesnel committed
947
            ImNodes::SetNodeGridSpacePos(top.children[v].second, positions[v]);
948
        }
949
    }
950

Gauthier Quesnel's avatar
Gauthier Quesnel committed
951
    ImNodes::EditorContextResetPanning(positions[0]);
952
953
}

954
955
status
editor::copy(const ImVector<int>& nodes) noexcept
956
{
957
958
959
960
961
962
963
964
965
966
967
    copier cp;

    std::vector<cluster_id> copy_stack;

    for (int i = 0, e = nodes.size(); i != e; ++i) {
        const auto index = top.get_index(nodes[i]);
        if (index == not_found)
            continue;

        const auto child = top.children[index];

968
969
        if (child.first.index() == 0) {
            const auto id = std::get<model_id>(child.first);
970
971
972
            if (auto* mdl = sim.models.try_to_get(id); mdl)
                cp.c_models.emplace_back(id, undefined<model_id>());
        } else {
973
            const auto id = std::get<cluster_id>(child.first);
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
            if (auto* gp = clusters.try_to_get(id); gp) {
                cp.c_clusters.emplace_back(id, undefined<cluster_id>());
                copy_stack.emplace_back(id);
            }
        }
    }

    const auto models_to_merge_with_top = std::size(cp.c_models);
    const auto clusters_to_merge_with_top = std::size(cp.c_clusters);

    while (!copy_stack.empty()) {
        const auto gp_id = copy_stack.back();
        copy_stack.pop_back();

        if (auto* gp = clusters.try_to_get(gp_id); gp) {
            for (const auto child : gp->children) {
                if (child.index() == 0) {
                    const auto id = std::get<model_id>(child);
                    if (auto* mdl = sim.models.try_to_get(id); mdl)
                        cp.c_models.emplace_back(id, undefined<model_id>());
                } else {
                    const auto id = std::get<cluster_id>(child);
                    if (auto* gp = clusters.try_to_get(id); gp) {
                        cp.c_clusters.emplace_back(id, undefined<cluster_id>());
                        copy_stack.emplace_back(id);
                    }
                }
            }
        }
    }

    return cp.copy(*this, models_to_merge_with_top, clusters_to_merge_with_top);
1006
}
1007

1008
1009
1010
status
editor::initialize(u32 id) noexcept
{
1011
1012
    irt_return_if_bad(sim.init(to_unsigned(settings.kernel_model_cache),
                               to_unsigned(settings.kernel_message_cache)));
Gauthier Quesnel's avatar
Gauthier Quesnel committed
1013
    irt_return_if_bad(clusters.init(sim.models.capacity()));
1014
1015
1016
    irt_return_if_bad(top.init(to_unsigned(settings.gui_node_cache)));
    irt_return_if_bad(plot_outs.init(to_unsigned(settings.kernel_model_cache)));
    irt_return_if_bad(file_outs.init(to_unsigned(settings.kernel_model_cache)));
1017
1018
    irt_return_if_bad(
      file_discrete_outs.init(to_unsigned(settings.kernel_model_cache)));
1019

1020
1021
    irt_return_if_bad(srcs.init(50));
    sim.source_dispatch = srcs;
1022

Gauthier Quesnel's avatar
Gauthier Quesnel committed
1023
1024
1025
1026
1027
    try {
        observation_outputs.resize(sim.models.capacity());
        models_mapper.resize(sim.models.capacity(), undefined<cluster_id>());
        clusters_mapper.resize(sim.models.capacity(), undefined<cluster_id>());
        models_make_transition.resize(sim.models.capacity(), false);
1028

Gauthier Quesnel's avatar
Gauthier Quesnel committed
1029
1030
1031
1032
        positions.resize(sim.models.capacity() + clusters.capacity(),
                         ImVec2{ 0.f, 0.f });
        displacements.resize(sim.models.capacity() + clusters.capacity(),
                             ImVec2{ 0.f, 0.f });
1033
1034
1035

        observation_directory = std::filesystem::current_path();

Gauthier Quesnel's avatar
Gauthier Quesnel committed
1036
1037
1038
    } catch (const std::bad_alloc& /*e*/) {
        return status::gui_not_enough_memory;
    }
Gauthier Quesnel's avatar
Gauthier Quesnel committed
1039

1040
1041
1042
    use_real_time = false;
    synchronize_timestep = 0.;

1043
    format(name, "Editor {}", id);
1044

1045
1046
    return status::success;
}
1047

1048
1049
1050
status
editor::add_lotka_volterra() noexcept
{
1051
    if (!sim.models.can_alloc(10))
1052
1053
        return status::simulation_not_enough_model;

1054
1055
1056
1057
1058
1059
1060
    auto& sum_a = sim.alloc<adder_2>();
    auto& sum_b = sim.alloc<adder_2>();
    auto& product = sim.alloc<mult_2>();
    auto& integrator_a = sim.alloc<integrator>();
    auto& integrator_b = sim.alloc<integrator>();
    auto& quantifier_a = sim.alloc<quantifier>();
    auto& quantifier_b = sim.alloc<quantifier>();
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082

    integrator_a.default_current_value = 18.0;

    quantifier_a.default_adapt_state = irt::quantifier::adapt_state::possible;
    quantifier_a.default_zero_init_offset = true;
    quantifier_a.default_step_size = 0.01;
    quantifier_a.default_past_length = 3;

    integrator_b.default_current_value = 7.0;

    quantifier_b.default_adapt_state = irt::quantifier::adapt_state::possible;
    quantifier_b.default_zero_init_offset = true;
    quantifier_b.default_step_size = 0.01;
    quantifier_b.default_past_length = 3;

    product.default_input_coeffs[0] = 1.0;
    product.default_input_coeffs[1] = 1.0;
    sum_a.default_input_coeffs[0] = 2.0;
    sum_a.default_input_coeffs[1] = -0.4;
    sum_b.default_input_coeffs[0] = -1.0;
    sum_b.default_input_coeffs[1] = 0.1;

1083
1084
    irt_return_if_bad(sim.connect(sum_a, 0, integrator_a, 1));
    irt_return_if_bad(sim.connect(sum_b, 0, integrator_b, 1));
1085

1086
1087
    irt_return_if_bad(sim.connect(integrator_a, 0, sum_a, 0));
    irt_return_if_bad(sim.connect(integrator_b, 0, sum_b, 0));
1088

1089
1090
    irt_return_if_bad(sim.connect(integrator_a, 0, product, 0));
    irt_return_if_bad(sim.connect(integrator_b, 0, product, 1));
1091

1092
1093
    irt_return_if_bad(sim.connect(product, 0, sum_a, 1));
    irt_return_if_bad(sim.connect(product, 0, sum_b, 1));
1094

1095
1096
1097
1098
    irt_return_if_bad(sim.connect(quantifier_a, 0, integrator_a, 0));
    irt_return_if_bad(sim.connect(quantifier_b, 0, integrator_b, 0));
    irt_return_if_bad(sim.connect(integrator_a, 0, quantifier_a, 0));
    irt_return_if_bad(sim.connect(integrator_b, 0, quantifier_b, 0));
1099

1100
1101
1102
1103
1104
1105
1106
    top.emplace_back(sim.get_id(sum_a));
    top.emplace_back(sim.get_id(sum_b));
    top.emplace_back(sim.get_id(product));
    top.emplace_back(sim.get_id(integrator_a));
    top.emplace_back(sim.get_id(integrator_b));
    top.emplace_back(sim.get_id(quantifier_a));
    top.emplace_back(sim.get_id(quantifier_b));
1107

1108
1109
1110
1111
1112
1113
1114
    parent(sim.get_id(sum_a), undefined<cluster_id>());
    parent(sim.get_id(sum_b), undefined<cluster_id>());
    parent(sim.get_id(product), undefined<cluster_id>());
    parent(sim.get_id(integrator_a), undefined<cluster_id>());
    parent(sim.get_id(integrator_b), undefined<cluster_id>());
    parent(sim.get_id(quantifier_a), undefined<cluster_id>());
    parent(sim.get_id(quantifier_b), undefined<cluster_id>());
1115
1116
1117

    return status::success;
}
1118

1119
1120
1121
status
editor::add_izhikevitch() noexcept
{
1122
    if (!sim.models.can_alloc(14))
1123
1124
        return status::simulation_not_enough_model;

1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
    auto& constant = sim.alloc<irt::constant>();
    auto& constant2 = sim.alloc<irt::constant>();
    auto& constant3 = sim.alloc<irt::constant>();
    auto& sum_a = sim.alloc<irt::adder_2>();
    auto& sum_b = sim.alloc<irt::adder_2>();
    auto& sum_c = sim.alloc<irt::adder_4>();
    auto& sum_d = sim.alloc<irt::adder_2>();
    auto& product = sim.alloc<irt::mult_2>();
    auto& integrator_a = sim.alloc<irt::integrator>();
    auto& integrator_b = sim.alloc<irt::integrator>();
    auto& quantifier_a = sim.alloc<irt::quantifier>();
    auto& quantifier_b = sim.alloc<irt::quantifier>();
    auto& cross = sim.alloc<irt::cross>();
    auto& cross2 = sim.alloc<irt::cross>();
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181

    double a = 0.2;
    double b = 2.0;
    double c = -56.0;
    double d = -16.0;
    double I = -99.0;
    double vt = 30.0;

    constant.default_value = 1.0;
    constant2.default_value = c;
    constant3.default_value = I;

    cross.default_threshold = vt;
    cross2.default_threshold = vt;

    integrator_a.default_current_value = 0.0;

    quantifier_a.default_adapt_state = irt::quantifier::adapt_state::possible;
    quantifier_a.default_zero_init_offset = true;
    quantifier_a.default_step_size = 0.01;
    quantifier_a.default_past_length = 3;

    integrator_b.default_current_value = 0.0;

    quantifier_b.default_adapt_state = irt::quantifier::adapt_state::possible;
    quantifier_b.default_zero_init_offset = true;
    quantifier_b.default_step_size = 0.01;
    quantifier_b.default_past_length = 3;

    product.default_input_coeffs[0] = 1.0;
    product.default_input_coeffs[1] = 1.0;

    sum_a.default_input_coeffs[0] = 1.0;
    sum_a.default_input_coeffs[1] = -1.0;
    sum_b.default_input_coeffs[0] = -a;
    sum_b.default_input_coeffs[1] = a * b;
    sum_c.default_input_coeffs[0] = 0.04;
    sum_c.default_input_coeffs[1] = 5.0;
    sum_c.default_input_coeffs[2] = 140.0;
    sum_c.default_input_coeffs[3] = 1.0;
    sum_d.default_input_coeffs[0] = 1.0;
    sum_d.default_input_coeffs[1] = d;

1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
    irt_return_if_bad(sim.connect(integrator_a, 0, cross, 0));
    irt_return_if_bad(sim.connect(constant2, 0, cross, 1));
    irt_return_if_bad(sim.connect(integrator_a, 0, cross, 2));

    irt_return_if_bad(sim.connect(cross, 0, quantifier_a, 0));
    irt_return_if_bad(sim.connect(cross, 0, product, 0));
    irt_return_if_bad(sim.connect(cross, 0, product, 1));
    irt_return_if_bad(sim.connect(product, 0, sum_c, 0));
    irt_return_if_bad(sim.connect(cross, 0, sum_c, 1));
    irt_return_if_bad(sim.connect(cross, 0, sum_b, 1));

    irt_return_if_bad(sim.connect(constant, 0, sum_c, 2));
    irt_return_if_bad(sim.connect(constant3, 0, sum_c, 3));

    irt_return_if_bad(sim.connect(sum_c, 0, sum_a, 0));
    irt_return_if_bad(sim.connect(integrator_b, 0, sum_a, 1));
    irt_return_if_bad(sim.connect(cross2, 0, sum_a, 1));
    irt_return_if_bad(sim.connect(sum_a, 0, integrator_a, 1));
    irt_return_if_bad(sim.connect(cross, 0, integrator_a, 2));
    irt_return_if_bad(sim.connect(quantifier_a, 0, integrator_a, 0));

    irt_return_if_bad(sim.connect(cross2, 0, quantifier_b, 0));
    irt_return_if_bad(sim.connect(cross2, 0, sum_b, 0));
    irt_return_if_bad(sim.connect(quantifier_b, 0, integrator_b, 0));
    irt_return_if_bad(sim.connect(sum_b, 0, integrator_b, 1));

    irt_return_if_bad(sim.connect(cross2, 0, integrator_b, 2));
    irt_return_if_bad(sim.connect(integrator_a, 0, cross2, 0));
    irt_return_if_bad(sim.connect(integrator_b, 0, cross2, 2));
    irt_return_if_bad(sim.connect(sum_d, 0, cross2, 1));
    irt_return_if_bad(sim.connect(integrator_b, 0, sum_d, 0));
    irt_return_if_bad(sim.connect(constant, 0, sum_d, 1));

    top.emplace_back(sim.get_id(constant));
    top.emplace_back(sim.get_id(constant2));
    top.emplace_back(sim.get_id(constant3));
    top.emplace_back(sim.get_id(sum_a));
    top.emplace_back(sim.get_id(sum_b));
    top.emplace_back(sim.get_id(sum_c));
    top.emplace_back(sim.get_id(sum_d));
    top.emplace_back(sim.get_id(product));
    top.emplace_back(sim.get_id(integrator_a));
    top.emplace_back(sim.get_id(integrator_b));
    top.emplace_back(sim.get_id(quantifier_a));
    top.emplace_back(sim.get_id(quantifier_b));
    top.emplace_back(sim.get_id(cross));
    top.emplace_back(sim.get_id(cross2));

    parent(sim.get_id(constant), undefined<cluster_id>());
    parent(sim.get_id(constant2), undefined<cluster_id>());
    parent(sim.get_id(constant3), undefined<cluster_id>());
    parent(sim.get_id(sum_a), undefined<cluster_id>());
    parent(sim.get_id(sum_b), undefined<cluster_id>());
    parent(sim.get_id(sum_c), undefined<cluster_id>());
    parent(sim.get_id(sum_d), undefined<cluster_id>());
    parent(sim.get_id(product), undefined<cluster_id>());
    parent(sim.get_id(integrator_a), undefined<cluster_id>());
    parent(sim.get_id(integrator_b), undefined<cluster_id>());
    parent(sim.get_id(quantifier_a), undefined<cluster_id>());
    parent(sim.get_id(quantifier_b), undefined<cluster_id>());
    parent(sim.get_id(cross), undefined<cluster_id>());
    parent(sim.get_id(cross2), undefined<cluster_id>());
1244

1245
1246
    return status::success;
}
1247

1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
static int
show_connection(editor& ed, const model& mdl, int port, int connection_id)
{
    ed.sim.dispatch(
      mdl,
      [&ed, &mdl, port, &connection_id]<typename Dynamics>(
        Dynamics& dyn) -> void {
          if constexpr (is_detected_v<has_output_port_t, Dynamics>) {
              int out = make_output_node_id(ed.sim.get_id(dyn), port);

              for (const auto& c : dyn.y[port].connections) {
                  if (auto* mdl_dst = ed.sim.models.try_to_get(c.model);
                      mdl_dst) {
                      int in = make_input_node_id(c.model, c.port_index);
Gauthier Quesnel's avatar
Gauthier Quesnel committed
1262
                      ImNodes::Link(connection_id++, out, in);
1263
1264
1265
1266
                  }
              }
          }
      });
1267

1268
    return connection_id;