gui.cpp 7.82 KB
Newer Older
1
#include "creativity/Creativity.hpp"
2
#include "creativity/cmdargs/GUI.hpp"
3
#include "creativity/state/Storage.hpp"
Jason Rhinelander's avatar
Jason Rhinelander committed
4
#include "creativity/state/FileStorage.hpp"
5
#include "creativity/gui/GUI.hpp"
6
#include <eris/Simulation.hpp>
7
#include <eris/random/rng.hpp>
8
#include <Eigen/Core>
9 10
#include <functional>
#include <iostream>
11
#include <iomanip>
12 13 14 15 16 17 18 19 20 21
#include <cstddef>
#include <algorithm>
#include <chrono>
#include <ratio>
#include <stdexcept>
#include <string>
#include <utility>
#include <glibmm/error.h>
#include <glibmm/ustring.h>

22 23

using namespace creativity;
24
using namespace creativity::gui;
25
using namespace creativity::state;
26
using namespace eris;
27
using namespace Eigen;
28

29
int main(int argc, char *argv[1]) {
30
    Eigen::initParallel();
31
    Creativity creativity;
32

33
    // Handle any command line arguments.  These set things in creativity.set(), which form the
34
    // defaults for the GUI, and have a few extra leftovers that the GUI code handles itself.
35 36
    cmdargs::GUI args(creativity.set());
    args.parse(argc, argv);
37

38 39
    // Start out with an in-memory storage "file"--it is much more efficient than actual in-memory
    // storage of all of the states
Jason Rhinelander's avatar
Jason Rhinelander committed
40
    creativity.write<FileStorage>();
41

42 43 44
    std::cerr << std::setprecision(16);
    std::cout << std::setprecision(16);

Jason Rhinelander's avatar
Jason Rhinelander committed
45
    bool setup = false, stopped = true, step = false, quit = false;
46
    eris_time_t run_end = 0;
47
    std::chrono::milliseconds sync_speed{50};
48
    bool save_to_file = false, load_from_file = false;
49
    unsigned int max_threads = args.threads;
50 51

    // Set up handlers for user actions in the GUI
52
    auto on_configure = [&](GUI::Parameter p) { // Parameter
53
        switch (p.param) {
54
            case GUI::ParamType::seed:
55
                if (setup or load_from_file) throw std::runtime_error("Cannot change seed after initial setup");
56
                eris::random::seed(p.ul);
57
                break;
58 59
            case GUI::ParamType::load:
                if (setup) throw std::runtime_error("Cannot load after initial setup");
60
                if (save_to_file) throw std::runtime_error("Error: cannot load and save at the same time");
61
                creativity.read<FileStorage>(*reinterpret_cast<std::string*>(p.ptr), FileStorage::Mode::READONLY, args.memory, args.tmpdir);
62
                if (creativity.storage().first->size() == 0)
63
                    throw std::runtime_error("Unable to load file: file has no states");
64
                if (creativity.parameters.dimensions < 1)
65
                    throw std::runtime_error("Unable to load file: invalid dimensions < 1");
66
                if (creativity.parameters.boundary <= 0)
67 68 69 70
                    throw std::runtime_error("Unable to load file: file has invalid non-positive boundary value");
                load_from_file = true;
                break;
            case GUI::ParamType::save_as:
71
                creativity.write<FileStorage>(*reinterpret_cast<std::string*>(p.ptr), FileStorage::Mode::OVERWRITE, args.memory, args.tmpdir);
72 73
                save_to_file = true;
                break;
74
            case GUI::ParamType::threads:
75 76 77
                // This is the only setting that *can* be changed after the initial setup.  This
                // will throw if currently running, but that's okay: the GUI isn't allowed to send
                // it if the simulation is currently running.
78
                if (creativity.sim) creativity.sim->maxThreads(p.ul);
79
                else max_threads = p.ul;
80 81
                break;
        }
82
    };
83

84 85
    auto on_change_periods = [&](eris_time_t end) { run_end = end; };

86 87 88 89 90
    auto on_initialize = [&]() {
        if (load_from_file)
            throw std::logic_error("Cannot initialize a new simulation after loading one from a file");
        if (setup)
            throw std::logic_error("Cannot initialize: initialization already done!");
91
        if (creativity.parameters.dimensions < 1) throw std::domain_error(u8"Invalid simulation file: dimensions < 1");
92
        // setup() will check the other parameters for validity
93 94
        creativity.setup();
        creativity.sim->maxThreads(max_threads);
95 96 97
        setup = true;
    };

98
    auto on_run = [&]() {
99
        if (not setup)
100 101 102
            throw std::logic_error("Event error: RUN before successful SETUP");
        if (load_from_file)
            throw std::logic_error("Error: simulation state is readonly");
103 104 105 106 107
        stopped = false;
    };
    auto on_stop = [&]() { stopped = true; };
    auto on_step = [&]() { step = true; };
    auto on_quit = [&]() { quit = true; };
108

109
    GUI gui(creativity, on_configure, on_initialize, on_change_periods, on_run, on_stop, on_step, on_quit);
110 111

    try {
112
        gui.start(args);
113 114 115 116 117 118
    }
    catch (Glib::Error &e) {
        std::cerr << "Unable to start gui: " << e.what() << "\n";
        throw;
    }

119
    while (not setup and not load_from_file) {
120 121 122
        gui.waitEvents();
        if (quit) return 0;
    }
123 124 125
    if (load_from_file) {
        // We're in read-only mode, which means we do nothing but wait for the GUI to quit.
        gui.initialized();
126
        size_t num_states = creativity.storage().first->size() - 1; // -1 because we don't count the initial, t=0 setup state
127
        gui.progress(num_states, num_states, 0);
128 129 130 131 132 133 134
        gui.newStates(0);
        while (not quit) {
            gui.waitEvents();
        }
        return 0;
    }

135
    auto sim = creativity.sim;
136

137
    // Copy the initial state into the storage object
138
    creativity.storage().first->emplace_back(sim);
139

140
    // Tell the GUI we're done with initialization so that it can disable the various setup elements
Jason Rhinelander's avatar
Jason Rhinelander committed
141 142
    gui.initialized();

143
    gui.progress(0, run_end, 0);
144 145 146 147 148 149
    auto last_progress = std::chrono::high_resolution_clock::now();
    auto last_progress_t = sim->t();

    constexpr auto progress_freq = std::chrono::milliseconds{50};

    std::chrono::time_point<std::chrono::high_resolution_clock> start, end,
150
        next_sync{std::chrono::high_resolution_clock::now() + sync_speed};
151 152 153 154 155 156 157 158

    std::chrono::time_point<std::chrono::high_resolution_clock> next_progress{
        std::chrono::high_resolution_clock::now() + progress_freq};

    while (not quit) {
        if (sim->t() < run_end) {
            // Tell the GUI we've started running.
            gui.running();
159
        }
160 161 162
        while (not quit and (step or (not stopped and sim->t() < run_end))) {
            start = std::chrono::high_resolution_clock::now();

163
            creativity.run();
164 165 166 167 168

            if (step) step = false;

            bool finished = stopped or sim->t() >= run_end;
            end = std::chrono::high_resolution_clock::now();
169

170 171 172
            if (finished or end >= next_progress) {
                auto now_t = sim->t();
                double speed = (double) (now_t - last_progress_t) / std::chrono::duration<double>{end - last_progress}.count();
173
                gui.progress(now_t, std::max(now_t, run_end), speed);
174 175 176 177 178 179
                last_progress = end;
                last_progress_t = now_t;
                gui.checkEvents();
                next_progress = end + progress_freq;
            }

180 181 182 183 184
            if (quit) break;

            // Make sure stopped hasn't changed:
            if (not finished and stopped) finished = true;

185
            // Only tell the GUI about new states at most once every 50ms, or if we're done
186
            if (finished or end >= next_sync) {
187
                gui.newStates();
188
                next_sync = end + sync_speed;
189
            }
190
        }
191 192 193 194

        // If the GUI told us to quit, just quit.
        if (quit) break;

195
        // Tell the GUI we stopped but, if the argument is true, that there are more periods to go
Jason Rhinelander's avatar
Jason Rhinelander committed
196
        gui.stopped(sim->t() < run_end);
197 198 199

        // Wait for the GUI to tell us to do something else
        gui.waitEvents();
200 201 202 203

        // Update this so that the speed is right (otherwise however long waitEvents() waits for the
        // user waits would be included in the calculation).
        last_progress = std::chrono::high_resolution_clock::now();
204
    }
205

206
    creativity.storage().first->flush();
207
}