node-editor.hpp 12.9 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 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)

#ifndef ORG_VLEPROJECT_IRRITATOR_APP_NODE_EDITOR_2020
#define ORG_VLEPROJECT_IRRITATOR_APP_NODE_EDITOR_2020

#include <irritator/core.hpp>

#include <filesystem>
#include <fstream>
#include <thread>
#include <variant>
#include <vector>

#include "imnodes.hpp"
#include <imgui.h>

namespace irt {

Gauthier Quesnel's avatar
Gauthier Quesnel committed
21
22
23
24
25
26
27
28
29
30
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
inline const char*
status_string(const status s) noexcept
{
    static const char* str[] = {
        "success",
        "unknown_dynamics",
        "block_allocator_bad_capacity",
        "block_allocator_not_enough_memory",
        "head_allocator_bad_capacity",
        "head_allocator_not_enough_memory",
        "simulation_not_enough_model",
        "simulation_not_enough_memory_message_list_allocator",
        "simulation_not_enough_memory_input_port_list_allocator",
        "simulation_not_enough_memory_output_port_list_allocator",
        "data_array_init_capacity_error",
        "data_array_not_enough_memory",
        "data_array_archive_init_capacity_error",
        "data_array_archive_not_enough_memory",
        "array_init_capacity_zero",
        "array_init_capacity_too_big",
        "array_init_not_enough_memory",
        "vector_init_capacity_zero",
        "vector_init_capacity_too_big",
        "vector_init_not_enough_memory",
        "dynamics_unknown_id",
        "dynamics_unknown_port_id",
        "dynamics_not_enough_memory",
        "model_connect_output_port_unknown",
        "model_connect_input_port_unknown",
        "model_connect_already_exist",
        "model_connect_bad_dynamics",
        "model_integrator_dq_error",
        "model_integrator_X_error",
        "model_integrator_internal_error",
        "model_integrator_output_error",
        "model_integrator_running_without_x_dot",
        "model_integrator_ta_with_bad_x_dot",
        "model_quantifier_bad_quantum_parameter",
        "model_quantifier_bad_archive_length_parameter",
        "model_quantifier_shifting_value_neg",
        "model_quantifier_shifting_value_less_1",
        "model_time_func_bad_init_message",
63
64
        "model_flow_bad_samplerate",
        "model_flow_bad_data",
Gauthier Quesnel's avatar
Gauthier Quesnel committed
65
        "gui_not_enough_memory",
66
        "io_not_enough_memory",
Gauthier Quesnel's avatar
Gauthier Quesnel committed
67
68
69
70
71
72
73
74
75
        "io_file_format_error",
        "io_file_format_model_error",
        "io_file_format_model_number_error",
        "io_file_format_model_unknown",
        "io_file_format_dynamics_unknown",
        "io_file_format_dynamics_limit_reach",
        "io_file_format_dynamics_init_error"
    };

76
77
    static_assert(std::size(str) == status_size());

Gauthier Quesnel's avatar
Gauthier Quesnel committed
78
79
80
    return str[static_cast<int>(s)];
}

81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
template<class C>
constexpr int
length(const C& c) noexcept
{
    return static_cast<int>(c.size());
}

template<class T, size_t N>
constexpr int
length(const T (&array)[N]) noexcept
{
    (void)array;

    return static_cast<int>(N);
}

97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
template<typename Identifier>
constexpr Identifier
undefined() noexcept
{
    static_assert(
      std::is_enum<Identifier>::value,
      "Identifier must be a enumeration: enum class id : unsigned {};");

    return static_cast<Identifier>(0);
}

enum class editor_id : u64;
enum class cluster_id : u64;

using child_id = std::variant<model_id, cluster_id>;

113
enum class editor_status
114
{
115
116
117
118
119
    editing,
    initializing,
    running_debug,
    running_thread,
    running_thread_need_join
120
121
122
123
124
125
};

static inline constexpr int not_found = -1;

struct top_cluster
{
126
    std::vector<std::pair<child_id, int>> children;
127
128
129
130
    int next_node_id = 0;

    static inline constexpr int not_found = -1;

131
132
133
134
135
136
137
138
139
140
141
142
    status init(size_t models) noexcept
    {
        try {
            children.reserve(models);
        } catch (const std::bad_alloc&) {
            std::vector<std::pair<child_id, int>>().swap(children);
            irt_bad_return(status::gui_not_enough_memory);
        }

        return status::success;
    }

143
144
    int get_index(const child_id id) const noexcept
    {
145
146
147
        for (int i = 0, e = length(children); i != e; ++i)
            if (children[i].first == id)
                return i;
148

149
        return not_found;
150
151
152
153
    }

    int get_index(const int node) const noexcept
    {
154
155
156
        for (int i = 0, e = length(children); i != e; ++i)
            if (children[i].second == node)
                return i;
157

158
        return not_found;
159
160
161
162
163
164
165
166
167
168
169
170
171
    }

