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
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
|
|
|