Lamp-Da 0.1
A compact lantern project
Loading...
Searching...
No Matches
simulator.h
Go to the documentation of this file.
1
5#ifndef SIMULATOR_H
6#define SIMULATOR_H
7
8#include <cstring>
9#include <iostream>
10
11// include this first
12#include "src/system/global.h"
13
14#include "parameter_parser.h"
15#include "simulator_state.h"
16
17// see LMBD_LAMP_TYPE__${UPPER_SIM_NAME} hard-coded in simulator/Makefile
18#include "src/user/constants.h"
19
20#include <SFML/Graphics.hpp>
21#include <SFML/Graphics/Color.hpp>
22#include <SFML/System/Time.hpp>
23#include <SFML/Window/Keyboard.hpp>
24
26// #define LMBD_DEBUG_SIMU_REALCOLORS
27
28namespace simulator {
29// (_LampTy from simulator_state.h)
30constexpr int ledW = _LampTy::maxWidth;
31constexpr int ledH = _LampTy::maxOverflowHeight;
32constexpr int ledCount = _LampTy::ledCount;
33
34constexpr float fLedW = _LampTy::maxWidthFloat;
35constexpr float fResidueW = 1 / (2 * fLedW - 2 * floor(fLedW) - 1);
36} // namespace simulator
37
38#include "src/user/functions.h"
40
42// handle simulation of voltage and current in the system
44
45#include <thread>
46
47namespace simulator {
48
49//
50// simulator core loop
51//
52
53template<typename T> struct simulator
54{
55 static constexpr uint screenWidth = 1920;
56 static constexpr uint screenHeight = 1080;
57 static constexpr char title[] = "lampda simulator";
58
59 static constexpr uint brightnessWidth = 480;
60
61 static int run()
62 {
63 T simu;
64 sf::Clock time;
65
66 // brightness tracker
67 std::array<uint16_t, brightnessWidth> brightnessTracker {};
68 std::array<sf::RectangleShape, brightnessWidth> dots;
69
70 // matrix shift
71 int fakeXorigin = 0;
72 int fakeXend = 0;
73
74 // pixel shapes
75 std::array<sf::RectangleShape, _LampTy::ledCount> shapes;
76
77 // init font
78 sf::Font font;
79 bool enableFont = false;
80 bool enableText = false;
81 if (font.loadFromFile("simulator/resources/DejaVuSansMono.ttf"))
82 {
83 enableFont = true;
84 }
85 else
86 {
87 fprintf(stderr, "DejaVuSansMono.ttf not found, disabling text...");
88 }
89
90 // render window
91 sf::RenderWindow window(sf::VideoMode({screenWidth, screenHeight}), title);
92
93 sf::CircleShape buttonMask(simu.buttonSize);
94 sf::CircleShape indicator(simu.buttonSize + simu.buttonMargin);
95 buttonMask.setFillColor(sf::Color::Black);
96
97 sf::Vector2<int> mousePos = sf::Mouse::getPosition(window);
98 const float indCoordX = simu.buttonLeftPos;
99 const float indCoordY = (simu.ledSizePx + simu.ledPaddingPx) * (ledH + 3);
100
101 // TODO: #156 move mock-specific parts to another process
102 // ======================================================
103
104 // mock run the threads (do not give each subprocess a thread)
105 mock_registers::shouldStopThreads = false;
106
107 // load initial values
109 // start electrical simulation,
110 start_electrical_mock();
111
112 // force init of time clock
113 time_mocks::reset();
114
115 // Main program setup
117
118 // =================================================
119
120 // reference to global state
121 auto& state = globals::state;
122
123 uint64_t skipframe = 0;
124 while (window.isOpen())
125 {
126 uint64_t mouseClick = 0;
127
128 mousePos = sf::Mouse::getPosition(window);
129 enableText = enableFont && sf::Mouse::isButtonPressed(sf::Mouse::Button::Left);
130 enableText |= state.verbose;
131
132 // read events, prevent windows lockup
133 sf::Event event;
134 while (window.pollEvent(event))
135 {
136 if (event.type == sf::Event::Closed)
137 {
138 window.close();
139 break;
140 }
141
142 if (event.type == sf::Event::KeyPressed)
143 {
144 auto kpressed = event.key.code;
145 switch (kpressed)
146 {
147 default:
148 state.lastKeyPressed = 0;
149 break;
150 case sf::Keyboard::Key::P: // p == pause
151 state.lastKeyPressed = 'p';
152 break;
153 case sf::Keyboard::Key::V: // v == verbose
154 state.lastKeyPressed = 'v';
155 break;
156 case sf::Keyboard::Key::T: // t == tick +pause
157 state.lastKeyPressed = 't';
158 break;
159 case sf::Keyboard::Key::H: // h == higher speed
160 state.lastKeyPressed = 'h';
161 break;
162 case sf::Keyboard::Key::G: // g == glower speeds
163 state.lastKeyPressed = 'g';
164 break;
165 case sf::Keyboard::Key::J: // j == shift matrix left
166 state.lastKeyPressed = 'j';
167 break;
168 case sf::Keyboard::Key::K: // k == shift matrix right
169 state.lastKeyPressed = 'k';
170 break;
171 case sf::Keyboard::Key::D:
172 state.lastKeyPressed = 'd';
173 break;
174 case sf::Keyboard::Key::U:
175 state.lastKeyPressed = 'u';
176 break;
177 case sf::Keyboard::Key::I:
178 state.lastKeyPressed = 'i';
179 break;
180 case sf::Keyboard::Key::R:
181 state.lastKeyPressed = 'r';
182 break;
183 case sf::Keyboard::Key::C:
184 state.lastKeyPressed = 'c';
185 break;
186 case sf::Keyboard::Key::Q:
187 state.lastKeyPressed = 'q';
188 break;
189 }
190
191 break;
192 }
193
194 if (event.type == sf::Event::KeyReleased)
195 {
196 // tick forward (once) and pause
197 if (state.lastKeyPressed == 't')
198 {
199 state.paused = false;
200 state.tickAndPause = 2;
201 }
202
203 // pause event
204 if (state.lastKeyPressed == 'p')
205 {
206 state.paused = !state.paused;
207 }
208
209 // verbose event
210 if (state.lastKeyPressed == 'v')
211 {
212 state.verbose = !state.verbose;
213 }
214
215 // slower simulation
216 if (state.lastKeyPressed == 'g')
217 {
218 if (state.slowTimeFactor > 1.50f)
219 {
220 state.slowTimeFactor *= 0.95f;
221 }
222 else if (state.slowTimeFactor > 1.0f)
223 {
224 state.slowTimeFactor = ceil(state.slowTimeFactor * 20 - 1) / 20;
225 }
226 else
227 {
228 state.slowTimeFactor = std::max<float>(0.1f, state.slowTimeFactor - 0.05f);
229 }
230 fprintf(stderr, "slower %f\n", state.slowTimeFactor);
231 }
232
233 // faster simulation
234 if (state.lastKeyPressed == 'h')
235 {
236 if (state.slowTimeFactor > 1.20f)
237 {
238 state.slowTimeFactor *= 1.05f;
239 }
240 else if (state.slowTimeFactor > 1.0f)
241 {
242 state.slowTimeFactor = ceil(state.slowTimeFactor * 20 + 1) / 20;
243 }
244 else
245 {
246 state.slowTimeFactor += 0.05f;
247 }
248 fprintf(stderr, "faster %f\n", state.slowTimeFactor);
249 }
250
251 // shift forward XY display
252 if (state.lastKeyPressed == 'k')
253 {
254 fakeXorigin += 1;
255 if (fakeXorigin > ledW - 1)
256 fakeXorigin = 0;
257 fakeXend = std::max<int>(fakeXorigin - std::min<int>(4, ledW - 1 - fakeXorigin), 0);
258 }
259
260 // shift backward XY display
261 if (state.lastKeyPressed == 'j')
262 {
263 fakeXorigin -= 1;
264 if (fakeXorigin < 0)
265 fakeXorigin = ledW - 1;
266 fakeXend = std::max<int>(fakeXorigin - std::min<int>(4, ledW - 1 - fakeXorigin), 0);
267 }
268
269 // reset key pressed
270 state.lastKeyPressed = 0;
271 break;
272 }
273
274 if (event.type == sf::Event::MouseButtonPressed)
275 {
276 float dx = indCoordX - mousePos.x + simu.buttonSize;
277 float dy = indCoordY - mousePos.y + simu.buttonSize;
278 float norm = simu.buttonSize;
279
280 if (dx * dx + dy * dy < norm * norm)
281 {
282 mouseClick += 3;
283 }
284 break;
285 }
286 }
287
288 // update simulator global state
289 state.isButtonPressed = sf::Keyboard::isKeyPressed(sf::Keyboard::Key::Space);
290 if (mouseClick > 0)
291 {
292 mouseClick -= 1;
293 state.isButtonPressed = true;
294 }
295
296 if (state.tickAndPause > 0)
297 {
298 if (state.tickAndPause == 1)
299 {
300 state.paused = true;
301 }
302 state.tickAndPause -= 1;
303 }
304
305 // TODO: #156 move mock-specific parts to another process
306 // ======================================================
307
308 if (!state.paused)
309 {
310 // deep sleep, exit
311 if (mock_registers::isDeepSleep)
312 {
313 // close all threads
314 mock_registers::shouldStopThreads = true;
315 window.close();
316 break;
317 }
318
319 // update simu parameters
321 // update the callbacks of the gpios (simulate interrupts)
322 mock_gpios::update_callbacks();
323
324 // main program loop
325 ::lampda::main_loop(mock_registers::addedAlgoDelay);
326 }
327
328 const bool isOutputEnabled = is_output_enabled();
330 {
331#ifdef LMBD_LAMP_TYPE__INDEXABLE
332 // brightness on the indexale lamp
334 static curve_t brightnessCurve({curve_t::point_t {0, ::lampda::minimumAllowedBrightness_8},
335 curve_t::point_t {::lampda::brightness::absoluteMaximumBrightness, 255}});
336 state.brightness = brightnessCurve.sample(::lampda::logic::brightness::get_brightness());
337
338 const bool isVoltageHighEnough = mock_electrical::outputVoltage > 11.5;
339 for (size_t I = 0; I < _LampTy::ledCount; ++I)
340 {
341 state.colorBuffer[I] =
342 isOutputEnabled ?
343 (isVoltageHighEnough ? ::lampda::user::_private::strip.getRawPixelColor(I) : 0xffffff) :
344 0;
345 }
346#endif
347 }
348 else
349 {
350 state.brightness =
351 (::lampda::logic::brightness::get_brightness() * 255) / ::lampda::brightness::absoluteMaximumBrightness;
352
353 for (size_t I = 0; I < _LampTy::ledCount; ++I)
354 state.colorBuffer[I] = isOutputEnabled ? 0xffff00 : 0;
355 }
356
357 state.indicatorColor = mock_indicator::get_color();
358 // ======================================================
359
360 const float ledSz = simu.ledSizePx;
361 const auto ledPadSz = simu.ledPaddingPx + simu.ledSizePx;
362 const auto ledOffset = ledSz / _LampTy::shiftPeriod;
363
364 const float Xbase = _LampTy::extraShiftTotal > 0 ? 0 : -_LampTy::extraShiftTotal;
365 const float Xextra = _LampTy::extraShiftTotal < 0 ? 0 : _LampTy::extraShiftTotal;
366
367 // draw slightly grey background to hightlight turned off LEDs
368 window.clear(sf::Color(13, 12, 11));
369
370 for (int Ypos = 0; Ypos < ledH; ++Ypos)
371 {
372 int realRowSize = ledW;
373 if (_LampTy::allDeltaResiduesY[Ypos])
374 realRowSize += 1;
375
376 for (int fXpos = -fakeXorigin; fXpos < realRowSize - fakeXend; ++fXpos)
377 {
378 int Yoff = 0;
379 int Xoff = 0;
380
381 int Xpos = fXpos;
382 if (fXpos < 0)
383 {
384 Xpos = realRowSize + fXpos;
385 Yoff = 1;
386
387 if (_LampTy::allDeltaResiduesY[Ypos])
388 Xoff = 1;
389 }
390
391 size_t I = ::lampda::modes::to_strip(Xpos, Ypos);
392 auto& shape = shapes[I];
393
394 auto realPos = ::lampda::modes::strip_to_XY(I);
395 if (realPos.x != Xpos || realPos.y != Ypos)
396 continue;
397
398 shape.setSize({ledSz, ledSz});
399
400 int rXpos = fXpos + fakeXorigin + Xoff;
401 int rYpos = Ypos + Yoff;
402 int shiftR = _LampTy::extraShiftResiduesY[Ypos];
403 int shiftL = Ypos + shiftR + 1;
404
405 if (_LampTy::shiftResidue == 1 && _LampTy::shiftPerTurn < 0.1)
406 shiftR = 0;
407
408 float shapeX = ledSz + rXpos * ledPadSz;
409 shapeX += ledOffset * Xbase;
410 shapeX += ledOffset * (shiftL % _LampTy::shiftPeriod);
411 shapeX -= (ledOffset - simu.ledPaddingPx / 2) * (Yoff - shiftR);
412 shapeX -= Xoff * simu.ledPaddingPx;
413
414 // make the origin more obvious
415 if (fXpos >= 0)
416 shapeX += simu.ledPaddingPx;
417
418 float shapeY = rYpos * ledPadSz;
419 shape.setPosition({shapeX, shapeY});
420
421 const uint32_t color = state.colorBuffer[I];
422 float b = (color & 0xff);
423 float g = ((color >> 8) & 0xff);
424 float r = ((color >> 16) & 0xff);
425
426#ifndef LMBD_DEBUG_SIMU_REALCOLORS
427 if (state.brightness != 255)
428 {
429 // scale by brightness
430 r = (uint16_t(r) * state.brightness + state.brightness) >> 8;
431 g = (uint16_t(g) * state.brightness + state.brightness) >> 8;
432 b = (uint16_t(b) * state.brightness + state.brightness) >> 8;
433 }
434#endif
435
436 shape.setFillColor(sf::Color(r, g, b));
437 window.draw(shape);
438
439 // display text information if mouse is pressed
440 if (enableText)
441 {
442 int MouseYpos = mousePos.y;
443 int MouseXpos = mousePos.x;
444
445 auto shapeXY = shape.getPosition();
446 float dx = shapeXY.x + ledSz / 2 - MouseXpos;
447 float dy = shapeXY.y + ledSz / 2 - MouseYpos;
448
449 // all shapes too far from cursor, does not display info
450 if (abs(2 * dx) > ledPadSz || abs(2 * dy) > ledPadSz)
451 continue;
452
453 // display (X,Y) on bottom-right
454 float largestX = ledSz + (ledW + 1 + fakeXorigin - fakeXend) * ledPadSz + ledOffset * (Xextra + Xbase);
455 float largestY = (ledH + Yoff) * ledPadSz + 8;
456 {
457 auto str = "(" + std::to_string(Xpos) + ", " + std::to_string(Ypos) + ")";
458
459 sf::Text text(str, font, 12);
460 sf::Vector2f where(largestX, largestY);
461 text.setPosition(where);
462 window.draw(text);
463 }
464
465 // display Ypos on the right
466 {
467 auto str = std::to_string(Ypos);
468 if (_LampTy::allDeltaResiduesY[Ypos])
469 str += " *";
470
471 sf::Text text(str, font, 12);
472 sf::Vector2f where(largestX, shapeXY.y + ledSz / 4);
473 text.setPosition(where);
474 window.draw(text);
475 }
476
477 // display Xpos on the bottom
478 {
479 auto str = std::to_string(Xpos);
480 if (Xpos == ledW)
481 str += " *";
482
483 sf::Text text(str, font, 12);
484 sf::Vector2f where(shapeXY.x, largestY);
485 text.setPosition(where);
486 window.draw(text);
487 }
488
489 // display misc info next to button
490 {
491 auto str = "(" + std::to_string(int(r)) + ", ";
492 str += std::to_string(int(g)) + ", ";
493 str += std::to_string(int(b)) + ")";
494 str += ", " + std::to_string(I);
495
496 sf::Text text(str, font, 12);
497 sf::Vector2f where(indCoordX + simu.buttonSize * 3, indCoordY - 24.f);
498 text.setPosition(where);
499 window.draw(text);
500
501 // (avoid superposed text)
502 enableText = false;
503 }
504 }
505 }
506 }
507
508 // display the indicator button
509 const uint32_t indicatorColor = state.indicatorColor;
510 float b = (indicatorColor & 0xff);
511 float g = ((indicatorColor >> 8) & 0xff);
512 float r = ((indicatorColor >> 16) & 0xff);
513 indicator.setFillColor(sf::Color(r, g, b));
514 indicator.setPosition({indCoordX, indCoordY});
515 buttonMask.setPosition({indCoordX + simu.buttonMargin, indCoordY + simu.buttonMargin});
516 window.draw(indicator);
517 window.draw(buttonMask);
518
519 // track brightness & display dot curve
520 float now = (time.getElapsedTime().asMilliseconds() * simu.fps) / 1000.f;
521 uint32_t trackerPos = now / simu.brightnessRate;
522
523 trackerPos = trackerPos % brightnessTracker.size();
524 brightnessTracker[trackerPos] = state.brightness;
525 brightnessTracker[(trackerPos + 1) % brightnessTracker.size()] = 0;
526
527 float xDotsOrigin = indCoordX + simu.buttonSize * 2 + simu.buttonMargin * 2 + 16.f;
528 float yDotsOrigin = indCoordY + simu.brightnessScale;
529
530 for (size_t I = 0; I < brightnessTracker.size(); ++I)
531 {
532 auto& dot = dots[I];
533
534 float v = (brightnessTracker[I] * simu.brightnessScale) / 256;
535 float x = xDotsOrigin + I;
536 float y = yDotsOrigin - v;
537 dot.setSize({1, 1});
538 dot.setPosition({x, y});
539 dot.setFillColor(sf::Color(0, 0xff, 0));
540 window.draw(dot);
541 }
542
543 // draw window
544 window.display();
545 }
546
547 stop_electrical_mock();
548
549 return 0;
550 }
551};
552
553} // namespace simulator
554
555#endif
Given a set of points, will fit multiple linear segments to it.
Definition: curves.h:36
General electrical simulation of the Lampda board.
Define specific needed user functions.
Main input point of the whole program.
Handle the physical simulation paremeters of a real lamp.
brightness_t get_brightness()
Return the current brightness value (in range 0 - brightness::absoluteMaximumBrightness)....
Definition: brightness_handle.cpp:39
@ indexable
Equivalent to LMBD_LAMP_TYPE__INDEXABLE.
static constexpr uint16_t to_strip(uint16_t, uint16_t)
convert grid coordinates to strip index
Definition: lamp_type.hpp:1100
static constexpr XYTy strip_to_XY(uint16_t n)
convert strip index to grid coordinates
Definition: lamp_type.hpp:1132
void main_setup()
Setup of the program, call once on systel start.
Definition: global.cpp:82
void main_loop(const uint32_t addedDelay)
Run the main program loop.
Definition: global.cpp:228
GlobalSimStateTy state
Store the global simulation state.
Definition: simulator_state.cpp:7
Simulator dedicated namespace.
Definition: default_simulation.h:8
void read_and_update_parameters()
Read parameters from the parameter file, and update the simulation.
Definition: parameter_parser.h:24
Handle the modification of the simulation parameters by a file.
Contain some simulation state.
static constexpr float maxWidthFloat
Width as a precise floating point number, equal to stripXCoordinates.
Definition: lamp_type.hpp:369
static constexpr uint16_t maxOverflowHeight
Larger height, taken as the absolute maximum Y coordinate, overflowing.
Definition: lamp_type.hpp:393
static constexpr LampTypes flavor
Which lamp flavor is currently used by the implementation?
Definition: lamp_type.hpp:336
static constexpr uint16_t maxWidth
(indexable) Width of "pixel space" w/ lamp taken as a LED matrix
Definition: lamp_type.hpp:366
static constexpr uint16_t ledCount
(indexable) Count of indexable LEDs on the lamp
Definition: lamp_type.hpp:353
User defined constants, relative to specific lamp types.
Define useful functions.