    void clear() noexcept
    {
        children.clear();
    }

    void pop(const int index) noexcept
    {
        std::swap(children[index], children.back());
        children.pop_back();
    }

172
    int emplace_back(const child_id id)
173
    {
174
175
176
177
178
        int ret = next_node_id++;

        children.emplace_back(id, ret);

        return ret;
179
180
181
182
183
184
185
186
187
188
189
190
191
192
    }
};

struct cluster
{
    cluster() = default;

    small_string<16> name;
    std::vector<child_id> children;
    std::vector<input_port_id> input_ports;
    std::vector<output_port_id> output_ports;

    int get(const child_id id) const noexcept
    {
193
194
195
        auto it = std::find(std::begin(children), std::end(children), id);
        if (it == std::end(children))
            return not_found;
196

197
        return static_cast<int>(std::distance(std::begin(children), it));
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
    }
};

struct window_logger
{
    ImGuiTextBuffer buffer;
    ImGuiTextFilter filter;
    ImVector<int> line_offsets;

    bool auto_scroll = true;
    bool scroll_to_bottom = false;
    window_logger() = default;
    void clear() noexcept;

    void log(const int level, const char* fmt, ...) IM_FMTARGS(3);
    void log(const int level, const char* fmt, va_list args) IM_FMTLIST(3);
    void show(bool* is_show);
};

217
static inline window_logger log_w;
218

219
struct plot_output
220
{
221
    plot_output() = default;
222

223
    plot_output(std::string_view name_)
224
225
226
      : name(name_)
    {}

227
228
229
230
231
232
233
234
235
236
    void clear()
    {
        name.clear();
        xs.clear();
        ys.clear();
        tl = 0.0;
        min = -1.f;
        max = +1.f;
    }

Gauthier Quesnel's avatar
Gauthier Quesnel committed
237
238
    std::vector<float> xs;
    std::vector<float> ys;
239
    small_string<24u> name;
240
241
242
    double tl = 0.0;
    float min = -1.f;
    float max = +1.f;
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
};

struct file_output
{
    file_output() = default;

    file_output(std::string_view name_)
      : name(name_)
    {}

    void clear()
    {
        ofs.close();
        name.clear();
        tl = 0.0;
    }

    std::ofstream ofs;
    small_string<24u> name;
    double tl = 0.0;
};

void
observation_plot_output_initialize(const irt::observer& obs,
                                   const irt::time t) noexcept;
void
observation_file_output_initialize(const irt::observer& obs,
                                   const irt::time t) noexcept;
void
observation_plot_output_observe(const irt::observer& obs,
                                const irt::time t,
                                const irt::message& msg) noexcept;

void
observation_file_output_observe(const irt::observer& obs,
                                const irt::time t,
                                const irt::message& msg) noexcept;
void
observation_plot_output_free(const irt::observer& /*obs*/,
                             const irt::time /*t*/) noexcept;

void
observation_file_output_free(const irt::observer& obs,
                             const irt::time /*t*/) noexcept;

enum class plot_output_id : u64;
enum class file_output_id : u64;

struct observation_output
{
    constexpr observation_output() = default;

    constexpr void clear() noexcept
    {
        plot_id = undefined<plot_output_id>();
        file_id = undefined<file_output_id>();
    }

    plot_output_id plot_id = undefined<plot_output_id>();
    file_output_id file_id = undefined<file_output_id>();
303
304
305
306
307
308
309
310
311
312
313
};

struct editor
{
    small_string<16> name;
    std::filesystem::path path;
    imnodes::EditorContext* context = nullptr;
    bool initialized = false;
    bool show = true;

    simulation sim;
Gauthier Quesnel's avatar
Gauthier Quesnel committed
314

315
316
317
    double simulation_begin = 0.0;
    double simulation_end = 10.0;
    double simulation_current = 10.0;
318
    double simulation_next_time = 0.0;
Gauthier Quesnel's avatar
Gauthier Quesnel committed
319
    long simulation_bag_id = 0;
320

Gauthier Quesnel's avatar
Gauthier Quesnel committed
321
322
    double simulation_during_date;
    int simulation_during_bag;
323

324
    std::thread simulation_thread;
325
326
    editor_status st = editor_status::editing;
    status sim_st = status::success;
327

328
329
330
    bool simulation_show_value = false;
    bool stop = false;

331
332
333
    data_array<plot_output, plot_output_id> plot_outs;
    data_array<file_output, file_output_id> file_outs;

Gauthier Quesnel's avatar
Gauthier Quesnel committed
334
    std::vector<observation_output> observation_outputs;
335
336
337
    std::filesystem::path observation_directory;

