diff --git a/Makefile b/Makefile index 3d31304..91d9750 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ FLAGS = -pipe CFLAGS = -Wall CFLAGS += -Ofast -CFLAGS += -std=c++17 +CFLAGS += -std=c++20 #CFLAGS += -g #CFLAGS += -pg diff --git a/README.md b/README.md index b5a99ce..8d758a4 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Features/TODO - Display who is home - Display network speed - Display plex currently playing +- Play audio files (background music/tracks)? Depends on ========== diff --git a/src/board.cpp b/src/board.cpp new file mode 100644 index 0000000..d64c970 --- /dev/null +++ b/src/board.cpp @@ -0,0 +1,371 @@ +/////////////////////////////////////////////////////////////////////////////// +// Tyler Perkins +// 8-23-21 +// Board implementation +// + +#include "board.hpp" + +using namespace dashboard; + +/////////////////////////////////////////////////////////////////////////////// +// Structs //////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +size_t font_and_size_hash::operator()(const font_and_size& value) const{ + size_t hlhs = std::hash{}(value._font); + size_t hrhs = std::hash{}(value._ptsize); + + hlhs ^= hrhs + 0x9e3779b9 + (hlhs << 6) + (hlhs >> 2); + return hlhs; +} + +size_t string_and_font_hash::operator()(const string_and_font& value) const { + size_t hlhs = std::hash{}(value._string); + size_t hrhs = font_and_size_hash{}(value._fs); + + hlhs ^= hrhs + 0x9e3779b9 + (hlhs << 6) + (hlhs >> 2); + return hlhs; +} + +bool font_and_size::operator==(const font_and_size& rhs) const{ + if(this->_font == rhs._font && this->_ptsize == rhs._ptsize) + return true; + return false; +} +bool font_and_size::operator!=(const font_and_size& rhs) const{ + if(this->_font == rhs._font && this->_ptsize == rhs._ptsize) + return false; + return true; +} +bool font_and_size::operator> (const font_and_size& rhs) const{ + if(this->_font > rhs._font) + return true; + return false; +} +bool font_and_size::operator< (const font_and_size& rhs) const{ + if(this->_font < rhs._font) + return false; + return true; +} +bool font_and_size::operator>=(const font_and_size& rhs) const{ + return !(*this < rhs); +} +bool font_and_size::operator<=(const font_and_size& rhs) const{ + return !(*this > rhs); +} + +bool string_and_font::operator==(const string_and_font& rhs) const{ + if(this->_string == rhs._string && this->_fs == rhs._fs) + return true; + return false; +} +bool string_and_font::operator!=(const string_and_font& rhs) const{ + if(this->_string == rhs._string && this->_fs == rhs._fs) + return false; + return true; +} +bool string_and_font::operator> (const string_and_font& rhs) const{ + if(this->_string > rhs._string) + return true; + return false; +} +bool string_and_font::operator< (const string_and_font& rhs) const{ + if(this->_string < rhs._string) + return false; + return true; +} +bool string_and_font::operator>=(const string_and_font& rhs) const{ + return !(*this < rhs); +} +bool string_and_font::operator<=(const string_and_font& rhs) const{ + return !(*this > rhs); +} + +/////////////////////////////////////////////////////////////////////////////// +// SDL_Texture_Wrapper //////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +SDL_Texture_Wrapper::SDL_Texture_Wrapper(){ + _texture = nullptr; +} + +SDL_Texture_Wrapper::~SDL_Texture_Wrapper(){ + if(_texture != nullptr) + SDL_DestroyTexture(_texture); +} + +SDL_Texture_Wrapper::SDL_Texture_Wrapper(const std::string& text, const font_and_size& fs){ + SDL_Surface* tmpSurface; + + TTF_Font* fs_font = board::getFont(fs); + + tmpSurface = TTF_RenderText_Solid(fs_font, text.c_str(), + { BOARD_RED, BOARD_GREEN, BOARD_BLUE }); + + if(tmpSurface == NULL){ + SDL_Log("SDL_Texture_Wrapper: Failed to create surface from string (%s): %s\n", + text.c_str(), SDL_GetError()); + _texture = NULL; + return; + } + + _texture = SDL_CreateTextureFromSurface(board::getRenderer(), tmpSurface); + + if(_texture == NULL){ + SDL_Log("SDL_Texture_Wrapper: failed to create texture from surface of string (%s): %s\n", + text.c_str(), SDL_GetError()); + SDL_FreeSurface(tmpSurface); + return; + } + + //texture was created sucessfully, cleanup surface + + SDL_FreeSurface(tmpSurface); + SDL_UnlockTexture(_texture); +} + +SDL_Texture_Wrapper::SDL_Texture_Wrapper(const string_and_font& sf) + : SDL_Texture_Wrapper(sf._string, sf._fs) {}; + +SDL_Texture_Wrapper::SDL_Texture_Wrapper(const texture_path& path){ + SDL_Surface* tmpSurface; + + tmpSurface = IMG_Load(path.c_str()); + + if(tmpSurface == NULL){ + SDL_Log("SDL_Texture_Wrapper: failed to load image (%s): %s\n", + path.c_str(), SDL_GetError()); + return; + } + + _texture = SDL_CreateTextureFromSurface(board::getRenderer(), tmpSurface); + + if(_texture == NULL){ + SDL_Log("SDL_Texture_Wrapper: failed to create texture from surface of image (%s): %s\n", + path.c_str(), SDL_GetError()); + SDL_FreeSurface(tmpSurface); + return; + } + + //texture was created sucessfully, cleanup surface + + SDL_FreeSurface(tmpSurface); + SDL_UnlockTexture(_texture); +} + +SDL_Texture* SDL_Texture_Wrapper::texture() const { + return _texture; +} + +/////////////////////////////////////////////////////////////////////////////// +// SDL_Font_Wrapper /////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +SDL_Font_Wrapper::SDL_Font_Wrapper(){ + _font = nullptr; +} + +SDL_Font_Wrapper::~SDL_Font_Wrapper(){ + if(_font != nullptr) + TTF_CloseFont(_font); +} + +SDL_Font_Wrapper::SDL_Font_Wrapper(const font_path& fp, const size_t ptsize){ + _font = TTF_OpenFont(fp.c_str(), ptsize); + + if(_font == NULL){ + SDL_Log("SDL_Font_Wrapper: Failed to open font (%s) with ptsize (%lu): %s\n", + fp.c_str(), ptsize, SDL_GetError()); + return; + } +} + +SDL_Font_Wrapper::SDL_Font_Wrapper(const font_and_size& fs){ + _font = TTF_OpenFont(fs._font.c_str(), fs._ptsize); + + if(_font == NULL){ + SDL_Log("SDL_Font_Wrapper: Failed to open font (%s) with ptsize (%lu): %s\n", + fs._font.c_str(), fs._ptsize, SDL_GetError()); + return; + } +} + +TTF_Font* SDL_Font_Wrapper::font() const { + return _font; +} + +/////////////////////////////////////////////////////////////////////////////// +// Static functions /////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +SDL_Window* board::getWindow(){ + static SDL_Window* _window = nullptr; + + if(_window == nullptr){ + _window = SDL_CreateWindow(WINDOW_TITLE, + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + SCREEN_WIDTH, SCREEN_HEIGHT, + SDL_WINDOW_FLAGS); + + if(_window == NULL) + SDL_Log("Window could not be created, %s\n", SDL_GetError()); + } + return _window; +} + +SDL_Renderer* board::getRenderer(){ + static SDL_Renderer* _renderer = nullptr; + + if(_renderer == nullptr){ + _renderer = SDL_CreateRenderer(board::getWindow(), -1, + SDL_RENDERER_ACCELERATED); + + if(_renderer == NULL) + SDL_Log("Renderer could not be created, %s\n", SDL_GetError()); + } + + return _renderer; +} + +/////////////////////////////////////////////////////////////////////////////// +// Constructors /////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +board::board(const bool init = true){ + _window = nullptr; + _renderer = nullptr; + + if(init) + this->init(); +} + +board::~board(){ + if(_renderer != nullptr) + SDL_DestroyRenderer(_renderer); + + if(_window != nullptr) + SDL_DestroyWindow(_window); + + //allocated resources will be taken care of by the LRUCache + + TTF_Quit(); + IMG_Quit(); + SDL_Quit(); +} + +int board::init(){ + SDL_Log("Init SDL...\n"); + + //check if already has been init + if(_window != NULL || _renderer != NULL){ + SDL_Log("Failed to init SDL: Already init\n"); + return 1; + } + + //setup sdl + if(SDL_Init(SDL_FLAGS) != 0){ + SDL_Log("Failed to init SDL: %s\n", SDL_GetError()); + return -1; + } + + //create window + _window = board::getWindow(); + if(_window == NULL){ + SDL_Log("Failed to init SDL; Window could not be created: %s\n", + SDL_GetError()); + return -2; + } + + //create renderer + _renderer = board::getRenderer(); + if(_renderer == NULL){ + SDL_Log("Failed to init SDL; Renderer could not be created: %s\n", + SDL_GetError()); + return -3; + } + + //set cursor mode + SDL_ShowCursor(SDL_SHOW_CURSOR); + + + //enable alpha + SDL_SetRenderDrawBlendMode(_renderer, SDL_BLENDMODE_BLEND); + + //init sdl_image + if((IMG_Init(IMG_FLAGS) & IMG_FLAGS) != IMG_FLAGS){ + SDL_Log("Failed to init SDL; Failed to init SDL_Image: %s\n", + IMG_GetError()); + return -4; + } + + //init sdl_ttf + if(TTF_Init() == -1){ + SDL_Log("Failed to init SDL; Failed to init SDL_TTF: %s\n", + TTF_GetError()); + return -5; + } + + SDL_Log("Sucessfully setup SDL\n"); + return 0; +} + +/////////////////////////////////////// +// This is called to start the main loop. +// This is where most of the logic lives +void board::start(){ + + + + +} + +/////////////////////////////////////////////////////////////////////////////// +// Memory Functions /////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +SDL_Texture* board::getString(const std::string& text, const font_and_size& fs){ + //check for string in static strings, then check in dynamic strings + + string_and_font sf = {text, fs}; + if(_strings.find(sf) != _strings.end()) + return _strings.find(sf)->second.texture(); + + //is dynamic string, generate: + return _dynamic_strings.get(sf).texture(); +} + +SDL_Texture* board::getImage(const std::string& path){ + if(_textures.find(path) != _textures.end()) + return _textures.find(path)->second.texture(); + + //TODO: Dynamic Images? + //not found, return null + return nullptr; +} + +TTF_Font* board::getFont(const font_and_size& fs){ + if(_fonts.find(fs) != _fonts.end()) + return _fonts.find(fs)->second.font(); + + //Dynamic Fonts? Is the needed? + //not found, return null + return nullptr; +} + +SDL_Texture* board::setString(const std::string& text, const font_and_size& fs){ + string_and_font sf = {text, fs}; + _strings.insert_or_assign(sf, SDL_Texture_Wrapper(text, fs)); + return _strings[sf].texture(); +} + +SDL_Texture* board::setImage(const std::string& path){ + _textures.insert_or_assign(path, SDL_Texture_Wrapper(path)); + return _textures[path].texture(); +} + +TTF_Font* board::setFont(const font_and_size& fs){ + _fonts.insert_or_assign(fs, SDL_Font_Wrapper(fs)); + return _fonts[fs].font(); +} diff --git a/src/board.hpp b/src/board.hpp new file mode 100644 index 0000000..251ea0a --- /dev/null +++ b/src/board.hpp @@ -0,0 +1,135 @@ +/////////////////////////////////////////////////////////////////////////////// +// Tyler Perkins +// 8-23-21 +// Board defintions +// + +#pragma once + +#include "config.hpp" + +#include "util/lru.hpp" + +#include +#include + +#include +#include +#include + +namespace dashboard { + struct font_and_size { + std::string _font; + size_t _ptsize; + + bool operator==(const font_and_size&) const; + bool operator!=(const font_and_size&) const; + bool operator> (const font_and_size&) const; + bool operator< (const font_and_size&) const; + bool operator>=(const font_and_size&) const; + bool operator<=(const font_and_size&) const; + + }; + + struct string_and_font { + std::string _string; + font_and_size _fs; + + bool operator==(const string_and_font&) const; + bool operator!=(const string_and_font&) const; + bool operator> (const string_and_font&) const; + bool operator< (const string_and_font&) const; + bool operator>=(const string_and_font&) const; + bool operator<=(const string_and_font&) const; + }; + + struct font_and_size_hash { + std::size_t operator()(const font_and_size&) const; + }; + + struct string_and_font_hash { + std::size_t operator()(const string_and_font&) const; + }; + + typedef std::string texture_path; + class SDL_Texture_Wrapper { + public: + SDL_Texture_Wrapper(); + ~SDL_Texture_Wrapper(); + + SDL_Texture_Wrapper(const std::string&, const font_and_size&); + SDL_Texture_Wrapper(const string_and_font&); + + SDL_Texture_Wrapper(const texture_path&); + + SDL_Texture* texture() const; + + private: + SDL_Texture* _texture; + }; + + typedef std::string font_path; + class SDL_Font_Wrapper { + public: + SDL_Font_Wrapper(); + ~SDL_Font_Wrapper(); + + SDL_Font_Wrapper(const font_path&, const size_t); + SDL_Font_Wrapper(const font_and_size&); + + TTF_Font* font() const; + + private: + TTF_Font* _font; + }; + + + + class board { + public: + board(const bool = true); + ~board(); + + //sdl setup function + int init(); + + //start main loop + void start(); + + //globals + inline static SDL_Window* getWindow(); + inline static SDL_Renderer* getRenderer(); + + //memory functions (also globals) + //these are called by other objects to get their memory + inline static SDL_Texture* getString(const std::string&, const font_and_size&); + inline static SDL_Texture* getImage (const std::string&); + inline static TTF_Font* getFont (const font_and_size&); + + private: + //setup memory management with + //all const resrouces + void initConstResources(); + + //setup static memory. These are run in initConstResources() + SDL_Texture* setString(const std::string&, const font_and_size&); + SDL_Texture* setImage (const std::string&); + TTF_Font* setFont (const font_and_size&); + + //containers for resources + inline static std::unordered_map _textures; + inline static std::unordered_map _fonts; + inline static std::unordered_map _strings; + + static clortox::LRUCache _dynamic_strings; + + //TODO: Dynamic images? + + //local pointers to the globals + SDL_Window* _window; + SDL_Renderer* _renderer; + }; +} diff --git a/src/main.cpp b/src/main.cpp index d71214d..b05b4fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,11 +6,13 @@ #include "config.hpp" #include "handler/handler.hpp" - +#include "board.hpp" int main(int argc, char** argv){ dashboard::handlers::setHandlers(); + dashboard::board _board; + return 0; } diff --git a/src/pannel/pannel.cpp b/src/pannel/pannel.cpp new file mode 100644 index 0000000..bf8fa69 --- /dev/null +++ b/src/pannel/pannel.cpp @@ -0,0 +1,7 @@ +/////////////////////////////////////////////////////////////////////////////// +// Tyler Perkins +// 8-23-21 +// pannel implementation +// + +#include "pannel.hpp" diff --git a/src/pannel/pannel.hpp b/src/pannel/pannel.hpp new file mode 100644 index 0000000..20e0805 --- /dev/null +++ b/src/pannel/pannel.hpp @@ -0,0 +1,30 @@ +/////////////////////////////////////////////////////////////////////////////// +// Tyler Perkins +// 8-23-21 +// pannel definition +// +/////////////////////////////////////// +// Note: +// This class is a pure virtual class. board has an array of these, and +// each of them is a different page that will be shown. Whatever pannel is +// set as pannel_border will not check its own time elapsed, and will be +// displayed on top of all other pannels + + +#pragma once + +#include +#include +#include + +namespace dashboard { + class pannel { + public: + pannel() = default; + ~pannel() = default; + + virtual void draw() = 0; + + size_t _time_on_screen = 0; + }; +} diff --git a/src/util/lru.hpp b/src/util/lru.hpp new file mode 100644 index 0000000..aa4c1df --- /dev/null +++ b/src/util/lru.hpp @@ -0,0 +1,272 @@ +/////////////////////////////////////////////////////////////////////////////// +// Tyler Perkins +// 8-23-21 +// LRU cache implementation +// + +#ifndef _LRUCACHE_HPP_ +#define _LRUCACHE_HPP_ + +#include +#include +#include + +#include + +namespace clortox { + constexpr size_t __default_max_cache_size = 50; + + + template + struct value_iterator { + typename std::list::iterator it; + T value; + }; + + template> + class LRUCache{ + public: + LRUCache(); + LRUCache(size_t); + ~LRUCache(); + LRUCache(const LRUCache&); + LRUCache& operator=(const LRUCache&); + + void swap(LRUCache&); + + //utility + void clear (); + inline bool empty () const; + inline bool full () const; + inline size_t getMaxCacheSize() const; + + //accessors functions + T& get (const Key&); + inline T& operator[](const Key&); + inline T& operator[](Key&); + void put (const Key&, const T&); + bool erase (const Key&); + void pop_back (); + +#ifdef _LRU_DEBUG_ + bool wasHit = false; +#endif + + private: + //list + std::list _list; + //hashmap + std::unordered_map, Hash> _hash_map; + + size_t _max_cache_size; + }; +} + +/////////////////////////////////////////////////////////////////////////////// +// Implementation ///////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////// +// Default Ctor +template +clortox::LRUCache::LRUCache() + : LRUCache(__default_max_cache_size) {}; + +template +clortox::LRUCache::LRUCache( + size_t max_size) : _max_cache_size(max_size){ + + _hash_map.reserve(max_size); +} + +/////////////////////////////////////// +// Dector +template +clortox::LRUCache::~LRUCache(){ + while(!empty()){ + pop_back(); + } +} + +/////////////////////////////////////// +// Copy ctor +template +clortox::LRUCache::LRUCache(const LRUCache& rhs){ + _max_cache_size = rhs._max_cache_size; + _hash_map = rhs._hash_map; + _list = rhs._list; +} + +/////////////////////////////////////// +// Assignment operator +template +clortox::LRUCache& clortox::LRUCache< + Key, T, Hash>::operator=(const LRUCache& rhs){ + + if(this != &rhs){ + _max_cache_size = rhs._max_cache_size; + _hash_map = rhs._hash_map; + _list = rhs._list; + } + return *this; +} + +/////////////////////////////////////// +// Swap function +template +void clortox::LRUCache::swap(LRUCache& rhs){ + decltype(_hash_map) tmphashmap = rhs._hash_map; + rhs._hash_map = _hash_map; + _hash_map = tmphashmap; + + decltype(_list) tmplist = rhs._list; + rhs._list = _list; + _list = tmplist; + + decltype(_max_cache_size) tmpmax = rhs._max_cache_size; + rhs._max_cache_size = _max_cache_size; + _max_cache_size = tmpmax; +} + +/////////////////////////////////////// +// Clears out all elements from cache +// This should only be called if all +// elements in the cache are not likely +// to be used again +template +void clortox::LRUCache::clear(){ + for(auto it = _hash_map.begin(); it != _hash_map.end();){ + _list.erase(it->second.it); + it = _hash_map.erase(it); + } +} + +/////////////////////////////////////// +// Returns true if the cache is empty +template +bool clortox::LRUCache::empty() const{ + return _list.empty(); +} + +/////////////////////////////////////// +// Check if cache is full +template +bool clortox::LRUCache::full() const{ + return _list.size() == _max_cache_size; +} + + +/////////////////////////////////////// +// Returns the max cache size +template +size_t clortox::LRUCache::getMaxCacheSize() const{ + return _max_cache_size; +} + +/////////////////////////////////////////////////////////////////////////////// +// Accessor functions ///////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////// +// get element from cache. If it does +// not exist, create it +template +T& clortox::LRUCache::get(const Key& key){ + if(_hash_map.find(key) != _hash_map.end()){ //cache hit +#ifdef _LRU_DEBUG_ + wasHit = true; +#endif + //update list + _list.erase(_hash_map[key].it); + _list.push_front(key); + _hash_map[key].it = _list.begin(); + + return _hash_map[key].value; + } else { //cache miss +#ifdef _LRU_DEBUG_ + wasHit = false; +#endif + //if cache is full, remove oldest element + if(_list.size() == _max_cache_size){ + _hash_map.erase(_list.back()); + _list.pop_back(); + } + + //add new element + _list.push_front(key); + + _hash_map[key] = { .it = _list.begin(), .value = T(key) }; + return _hash_map[key].value; + } +} + +/////////////////////////////////////// +// Index operator returns value with +// assoc key. If it does not exist, we +// create it +template +T& clortox::LRUCache::operator[](const Key& key){ + return get(key); +} +template +T& clortox::LRUCache::operator[](Key& key){ + return get(key); +} + +/////////////////////////////////////// +// Puts the key value pair into the +// cache +template +void clortox::LRUCache::put(const Key& key, const T& value){ + if(_hash_map.find(key) != _hash_map.end()){ //cache hit +#ifdef _LRU_DEBUG_ + wasHit = true; +#endif + //update list + _list.erase(_hash_map[key].it); + _list.push_front(key); + _hash_map[key].it = _list.begin(); + + _hash_map[key].value = value; + } else { //cache hit, insert new element +#ifdef _LRU_DEBUG_ + wasHit = false; +#endif + //if cache is full, remove oldest element + if(_list.size() == _max_cache_size){ + _hash_map.erase(_list.back()); + _list.pop_back(); + } + + //add new element + _list.push_front(key); + _hash_map[key] = { .it = _list.begin(), .value = value }; + } +} + +/////////////////////////////////////// +// Remove element from cache. Only do +// this if positive it will not be +// referenced again +template +bool clortox::LRUCache::erase(const Key& key){ + if(_hash_map.find(key) != _hash_map.end()){ //cache hit + //remove element + _list.erase(_hash_map[key].it); + _hash_map.erase(key); + return true; + } //not in cache, return false + return false; +} + +/////////////////////////////////////// +// Remove oldest element +template +void clortox::LRUCache::pop_back(){ + if(!_list.empty()){ + _hash_map.erase(_list.back()); + _list.pop_back(); + } +} + +#endif //!_LRUCACHE_HPP_