CellModules
world.hpp
Go to the documentation of this file.
1#ifndef MECACELL_WORLD_H
2#define MECACELL_WORLD_H
3#include <array>
4#include <functional>
5#include <unordered_set>
6#include <utility>
7#include <vector>
8#include "integrators.hpp"
12#include <chrono>
13#include <string>
14#include <map>
15#include <unordered_map>
16
17namespace MecaCell {
18
29template <typename Cell, typename Integrator = Euler> class World {
30 private:
31 size_t nbThreads = 0;
32 using Clock = std::chrono::high_resolution_clock;
33 std::unordered_map<std::string, std::map<size_t, double>> benchResults;
34
35 public:
36 using cell_t = Cell;
37 using integrator_t = Integrator;
38 using hook_s = void(World *);
40
41 ThreadPool threadpool; // threadpool that can be used by plugins (and is also used to
42 // parallelize updateBehavior)
43
51 // -------------------------------------
52 // The following code detects if any embedded plugin type is specified and defaults
53 // to the instantiation of a useles byte if not.
54 struct dumb {};
55 template <class, class = MecaCell::void_t<>>
56 struct embedded_plugin_type { // declaration
57 using type = dumb; // defaults to empty type if no embedded plugin is defined
58 };
59 template <class T> // specialization
61 using type = typename T::embedded_plugin_t; // embedded plugin detected
62 };
64 typename embedded_plugin_type<cell_t>::type; // either dumb empty struct or the
65 // plugin type
66 // defined by Cell (if existing)
67 protected:
68 size_t frame = 0;
69 size_t nbAddedCells = 0;
70 size_t updtBhvPeriod = 1;
72 bool shuffleCells = true;
73
78 for (auto i = cells.begin(); i != cells.end();) {
79 if ((*i)->isDead()) {
80 auto tmp = *i;
81 i = cells.erase(i);
82 delete tmp;
83 } else {
84 ++i;
85 }
86 }
87 }
88
89 public:
90 double dt = 1.0 / 100.0;
91 double dtMn = dt/60.;
92 double dtH = dt/3600.;
93 bool benchmark = false;
96
97 cellPlugin_t cellPlugin; // instance of the embedded cell plugin type
98 // (cellPlugin_t default to a dumb char if not specified)
99
101
102 /* HOOKS
103 */
104 DECLARE_HOOK(onAddCell, beginUpdate, preBehaviorUpdate, preDeleteDeadCellsUpdate,
105 postBehaviorUpdate, endUpdate, allForcesAppliedToCells, destructor)
106
107 /* CELLS */
108 std::vector<Cell *> cells;
109
118 World(size_t nThreads = 0) : nbThreads(nThreads), threadpool(nThreads) {
120 }
121
129
137 size_t getNbThreads() { return nbThreads; }
138
139 void setNbThreads(size_t n) {
140 nbThreads = n;
142 }
143
144 /**********************************************
145 * HOOKS & PLUGINS *
146 *********************************************/
147
154 //void registerHook(const Hooks &h, hook_t f) { hooks[eToUI(h)].push_back(f); }
155 void registerHook(const Hooks &h,
156 const std::string &pluginName,
157 const std::string &hookName,
158 std::function<hook_s> fn) {
159 hooks[eToUI(h)].push_back({ pluginName, hookName, std::move(fn) });
160 }
161
162 void runHooks(const Hooks &h) {
163 if(!benchmark){
164 for (auto &hw : hooks[eToUI(h)]) hw.f(this);
165 } else {
166 for (auto &hw : hooks[eToUI(h)]) {
167 auto t0 = Clock::now();
168 hw.f(this);
169 auto dt = std::chrono::duration<double, std::micro>(Clock::now() - t0).count();
170 benchResults[hw.pluginName + "@" + hw.hookName][frame] += dt;
171 }
172 }
173 }
195 template <typename P, typename... Rest>
196 void registerPlugins(P &&p, Rest &&... otherPlugins) {
197 // registerPlugin returns the onRegister hook from the plugin
198 registerPlugin(std::forward<P>(p))(this);
199 registerPlugins(std::forward<Rest>(otherPlugins)...);
200 }
202
207 void clearHooks() {
208 for (auto &h : hooks) h.clear();
209 }
210
211 /**********************************************
212 * GET & SET *
213 *********************************************/
214
220 size_t getNbUpdates() const { return frame; }
221
230 template <typename... Args> Cell *createCell(Args &&... args) {
231 Cell *c = new Cell(std::forward<Args>(args)...);
232 addCell(c);
233 return c;
234 }
235
246 void addCell(Cell *c) {
247 if (c) {
248 c->getBody().setCellPlugin(&cellPlugin);
249 c->setWorld(this);
250 newCells.push_back(c);
251 c->id = nbAddedCells++;
252 }
253 }
254
255 void refreshId(Cell *c) {
256 if (c) {
257 c->id = nbAddedCells++;
258 }
259 }
260
265 void addNewCells() {
266 if (newCells.size()) {
267 runHooks(Hooks::onAddCell);
268 cells.insert(cells.end(), newCells.begin(), newCells.end());
269 newCells.clear();
270 }
271 }
272
278 runHooks(Hooks::allForcesAppliedToCells);
279 }
280
289 void setDt(double d) { dt = d; dtMn = d/60.; dtH = d/3600.; }
290 void setDtInS(double d) { dt = d; dtMn = d/60.; dtH = d/3600.; }
291 void setDtInMn(double d) { dt = d*60.; dtMn = d; dtH = d/60.; }
292 void setDtInH(double d) { dt = d*3600.; dtMn = d*60.; dtH = d; }
293 double getDt() const{ return dt; }
294 double getDtInS() const{ return dt; }
295 double getDtInMn() const{ return dtMn; }
296 double getDtInH() const{ return dtH; }
297
298 void setShuffleCells(bool s) { shuffleCells = s; }
299
308
309 size_t getUpdateBehaviorPeriod() const { return updtBhvPeriod; }
310
311
318 std::unordered_set<ordered_pair<cell_t *>> res;
319 for (auto &c : cells) {
320 for (auto &connected : c->getConnectedCells())
321 res.insert(make_ordered_cell_pair(c, connected));
322 }
324 for (auto &r : res) vecRes.push_back(std::make_pair(r.first, r.second));
325 return vecRes;
326 }
327
333 if (!benchmark) {
334 if(shuffleCells) std::shuffle(std::begin(cells), std::end(cells), MecaCell::Config::globalRand());
335 const size_t MIN_CHUNK_SIZE = 2;
336 const double AVG_TASKS_PER_THREAD = 3.0;
338 threadpool.autoChunks(cells, MIN_CHUNK_SIZE, AVG_TASKS_PER_THREAD,
339 [](auto *c) { c->updateBehavior(); });
341 } else
342 for (auto &c : cells) c->updateBehavior();
343 } else {
344 auto t0 = Clock::now();
345 if(shuffleCells) std::shuffle(std::begin(cells), std::end(cells), MecaCell::Config::globalRand());
346 const size_t MIN_CHUNK_SIZE = 2;
347 const double AVG_TASKS_PER_THREAD = 3.0;
349 threadpool.autoChunks(cells, MIN_CHUNK_SIZE, AVG_TASKS_PER_THREAD,
350 [](auto *c) { c->updateBehavior(); });
352 } else
353 for (auto &c : cells) c->updateBehavior();
354 auto dt = std::chrono::duration<double, std::micro>(Clock::now() - t0).count();
355 benchResults["World@callUpdateBehavior"][frame] = dt;
356 }
357 }
358
371 void update() {
372 runHooks(Hooks::beginUpdate);
373 runHooks(Hooks::preBehaviorUpdate);
375 addNewCells();
376 runHooks(Hooks::preDeleteDeadCellsUpdate);
378 runHooks(Hooks::postBehaviorUpdate);
379 runHooks(Hooks::endUpdate);
380 ++frame;
381 }
382
383 std::string getBenchmarksJSON() const {
384 auto demangle = [&](const std::string &mangled) {
385 int status = 0;
386 char* dem = abi::__cxa_demangle(mangled.c_str(), nullptr, nullptr, &status);
387 std::string s = (status == 0 && dem) ? dem : mangled;
388 std::free(dem);
389 return s;
390 };
391 std::map<std::string,std::map<std::string, std::map<size_t, double>>> nested;
392
393 for (auto const& [key, frameMap] : benchResults) {
394 auto pos = key.rfind('@');
395 std::string mangledP = (pos != std::string::npos ? key.substr(0, pos) : key);
396 std::string hookName = (pos != std::string::npos ? key.substr(pos + 1) : "");
397 std::string pluginName = demangle(mangledP);
398 auto lt = pluginName.find('<');
399 pluginName = lt!=std::string::npos ? pluginName.substr(0,lt) : pluginName;
400 auto ns = pluginName.rfind("::");
401 pluginName = ns!=std::string::npos ? pluginName.substr(ns+2) : pluginName;
402 nested[pluginName][hookName] = frameMap;
403 }
404
405 std::ostringstream oss;
406 oss << "{";
407 bool firstPlugin = true;
408 for (auto const& [plugin, hooks] : nested) {
409 if (!firstPlugin) oss << ",";
410 firstPlugin = false;
411 oss << "\n \"" << plugin << "\": {";
412
413 bool firstHook = true;
414 for (auto const& [hook, frames] : hooks) {
415 if (!firstHook) oss << ",";
416 firstHook = false;
417 oss << "\n \"" << hook << "\": {";
418
419 bool firstFrame = true;
420 for (auto const& [frm, us] : frames) {
421 if (!firstFrame) oss << ",";
422 firstFrame = false;
423 oss << "\n \"" << frm << "\": " << us;
424 }
425 oss << "\n }"; // end hook
426 }
427 oss << "\n }"; // end plugin
428 }
429 oss << "\n}";
430 return oss.str();
431 }
432
434 if(!benchmark) {
435 std::cout << "Benchmarking is disabled. Enable it by setting World::benchmark to true.\n";
436 return;
437 }
438 struct Stats { double sum=0, mean=0, stddev=0; size_t n=0; };
439
440 auto demangle = [&](const std::string &mangled) {
441 int status = 0;
442 char* dem = abi::__cxa_demangle(mangled.c_str(), nullptr, nullptr, &status);
443 std::string s = (status==0 && dem) ? dem : mangled;
444 std::free(dem);
445 return s;
446 };
447
448 auto formatTime = [&](double us)->std::string {
449 std::ostringstream o;
450 if (us < 1e3) o << std::fixed<<std::setprecision(2)<<us << " us";
451 else if (us < 1e6) o << std::fixed<<std::setprecision(2)<<us/1e3<< " ms";
452 else if (us < 6e7) o << std::fixed<<std::setprecision(2)<<us/1e6<< " s";
453 else o << std::fixed<<std::setprecision(2)<<us/6e7<< " min";
454 return o.str();
455 };
456
457 struct Row { std::string plugin, hook; Stats st; };
458 std::vector<Row> rows;
459 rows.reserve(benchResults.size());
460 size_t maxP=0, maxH=0;
461 for (auto const& [key, frameMap] : benchResults) {
462 auto pos = key.rfind('@');
463 std::string mangledP = (pos!=std::string::npos ? key.substr(0,pos) : key);
464 std::string hookName = (pos!=std::string::npos ? key.substr(pos+1) : "");
465 std::string pluginName = demangle(mangledP);
466 auto lt = pluginName.find('<');
467 pluginName = lt!=std::string::npos ? pluginName.substr(0,lt) : pluginName;
468 auto ns = pluginName.rfind("::");
469 pluginName = ns!=std::string::npos ? pluginName.substr(ns+2) : pluginName;
470
471 maxP = std::max(maxP, pluginName.size());
472 maxH = std::max(maxH, hookName.size());
473
474 Stats st; st.n = frameMap.size();
475 for (auto const& [_f, us] : frameMap) st.sum += us;
476 if (st.n) {
477 st.mean = st.sum / st.n;
478 double var = 0;
479 for (auto const& [_f, us] : frameMap) {
480 double d = us - st.mean;
481 var += d*d;
482 }
483 st.stddev = std::sqrt(var / st.n);
484 }
485 rows.push_back({ pluginName, hookName, st });
486 }
487
488 std::sort(rows.begin(), rows.end(),
489 [](auto const &a, auto const &b){
490 return a.st.sum > b.st.sum;
491 }
492 );
493
494 const size_t W_P = std::min(maxP+2, size_t(40));
495 const size_t W_H = std::min(maxH+2, size_t(30));
496 const int W_N = 14;
497
499 << std::left << std::setw(W_P) << "Plugin"
500 << std::left << std::setw(W_H) << "Hook"
501 << std::right
502 << std::setw(W_N) << "Total"
503 << std::setw(W_N) << "Mean"
504 << std::setw(W_N) << "StdDev"
505 << std::setw(10) << "Frames"
506 << "\n"
507 << std::string(W_P + W_H + W_N*3 + 10, '-') << "\n";
508
509 for (auto const& r : rows) {
510 auto p = r.plugin.size() > W_P-1
511 ? r.plugin.substr(0, W_P-4) + "..."
512 : r.plugin;
513 auto h = r.hook.size() > W_H-1
514 ? r.hook.substr(0, W_H-4) + "..."
515 : r.hook;
516
518 << std::left << std::setw(W_P) << p
519 << std::left << std::setw(W_H) << h
520 << std::right
521 << std::setw(W_N) << formatTime(r.st.sum)
522 << std::setw(W_N) << formatTime(r.st.mean)
523 << std::setw(W_N) << formatTime(r.st.stddev)
524 << std::setw(10) << r.st.n
525 << "\n";
526 }
527 }
532 runHooks(Hooks::destructor);
533 while (!cells.empty()) delete cells.back(), cells.pop_back();
534 while (!newCells.empty()) delete newCells.back(), newCells.pop_back();
535 }
536
539};
540} // namespace MecaCell
541#endif
MecaCell::Vec pos
Definition: CellBody.hpp:34
CellPlugin< cell_t > embedded_plugin_t
Definition: CellBody.hpp:39
PrimoCell< CellBody > Cell
Definition: Scenario.hpp:16
Where "everything" happens.
Definition: world.hpp:29
void registerPlugins(P &&p, Rest &&... otherPlugins)
Registers a plugin. A plugin is a class or struct that contains hooks.
Definition: world.hpp:196
void registerPlugins()
Definition: world.hpp:201
double getDt() const
Definition: world.hpp:293
void setUpdateBehaviorPeriod(size_t p)
sets the period at which the world must call the updateBehavior method of each cell....
Definition: world.hpp:307
size_t getNbUpdates() const
get the number of update since the creation of the world
Definition: world.hpp:220
void(World *) hook_s
Definition: world.hpp:38
void allForcesHaveBeenAppliedToCells()
this method triggers the allForcesAppliedToCells. It should be called by the embedded physics plugin ...
Definition: world.hpp:277
void setDtInH(double d)
Definition: world.hpp:292
Cell * createCell(Args &&... args)
Creates a new cell and adds it through addCell()
Definition: world.hpp:230
void setDt(double d)
sets the amount by which time is increased at each update() call.
Definition: world.hpp:289
bool benchmark
Definition: world.hpp:93
DECLARE_HOOK(onAddCell, beginUpdate, preBehaviorUpdate, preDeleteDeadCellsUpdate, postBehaviorUpdate, endUpdate, allForcesAppliedToCells, destructor) std World(size_t nThreads=0)
cells that are registered to be added
Definition: world.hpp:118
void setShuffleCells(bool s)
Definition: world.hpp:298
double getDtInH() const
Definition: world.hpp:296
void addNewCells()
effectively adds the new cells that were registered by addCell triggers addCell hooks if there is som...
Definition: world.hpp:265
~World()
World's destructor. Triggers the destructor hooks and delete all cells.
Definition: world.hpp:531
void setDtInS(double d)
Definition: world.hpp:290
void update()
main update method
Definition: world.hpp:371
void runHooks(const Hooks &h)
Definition: world.hpp:162
size_t updtBhvPeriod
+1 on cell add. Used for cell's unique ids
Definition: world.hpp:70
Integrator integrator_t
Definition: world.hpp:37
EXPORTABLE(World, KV(frame), KV(cells))
@ignore
void addCell(Cell *c)
adds a cell to the new cells batch (which will be added to the main cells container at the end of the...
Definition: world.hpp:246
std::string getBenchmarksJSON() const
Definition: world.hpp:383
void setParallelUpdateBehavior(bool p)
enables or disables the parallelisation of the cells' updateBehavior methods Only has effect if nbThr...
Definition: world.hpp:128
void registerHook(const Hooks &h, const std::string &pluginName, const std::string &hookName, std::function< hook_s > fn)
register a single hook method. cf registerPlugins()
Definition: world.hpp:155
std::vector< std::pair< cell_t *, cell_t * > > getConnectedCellsList()
returns a list of pair of connected cells
Definition: world.hpp:317
double getDtInS() const
Definition: world.hpp:294
vector< Cell * > newCells
Definition: world.hpp:100
bool shuffleCells
Definition: world.hpp:72
size_t nbAddedCells
+1 at each update. cf getNbUpdates()
Definition: world.hpp:69
void setDtInMn(double d)
Definition: world.hpp:291
double getDtInMn() const
Definition: world.hpp:295
double dtH
Definition: world.hpp:92
std::unordered_map< std::string, std::map< size_t, double > > benchResults
Definition: world.hpp:33
void setNbThreads(size_t n)
Definition: world.hpp:139
void clearHooks()
end of recursion
Definition: world.hpp:207
void deleteDeadCells()
removes all cells marked dead
Definition: world.hpp:77
double dt
Definition: world.hpp:90
void refreshId(Cell *c)
Definition: world.hpp:255
double dtMn
The amount by which time is increased every update.
Definition: world.hpp:91
ThreadPool threadpool
Definition: world.hpp:41
bool parallelUpdateBehavior
period at which the world should call the cells updateBehavior method.
Definition: world.hpp:71
typename embedded_plugin_type< cell_t >::type cellPlugin_t
Definition: world.hpp:64
void showBenchmarksSummaries() const
Definition: world.hpp:433
size_t nbThreads
Definition: world.hpp:31
std::chrono::high_resolution_clock Clock
Definition: world.hpp:32
size_t getUpdateBehaviorPeriod() const
Definition: world.hpp:309
cellPlugin_t cellPlugin
Definition: world.hpp:97
size_t frame
Definition: world.hpp:68
size_t getNbThreads()
the size of the threadpool that can be used by plugins to launch asynchronous jobs and also directly ...
Definition: world.hpp:137
void callUpdateBehavior()
calls the updateBehavior of each cell, potentially in parallel (see parallelUpdateBehavior and nbThre...
Definition: world.hpp:332
Represents a cell in the simulation.
Definition: PrimoCell.hpp:58
void setWorld(World *w)
Sets the world for the cell.
Definition: PrimoCell.hpp:155
Simple threadpool, largely inspired by github.com/progschj/ThreadPool.
Definition: threadpool.hpp:24
void setNbThreads(size_t n)
Definition: threadpool.hpp:38
void waitUntilLast()
Definition: threadpool.hpp:59
void autoChunks(Container &v, size_t minChunkSize, double avgTasksPerThread, F f)
Definition: threadpool.hpp:99
A simple vector class template.
Definition: std.hpp:324
void reserve(size_t newCapacity)
Reserves storage for the specified number of elements.
Definition: std.hpp:406
void pop_back()
Removes the last element of the vector.
Definition: std.hpp:345
void push_back(const T &value)
Adds an element to the end of the vector.
Definition: std.hpp:338
iterator begin()
Returns an iterator to the first element.
Definition: std.hpp:443
iterator end()
Returns an iterator to the last element.
Definition: std.hpp:452
T & back()
Returns a reference to the last element.
Definition: std.hpp:372
void insert(iterator position, const T &value)
Inserts an element at the specified position.
Definition: std.hpp:425
bool empty() const
Checks if the vector is empty.
Definition: std.hpp:381
#define KV(p)
Definition: exportable.hpp:30
#define DECLARE_HOOK(...)
Definition: hooktools.hpp:194
this file contains various miscellanious utility functions & helpers *
constexpr size_t eToUI(const T &t)
Definition: utils.hpp:129
ordered_pair< T * > make_ordered_cell_pair(T *a, T *b)
typename make_void< Ts... >::type void_t
Definition: introspect.hpp:12
iostream cout
Standard output stream.
Definition: std.hpp:301
void shuffle(RandomIt first, RandomIt last)
Shuffles the elements in a range.
Definition: std.hpp:227
double sqrt(double x)
Computes the square root of a number.
Definition: std.hpp:32
double max(double a, double b)
Computes the maximum of two numbers.
Definition: std.hpp:280
double min(double a, double b)
Computes the minimum of two numbers.
Definition: std.hpp:291
static random_engine_t & globalRand()
access to the static global random engine.This pseudo - random generator is* used in random 3D vector...
Definition: config.hpp:39