Conway's game of life simulator made in SkeletonGL & C++ with a focus on performance
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

332 lines
16 KiB

// Author: XENOBYTE.XYZ
// License: MIT License
// Website: https://xenobyte.xyz/projects/?nav=skeletongl
#ifndef WINDOW_HPP
#define WINDOW_HPP
#include <iostream>
#include <array>
#include <ctime>
#include <vector>
#include <limits>
#include <memory>
#include <stdio.h>
#include <thread>
#include <cmath>
#include <fstream>
#include <cstdint>
#include <random>
#include <stdexcept>
#include "skeletonGL/skeletonGL.hpp"
#include "cell_auto_simulation.hpp"
// Fixed Time Step
struct FTS
{
float _deltaTimeNS; // The time it took the past frame to complete in nanoseconds
float _deltaTimeMS; // The time it took the past frame to complete in milliseconds
float _fixedTimeStepMS; // The goal FPS in milliseconds i.e. (1.0f / 60.0f) for 60 FPS
float _dTAccumulator; // Used to compute the total amount of frames to update based on the delta time accumulated so far
std::uint16_t _targetFPS; // Goal FPS
std::uint16_t _updateCycles; // Updates to be performed this frame
std::uint16_t _maxUpdatesPerFrame; // Max amount of updates to be processed in a single frame
std::chrono::steady_clock::time_point _frameTimeStart, _frameTimeEnd; ///< C++11 chrono based counter
float _timeElapsed, _deltaTimeElapsed; // Total time since epoch, used to time generate FPS meta data
std::uint16_t _frameCounter; // Counts frames, reset every second
std::uint16_t _FPS; // Current FPS, updated every second
FTS(std::uint16_t targetFPS = 60, std::uint8_t maxUpdatesPerFrame = 5)
{
_deltaTimeNS = _deltaTimeMS = _dTAccumulator = 0.0f;
_targetFPS = targetFPS;
_fixedTimeStepMS = 1.0f / static_cast<float>(_targetFPS); // Refresh the simulation at 60hz
_maxUpdatesPerFrame = maxUpdatesPerFrame;
_updateCycles = 0;
_frameTimeStart = std::chrono::steady_clock::now();
_frameTimeEnd = _frameTimeStart;
_timeElapsed = _deltaTimeElapsed = 0.0f;
_frameCounter = _FPS = 0;
}
void frameStart()
{
_frameTimeStart = std::chrono::steady_clock::now();
_timeElapsed += std::chrono::duration_cast<std::chrono::milliseconds>(_frameTimeStart - _frameTimeEnd).count();
// Transform the returned time in nanoseconds to milliseconds by / 1000000000, std::chrono::milliseconds return an int
// and looses all but the first two values of resolution
_deltaTimeNS = std::chrono::duration_cast<std::chrono::nanoseconds>(_frameTimeStart - _frameTimeEnd).count();
_deltaTimeMS = _deltaTimeNS / 1000000000;
// Process frameStart meta data?
_frameCounter++;
if ( ( (_timeElapsed / 1000) - _deltaTimeElapsed) >= 1.0) // A second has passed, collect meta data
{
_FPS = _frameCounter;
_frameCounter = 0;
_deltaTimeElapsed = _timeElapsed / 1000;
}
// Process frame time
_frameTimeEnd = _frameTimeStart;
// Fix the time step
_dTAccumulator += _deltaTimeMS;
// Compute the total updates for this frame
_updateCycles = static_cast<int>(std::floor(_dTAccumulator / _fixedTimeStepMS));
// Consume the cycles from the accumulator using the goal FPS time as reference
if (_updateCycles > 0)
_dTAccumulator -= _updateCycles * _fixedTimeStepMS;
// The accumulator must be smaller than the goal delta time
assert("Accumulator must have a value lower than the fixed time step" && _dTAccumulator < _fixedTimeStepMS + FLT_EPSILON);
// Should the program lag behind its predefined time step, force it to recover a max amount
// of times each frame to prevent a sprial-of-death scenario
_updateCycles = std::min(_updateCycles, _maxUpdatesPerFrame);
}
inline std::uint16_t consumeUpdateCycle()
{
std::uint16_t tmp = _updateCycles;
_updateCycles--;
return tmp;
}
};
// Simulates a simple console for debugging purposes
// To add support for more keys, add the SDL2 key codes to
// SGL_InputFrame & SGL_Window::input first
class DebugConsole
{
// No copying
DebugConsole(const DebugConsole&) = delete;
DebugConsole &operator = (const DebugConsole&) = delete;
DebugConsole( DebugConsole&) = delete;
DebugConsole &operator = (DebugConsole&) = delete;
bool _textMode;
std::vector<std::string> _textInput, _commandHistory;
std::string _textInputCache;
std::uint16_t _scrollTextInputCounter, _historyLimit;
bool _scrollTextInputCopied, _textInputFocus;
std::uint8_t _maxInputSize;
public:
DebugConsole() : _textMode(false), _textInputCache(""), _scrollTextInputCounter(0),
_scrollTextInputCopied(true), _textInputFocus(true), _maxInputSize(100),
_historyLimit(300) {}
auto historySize() const noexcept { return _textInput.size(); }
auto cmdSize() const noexcept { return _commandHistory.size(); }
std::string inputStr() const noexcept { return this->_textInputCache; }
// Return the oldest command in the vector
std::string lastCommand() noexcept
{
if (!_commandHistory.empty())
return this->_commandHistory.front();
else
return "";
}
void input(const SGL_InputFrame &inputFrame, const SGL_InputFrame &deltaFrame)
{
if (this->_textInputCache.size() <= 80)
{
if (inputFrame.q.pressed && !deltaFrame.q.pressed) { this->_textInputCache += 'q';}
if (inputFrame.w.pressed && !deltaFrame.w.pressed) { this->_textInputCache += 'w';}
if (inputFrame.e.pressed && !deltaFrame.e.pressed) { this->_textInputCache += 'e';}
if (inputFrame.r.pressed && !deltaFrame.r.pressed) { this->_textInputCache += 'r';}
if (inputFrame.t.pressed && !deltaFrame.t.pressed) { this->_textInputCache += 't';}
if (inputFrame.y.pressed && !deltaFrame.y.pressed) { this->_textInputCache += 'y';}
if (inputFrame.u.pressed && !deltaFrame.u.pressed) { this->_textInputCache += 'u';}
if (inputFrame.i.pressed && !deltaFrame.i.pressed) { this->_textInputCache += 'i';}
if (inputFrame.o.pressed && !deltaFrame.o.pressed) { this->_textInputCache += 'o';}
if (inputFrame.p.pressed && !deltaFrame.p.pressed) { this->_textInputCache += 'p';}
if (inputFrame.a.pressed && !deltaFrame.a.pressed) { this->_textInputCache += 'a';}
if (inputFrame.s.pressed && !deltaFrame.s.pressed) { this->_textInputCache += 's';}
if (inputFrame.d.pressed && !deltaFrame.d.pressed) { this->_textInputCache += 'd';}
if (inputFrame.f.pressed && !deltaFrame.f.pressed) { this->_textInputCache += 'f';}
if (inputFrame.g.pressed && !deltaFrame.g.pressed) { this->_textInputCache += 'g';}
if (inputFrame.h.pressed && !deltaFrame.h.pressed) { this->_textInputCache += 'h';}
if (inputFrame.j.pressed && !deltaFrame.j.pressed) { this->_textInputCache += 'j';}
if (inputFrame.k.pressed && !deltaFrame.k.pressed) { this->_textInputCache += 'k';}
if (inputFrame.l.pressed && !deltaFrame.l.pressed) { this->_textInputCache += 'l';}
if (inputFrame.z.pressed && !deltaFrame.z.pressed) { this->_textInputCache += 'z';}
if (inputFrame.x.pressed && !deltaFrame.x.pressed) { this->_textInputCache += 'x';}
if (inputFrame.c.pressed && !deltaFrame.c.pressed) { this->_textInputCache += 'c';}
if (inputFrame.v.pressed && !deltaFrame.v.pressed) { this->_textInputCache += 'v';}
if (inputFrame.b.pressed && !deltaFrame.b.pressed) { this->_textInputCache += 'b';}
if (inputFrame.n.pressed && !deltaFrame.n.pressed) { this->_textInputCache += 'n';}
if (inputFrame.m.pressed && !deltaFrame.m.pressed) { this->_textInputCache += 'm';}
if (inputFrame.num0.pressed && !deltaFrame.num0.pressed) { this->_textInputCache += '0';}
if (inputFrame.num1.pressed && !deltaFrame.num1.pressed) { this->_textInputCache += '1';}
if (inputFrame.num2.pressed && !deltaFrame.num2.pressed) { this->_textInputCache += '2';}
if (inputFrame.num3.pressed && !deltaFrame.num3.pressed) { this->_textInputCache += '3';}
if (inputFrame.num4.pressed && !deltaFrame.num4.pressed) { this->_textInputCache += '4';}
if (inputFrame.num5.pressed && !deltaFrame.num5.pressed) { this->_textInputCache += '5';}
if (inputFrame.num6.pressed && !deltaFrame.num6.pressed) { this->_textInputCache += '6';}
if (inputFrame.num7.pressed && !deltaFrame.num7.pressed) { this->_textInputCache += '7';}
if (inputFrame.num8.pressed && !deltaFrame.num8.pressed) { this->_textInputCache += '8';}
if (inputFrame.num9.pressed && !deltaFrame.num9.pressed) { this->_textInputCache += '9';}
if (inputFrame.F1.pressed && !deltaFrame.F1.pressed) { this->_textInputCache += '1';}
if (inputFrame.F2.pressed && !deltaFrame.F2.pressed) { this->_textInputCache += '2';}
if (inputFrame.F3.pressed && !deltaFrame.F3.pressed) { this->_textInputCache += '3';}
if (inputFrame.F4.pressed && !deltaFrame.F4.pressed) { this->_textInputCache += '4';}
if (inputFrame.F5.pressed && !deltaFrame.F5.pressed) { this->_textInputCache += '5';}
if (inputFrame.F6.pressed && !deltaFrame.F6.pressed) { this->_textInputCache += '6';}
if (inputFrame.F7.pressed && !deltaFrame.F7.pressed) { this->_textInputCache += '7';}
if (inputFrame.F8.pressed && !deltaFrame.F8.pressed) { this->_textInputCache += '8';}
if (inputFrame.F9.pressed && !deltaFrame.F9.pressed) { this->_textInputCache += '9';}
if (inputFrame.F10.pressed && !deltaFrame.F10.pressed) { this->_textInputCache += "10";}
if (inputFrame.F11.pressed && !deltaFrame.F11.pressed) { this->_textInputCache += "11";}
if (inputFrame.F12.pressed && !deltaFrame.F12.pressed) { this->_textInputCache += "12";}
if (inputFrame.period.pressed && !deltaFrame.period.pressed) { this->_textInputCache += '.';}
if (inputFrame.comma.pressed && !deltaFrame.comma.pressed) { this->_textInputCache += ',';}
if (inputFrame.slash.pressed && !deltaFrame.slash.pressed) { this->_textInputCache += '/';}
}
if (inputFrame.space.pressed && !deltaFrame.space.pressed) { this->_textInputCache += ' '; }
if (inputFrame.backspace.pressed && !deltaFrame.backspace.pressed && _textInputCache.length() > 0)
this->_textInputCache.pop_back();
// Scrolls command history
if (inputFrame.up.pressed && !deltaFrame.up.pressed && !_textInput.empty())
{
if (_textInputFocus)
{
_textInputFocus = false;
this->_scrollTextInputCounter = 0;
}
else
{
if (this->_scrollTextInputCounter >= this->_textInput.size() - 1)
this->_scrollTextInputCounter = this->_textInput.size() - 1;
else
{
this->_scrollTextInputCounter++;
}
}
_scrollTextInputCopied = false;
}
if (inputFrame.down.pressed && !deltaFrame.down.pressed && !_textInput.empty())
{
if (this->_scrollTextInputCounter == 0)
{
_textInputCache = "";
_textInputFocus = true;
}
else
{
this->_scrollTextInputCounter--;
_scrollTextInputCopied = false;
}
}
if (!_textInputFocus && !_scrollTextInputCopied)
{
this->_textInputCache = this->_textInput[_scrollTextInputCounter];
_scrollTextInputCopied = true;
}
if (inputFrame.enter.pressed && !deltaFrame.enter.pressed)
{
std::string cmd = this->_textInputCache;
// If the string starts with a '/' treat it as a command
if (!cmd.empty() && cmd.front() == '/')
{
if (_commandHistory.size() >= _historyLimit)
_commandHistory.pop_back();
this->_commandHistory.insert(_commandHistory.begin(), std::move(cmd));
// SGL_Log("[" + std::to_string(+_textInput.size()) + "]-> " + this->_textInputCache, LOG_LEVEL::DEBUG, LOG_COLOR::TERM_RED);
}
// Add it to the console vector
SGL_Log("[" + std::to_string(+_textInput.size()) + "]-> " + this->_textInputCache);
if (!_textInputCache.empty())
{
if (_textInput.size() >= _historyLimit)
_textInput.pop_back();
this->_textInput.insert(_textInput.begin() ,std::move(this->_textInputCache));
}
this->_textInputCache.clear();
this->_scrollTextInputCounter = 0;
_textInputFocus = true;
}
}
};
class Window
{
private:
// No copying
Window(const Window&) = delete;
Window &operator = (const Window&) = delete;
std::unique_ptr<SGL_Window> _upWindowManager;
SGL_InputFrame _deltaInput;
// Used to keep the mouse values cache relevant for the debug window
bool _mouseLBHeld, _mouseRBHeld, _mouseMidHeld, _mouseScrollUp, _mouseScrollDown;
bool _mainLoopActive, _paused, _debugPanel;
float _cameraZoom;
std::unique_ptr<SGL_Sprite> _debugBG;
std::unique_ptr<DebugConsole> _console;
bool _textMode, _drawConsole;
// FTS
FTS _fts{60};
// UI sprites
std::shared_ptr<SGL_Sprite>pixel;
std::shared_ptr<SGL_Bitmap_Text>text;
float _simulationTime, _msgTime;
bool _generating;
std::shared_ptr<CellAutoSimulation> _CAS;
std::vector<glm::mat4> _spriteBatchData;
std::array<std::uint16_t, SIMULATION_WIDTH*SIMULATION_HEIGHT> _seed;
SGL_Color _monoColor;
std::string _saveFile, _displayMsg;
// Small window with program data for debbuging
void drawDebugPanel(float x, float y, float fontSize, SGL_Color color);
void drawConsole(float x, float y, float fontSize, SGL_Color color);
// Init the SGL subsystem
int createSGLWindow();
// Console commands and other controls
void processCommands();
public:
Window();
~Window();
void mainLoop();
void render();
void update();
void input();
bool saveSeed(const std::string &fn);
bool loadSeed(const std::string &fn);
void randomizeSeed(std::uint8_t freq);
// Repopulates the simulation with the data in the pSeed array
void reset();
void drawMouseData(std::int32_t mouseX, std::int32_t mouseY);
};
#endif