diff --git a/libstage/stage.hh b/libstage/stage.hh index 92a1d5f9b..36db49239 100644 --- a/libstage/stage.hh +++ b/libstage/stage.hh @@ -47,6 +47,7 @@ // C++ libs #include +#include #include #include #include @@ -852,6 +853,11 @@ protected: std::list ray_list; ///< List of rays traced for debug visualization usec_t sim_time; ///< the current sim time in this world in microseconds std::map superregions; + std::chrono::time_point next_run_time; + usec_t real_time_between_runs; + /** Stage attempts to run this many times faster than real + time. If -1, Stage runs as fast as possible. */ + double speedup; uint64_t updates; ///< the number of simulated time steps executed so far Worldfile *wf; ///< If set, points to the worldfile used to create this world @@ -859,6 +865,7 @@ protected: void CallUpdateCallbacks(); ///< Call all calbacks in cb_list, removing any that return true; public: + std::chrono::time_point NextRunTime() { return next_run_time; } uint64_t UpdateCount() { return updates; } bool paused; ///< if true, the simulation is stopped @@ -1032,6 +1039,7 @@ updates */ public: /** returns true when time to quit, false otherwise */ static bool UpdateAll(); + static bool UpdateAll(std::chrono::time_point &next_run); /** run all worlds. * If only non-gui worlds were created, UpdateAll() is @@ -1430,10 +1438,6 @@ private: FileManager *fileMan; ///< Used to load and save worldfiles std::vector interval_log; - /** Stage attempts to run this many times faster than real -time. If -1, Stage runs as fast as possible. */ - double speedup; - bool confirm_on_quit; ///< if true, show save dialog on (GUI) exit (default) Fl_Menu_Bar *mbar; diff --git a/libstage/world.cc b/libstage/world.cc index 28a631a2f..a256874c1 100644 --- a/libstage/world.cc +++ b/libstage/world.cc @@ -88,6 +88,8 @@ using std::abs; #include +#include +#include #include #include #include // for dirname(3) @@ -138,7 +140,9 @@ World::World(const std::string &, // protected cb_list(), extent(), graphics(false), option_table(), powerpack_list(), quit_time(0), - ray_list(), sim_time(0), superregions(), updates(0), wf(NULL), paused(false), + ray_list(), sim_time(0), superregions(), next_run_time(std::chrono::time_point::min()), + real_time_between_runs(0), speedup(-1.0), // default as fast as possible + updates(0), wf(NULL), paused(false), event_queues(1), // use 1 thread by default pending_update_callbacks(), active_energy(), active_velocity(), sim_interval(1e5), // 100 msec has proved a good default @@ -208,8 +212,10 @@ void World::Run() Fl::wait(); } } else { - while (!UpdateAll()) - ; + std::chrono::time_point next_run; + while (!UpdateAll(next_run)) { + std::this_thread::sleep_until(next_run); + } } } @@ -225,6 +231,21 @@ bool World::UpdateAll() return quit; } +bool World::UpdateAll(std::chrono::time_point &next_run) +{ + bool quit(true); + + next_run = std::chrono::time_point::max(); + FOR_EACH (world_it, World::world_set) { + if ((*world_it)->Update() == false) { + quit = false; + next_run = std::min(next_run, (*world_it)->NextRunTime()); + } + } + + return quit; +} + void *World::update_thread_entry(std::pair *thread_info) { World *world(thread_info->first); @@ -412,6 +433,9 @@ void World::LoadWorldPostHook() // read msec instead of usec: easier for user this->sim_interval = 1e3 * wf->ReadFloat(0, "interval_sim", this->sim_interval / 1e3); + this->speedup = wf->ReadFloat(0, "speedup", this->speedup); + this->real_time_between_runs = this->speedup > 0 ? this->sim_interval / this->speedup : 0; + this->worker_threads = wf->ReadInt(0, "threads", this->worker_threads); if (this->worker_threads < 1) { PRINT_WARN("threads set to <1. Forcing to 1"); @@ -612,6 +636,14 @@ bool World::Update() if (PastQuitTime() || World::quit_all || this->quit) return true; + if (real_time_between_runs > 0) { + if (std::chrono::steady_clock::now() < next_run_time) + return false; + next_run_time += std::chrono::microseconds(real_time_between_runs); + if (next_run_time < std::chrono::steady_clock::now()) + next_run_time = std::chrono::steady_clock::now() + std::chrono::microseconds(real_time_between_runs); + } + if (show_clock && ((this->updates % show_clock_interval) == 0)) { printf("\r[Stage: %s]", ClockString().c_str()); fflush(stdout); diff --git a/libstage/worldgui.cc b/libstage/worldgui.cc index e9733fdd5..57bf8d1fa 100644 --- a/libstage/worldgui.cc +++ b/libstage/worldgui.cc @@ -194,11 +194,13 @@ static const char *MoreHelpText = WorldGui::WorldGui(int width, int height, const char *caption) : Fl_Window(width, height, NULL), canvas(new Canvas(this, 0, 30, width, height - 30)), - drawOptions(), fileMan(new FileManager()), interval_log(), speedup(1.0), // real time + drawOptions(), fileMan(new FileManager()), interval_log(), confirm_on_quit(true), mbar(new Fl_Menu_Bar(0, 0, width, 30)), oDlg(NULL), pause_time(false), real_time_interval(sim_interval), real_time_now(RealTimeNow()), real_time_recorded(real_time_now), timing_interval(20) { + speedup = 1.0; // default in GUI mode is realtime + Fl::lock(); // start FLTK's thread safe behaviour Fl::scheme(""); @@ -301,7 +303,6 @@ void WorldGui::LoadWorldGuiPostHook(usec_t load_start_time) { // worldgui exclusive properties live in the top-level section const int world_section = 0; - speedup = wf->ReadFloat(world_section, "speedup", speedup); paused = wf->ReadInt(world_section, "paused", paused); confirm_on_quit = wf->ReadInt(world_section, "confirm_on_quit", confirm_on_quit);