    data_array<cluster, cluster_id> clusters;
Gauthier Quesnel's avatar
Gauthier Quesnel committed
338
339
    std::vector<cluster_id> clusters_mapper; /* group per cluster_id */
    std::vector<cluster_id> models_mapper;   /* group per model_id */
340

Gauthier Quesnel's avatar
Gauthier Quesnel committed
341
    std::vector<bool> models_make_transition;
Gauthier Quesnel's avatar
Gauthier Quesnel committed
342

343
344
345
    ImVector<ImVec2> positions;
    ImVector<ImVec2> displacements;

346
    bool use_real_time;
Gauthier Quesnel's avatar
Gauthier Quesnel committed
347
    bool starting = true;
348
349
    double synchronize_timestep;

350
351
    top_cluster top;

352
353
    std::string tooltip;

354
355
356
    bool show_load_file_dialog = false;
    bool show_save_file_dialog = false;
    bool show_select_directory_dialog = false;
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
    bool show_settings = false;

    struct settings_manager
    {
        int kernel_model_cache = 1024;
        int kernel_message_cache = 32768;
        int gui_node_cache = 1024;
        ImVec4 gui_model_color{ .27f, .27f, .54f, 1.f };
        ImVec4 gui_model_transition_color{ .27f, .54f, .54f, 1.f };
        ImVec4 gui_cluster_color{ .27f, .54f, .27f, 1.f };

        ImU32 gui_hovered_model_color;
        ImU32 gui_selected_model_color;
        ImU32 gui_hovered_model_transition_color;
        ImU32 gui_selected_model_transition_color;
        ImU32 gui_hovered_cluster_color;
        ImU32 gui_selected_cluster_color;

        int automatic_layout_iteration_limit = 200;
        float automatic_layout_x_distance = 350.f;
        float automatic_layout_y_distance = 350.f;
        float grid_layout_x_distance = 250.f;
        float grid_layout_y_distance = 250.f;

        bool show_dynamics_inputs_in_editor = false;

        void compute_colors() noexcept;
        void show(bool* is_open);

    } settings;
387

388
389
390
391
392
393
394
    status initialize(u32 id) noexcept;
    void clear() noexcept;

    void group(const ImVector<int>& nodes) noexcept;
    void ungroup(const int node) noexcept;
    void free_group(cluster& group) noexcept;
    void free_children(const ImVector<int>& nodes) noexcept;
395
396
    status copy(const ImVector<int>& nodes) noexcept;

397
398
    void compute_grid_layout() noexcept;
    void compute_automatic_layout() noexcept;
399
400
401
402
403

    bool is_in_hierarchy(const cluster& group,
                         const cluster_id group_to_search) const noexcept;
    cluster_id ancestor(const child_id child) const noexcept;
    int get_top_group_ref(const child_id child) const noexcept;
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424

    cluster_id parent(cluster_id child) const noexcept
    {
        return clusters_mapper[get_index(child)];
    }

    cluster_id parent(model_id child) const noexcept
    {
        return models_mapper[get_index(child)];
    }

    void parent(const cluster_id child, const cluster_id parent) noexcept
    {
        clusters_mapper[get_index(child)] = parent;
    }

    void parent(const model_id child, const cluster_id parent) noexcept
    {
        models_mapper[get_index(child)] = parent;
    }

425
    static int get_in(input_port_id id) noexcept
426
427
428
429
430
431
432
433
434
435
436
    {
        return static_cast<int>(get_index(id));
    }

    input_port_id get_in(int index) const noexcept
    {
        auto* port = sim.input_ports.try_to_get(static_cast<u32>(index));

        return port ? sim.input_ports.get_id(port) : undefined<input_port_id>();
    }

437
    static int get_out(output_port_id id) noexcept
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
    {
        constexpr u32 is_output = 1 << 31;
        u32 index = get_index(id);
        index |= is_output;

        return static_cast<int>(index);
    }

    output_port_id get_out(int index) const noexcept
    {
        constexpr u32 mask = ~(1 << 31); /* remove the first bit */
        index &= mask;

        auto* port = sim.output_ports.try_to_get(static_cast<u32>(index));

        return port ? sim.output_ports.get_id(port)
                    : undefined<output_port_id>();
    }

    status add_lotka_volterra() noexcept;
    status add_izhikevitch() noexcept;

    void show_connections() noexcept;
    void show_model_dynamics(model& mdl) noexcept;
    void show_model_cluster(cluster& mdl) noexcept;
    void show_top() noexcept;

    bool show_editor() noexcept;
};

468
void
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
show_simulation_box(editor& ed, bool* show_simulation);

struct application
{
    data_array<editor, editor_id> editors;

    struct settings_manager
    {
        settings_manager() noexcept;

        std::filesystem::path home_dir;
        std::filesystem::path executable_dir;
        std::vector<std::string> libraries_dir;

        void show(bool *is_open);
    } settings;

    bool show_log = true;
    bool show_simulation = true;
    bool show_demo = false;
    bool show_plot = true;
    bool show_settings = false;

    editor* alloc_editor();
    void free_editor(editor& ed);
};

static inline application app;

editor*
make_combo_editor_name(application& app, editor_id& current) noexcept;
500

501
502
} // namespace irt

503
#endif