diff options
41 files changed, 768 insertions, 183 deletions
diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 5b380e1..977c0ca 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,5 +1,6 @@ { "recommendations": [ + "llvm-vs-code-extensions.vscode-clangd", "EditorConfig.EditorConfig", "ms-vscode.cmake-tools" ] diff --git a/contributing.md b/contributing.md index eec5bc0..9f87ec5 100644 --- a/contributing.md +++ b/contributing.md @@ -22,6 +22,35 @@ - When using libraries of which the header include order is important, make sure to separate the include statements using a blank line (clang-format may sort include statements, but does not sort across empty lines). +- All engine-related code is implemented under the `crepe` namespace, + user-facing APIs under `crepe::api` (the folder structure should also reflect + this). +- `using namespace` may not be used in header files, only in source files. +- Do not (indirectly) include private dependency headers in API header files, + as these are no longer accessible when the engine is installed +- Getter and setter functions are appropriately prefixed with `get_` and + `set_`. +- Doxygen commands are used with a backslash instead of an at-sign (i.e. + `\brief` instead of `@brief`) +- A singleton's instance is always accessed using a getter function that + instantiates its own class as a static variable within the getter function + scope, instead of storing the instance as a member variable directly: + + ```cpp + class Bad { + static Bad instance; + Bad & get_instance() { return instance; } + }; + + class Good { + Good & get_instance() { + static Good instance; + return instance; + } + }; + ``` +- Member variable default values should be directly defined in the class + declaration instead of using the constructor. ## CMakeLists specific diff --git a/lib/soloud/CMakeLists.txt b/lib/soloud/CMakeLists.txt index 8433281..aaccd96 100644 --- a/lib/soloud/CMakeLists.txt +++ b/lib/soloud/CMakeLists.txt @@ -9,7 +9,7 @@ add_subdirectory(../sdl2 sdl2) project(soloud C CXX) -add_library(soloud STATIC +add_library(soloud SHARED src/src/audiosource/ay/chipplayer.cpp src/src/audiosource/ay/sndbuffer.cpp src/src/audiosource/ay/sndchip.cpp @@ -6,6 +6,5 @@ doxygen: Doxyfile FORCE FMT += $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') format: FORCE clang-format -i $(FMT) -# clang tidy doesn't work that well :/ -# clang-tidy --fix-errors $(FMT) + $(MAKE) -C src $@ diff --git a/mwe/audio/soloud/main.cpp b/mwe/audio/soloud/main.cpp index 25ba003..50df0b7 100644 --- a/mwe/audio/soloud/main.cpp +++ b/mwe/audio/soloud/main.cpp @@ -38,8 +38,7 @@ int main() { this_thread::sleep_for(500ms); // play all samples simultaniously - for (unsigned i = 0; i < 3; i++) - soloud.play(sfx[i]); + for (unsigned i = 0; i < 3; i++) soloud.play(sfx[i]); this_thread::sleep_for(1000ms); // stop all audio and exit diff --git a/mwe/dynlink/exec/CMakeLists.txt b/mwe/dynlink/exec/CMakeLists.txt index 5335f0f..465b45e 100644 --- a/mwe/dynlink/exec/CMakeLists.txt +++ b/mwe/dynlink/exec/CMakeLists.txt @@ -5,12 +5,20 @@ set(CMAKE_CXX_STANDARD 20) project(main C CXX) +add_executable(main main.c) + # Since we have the source code for the test library, we might as well let # CMake use it so it automatically picks up the interface headers and compiles # the library for us. The same can be achieved manually, but is more code. add_subdirectory(../lib test) -add_executable(main main.c) +# include(ExternalProject) +# ExternalProject_Add(test_ext +# SOURCE_DIR ../../lib +# CMAKE_ARGS -DCMAKE_INSTALL_PREFIX="${CMAKE_BINARY_DIR}/ext" +# ) +# add_dependencies(main test_ext) +# find_package(test REQUIRED) # Make sure ld.so (linux) looks in the same folder as the final executable for # the .so dependency diff --git a/mwe/dynlink/exec/main.c b/mwe/dynlink/exec/main.c index 2bbdc20..ff884f2 100644 --- a/mwe/dynlink/exec/main.c +++ b/mwe/dynlink/exec/main.c @@ -1,4 +1,4 @@ -#include <lib.h> +#include <test.h> int main() { library_function(); diff --git a/mwe/dynlink/lib/CMakeLists.txt b/mwe/dynlink/lib/CMakeLists.txt index 2d15353..7efc91a 100644 --- a/mwe/dynlink/lib/CMakeLists.txt +++ b/mwe/dynlink/lib/CMakeLists.txt @@ -5,6 +5,20 @@ set(CMAKE_CXX_STANDARD 20) project(lib C CXX) -add_library(test SHARED lib.c) +add_library(test SHARED) + target_include_directories(test SYSTEM INTERFACE .) +target_sources(test PUBLIC + test.c +) + +target_sources(test PUBLIC FILE_SET HEADERS FILES + test.h +) + +install( + TARGETS test + FILE_SET HEADERS DESTINATION include +) + diff --git a/mwe/dynlink/lib/lib.c b/mwe/dynlink/lib/test.c index c7a78e4..c7a78e4 100644 --- a/mwe/dynlink/lib/lib.c +++ b/mwe/dynlink/lib/test.c diff --git a/mwe/dynlink/lib/lib.h b/mwe/dynlink/lib/test.h index 093eadb..093eadb 100644 --- a/mwe/dynlink/lib/lib.h +++ b/mwe/dynlink/lib/test.h diff --git a/mwe/gameloop/include/gameObject.h b/mwe/gameloop/include/gameObject.h index fc7d026..abdc9b0 100644 --- a/mwe/gameloop/include/gameObject.h +++ b/mwe/gameloop/include/gameObject.h @@ -1,30 +1,32 @@ #pragma once #include <iostream> class GameObject { - public: - GameObject(); - GameObject(std::string name, float x, float y, float width, float height, float velX, float velY); - std::string getName() const; - float getX() const; - float getY() const; - float getWidth() const; - float getHeight() const; - float getVelX() const; - float getVelY() const; - void setName(std::string value); - void setX(float value); - void setY(float value); - void setWidth(float value); - void setHeight(float value); - void setVelX(float value); - void setVelY(float value); - int direction; - private: - std::string name = ""; - float x = 0; - float y = 0; - float width = 0; - float height = 0; - float velX = 0; - float velY = 0; +public: + GameObject(); + GameObject(std::string name, float x, float y, float width, float height, + float velX, float velY); + std::string getName() const; + float getX() const; + float getY() const; + float getWidth() const; + float getHeight() const; + float getVelX() const; + float getVelY() const; + void setName(std::string value); + void setX(float value); + void setY(float value); + void setWidth(float value); + void setHeight(float value); + void setVelX(float value); + void setVelY(float value); + int direction; + +private: + std::string name = ""; + float x = 0; + float y = 0; + float width = 0; + float height = 0; + float velX = 0; + float velY = 0; }; diff --git a/mwe/gameloop/include/timer.h b/mwe/gameloop/include/timer.h index ca1e5f3..8273746 100644 --- a/mwe/gameloop/include/timer.h +++ b/mwe/gameloop/include/timer.h @@ -4,29 +4,30 @@ class LoopTimer { public: - static LoopTimer& getInstance(); - void start(); - void update(); - double getDeltaTime() const; - int getCurrentTime() const; + static LoopTimer & getInstance(); + void start(); + void update(); + double getDeltaTime() const; + int getCurrentTime() const; void advanceFixedUpdate(); double getFixedDeltaTime() const; - void setFPS(int FPS); - int getFPS() const; + void setFPS(int FPS); + int getFPS() const; double getGameScale(); void setGameScale(double); - void enforceFrameRate(); + void enforceFrameRate(); double getLag() const; + private: - LoopTimer(); + LoopTimer(); int FPS = 50; double gameScale = 1; double maximumDeltaTime = 0.25; - double deltaTime; + double deltaTime; double frameTargetTime = FPS / 1000; - double fixedDeltaTime = 0.01; - double elapsedTime; - double elapsedFixedTime; + double fixedDeltaTime = 0.01; + double elapsedTime; + double elapsedFixedTime; double time; - uint64_t lastFrameTime; + uint64_t lastFrameTime; }; diff --git a/mwe/gameloop/src/loopManager.cpp b/mwe/gameloop/src/loopManager.cpp index dde3cef..0392853 100644 --- a/mwe/gameloop/src/loopManager.cpp +++ b/mwe/gameloop/src/loopManager.cpp @@ -1,92 +1,89 @@ #include "loopManager.h" #include "timer.h" -LoopManager::LoopManager(){ -} -void LoopManager::processInput(){ +LoopManager::LoopManager() {} +void LoopManager::processInput() { SDL_Event event; SDL_PollEvent(&event); - switch(event.type){ + switch (event.type) { case SDL_QUIT: gameRunning = false; break; case SDL_KEYDOWN: - if(event.key.keysym.sym == SDLK_ESCAPE){ + if (event.key.keysym.sym == SDLK_ESCAPE) { gameRunning = false; - }else if(event.key.keysym.sym == SDLK_i){ - LoopTimer::getInstance().setGameScale(LoopTimer::getInstance().getGameScale() + 0.1); - }else if(event.key.keysym.sym == SDLK_k){ - LoopTimer::getInstance().setGameScale(LoopTimer::getInstance().getGameScale() - 0.1); + } else if (event.key.keysym.sym == SDLK_i) { + LoopTimer::getInstance().setGameScale( + LoopTimer::getInstance().getGameScale() + 0.1); + } else if (event.key.keysym.sym == SDLK_k) { + LoopTimer::getInstance().setGameScale( + LoopTimer::getInstance().getGameScale() - 0.1); } - - break; + + break; } } -void LoopManager::fixedUpdate(){ - fprintf(stderr,"fixed update\n"); -} +void LoopManager::fixedUpdate() { fprintf(stderr, "fixed update\n"); } void LoopManager::loop() { - LoopTimer& timer = LoopTimer::getInstance(); - timer.start(); + LoopTimer & timer = LoopTimer::getInstance(); + timer.start(); - while (gameRunning) { - timer.update(); + while (gameRunning) { + timer.update(); - while (timer.getLag() >= timer.getFixedDeltaTime()) { - processInput(); - fixedUpdate(); - timer.advanceFixedUpdate(); - } + while (timer.getLag() >= timer.getFixedDeltaTime()) { + processInput(); + fixedUpdate(); + timer.advanceFixedUpdate(); + } - update(); - render(); + update(); + render(); - timer.enforceFrameRate(); - } + timer.enforceFrameRate(); + } - window.destroyWindow(); + window.destroyWindow(); } -void LoopManager::setup(){ +void LoopManager::setup() { gameRunning = window.initWindow(); LoopTimer::getInstance().start(); LoopTimer::getInstance().setFPS(500); - - for(int i = 1; i < 3;i++){ - GameObject* square = new GameObject("square2",i*60,i*60,20,20,0,0); + + for (int i = 1; i < 3; i++) { + GameObject * square + = new GameObject("square2", i * 60, i * 60, 20, 20, 0, 0); objectList.push_back(square); } } -void LoopManager::render(){ - fprintf(stderr,"**********render********** \n"); - if(gameRunning){ +void LoopManager::render() { + fprintf(stderr, "**********render********** \n"); + if (gameRunning) { window.render(objectList); } } void LoopManager::update() { - fprintf(stderr, "********** normal update ********** \n"); - LoopTimer& timer = LoopTimer::getInstance(); - - float delta = timer.getDeltaTime(); - for (int i = 0; i < objectList.size(); i++) { - GameObject* obj = objectList[i]; - - // Move the object based on its direction - if (obj->direction == 1) { - obj->setX(obj->getX() + 50 * delta); - } else { - obj->setX(obj->getX() - 50 * delta); - } - + fprintf(stderr, "********** normal update ********** \n"); + LoopTimer & timer = LoopTimer::getInstance(); - if (obj->getX() > 500) { - obj->setX(500); - obj->direction = 0; // Switch direction to left - } else if (obj->getX() < 50) { - obj->setX(50); // Clamp the position to the boundary - obj->direction = 1; // Switch direction to right - } - } -} + float delta = timer.getDeltaTime(); + for (int i = 0; i < objectList.size(); i++) { + GameObject * obj = objectList[i]; + // Move the object based on its direction + if (obj->direction == 1) { + obj->setX(obj->getX() + 50 * delta); + } else { + obj->setX(obj->getX() - 50 * delta); + } + if (obj->getX() > 500) { + obj->setX(500); + obj->direction = 0; // Switch direction to left + } else if (obj->getX() < 50) { + obj->setX(50); // Clamp the position to the boundary + obj->direction = 1; // Switch direction to right + } + } +} diff --git a/mwe/gameloop/src/main.cpp b/mwe/gameloop/src/main.cpp index 9f407c4..c0f216a 100644 --- a/mwe/gameloop/src/main.cpp +++ b/mwe/gameloop/src/main.cpp @@ -5,7 +5,6 @@ #include "loopManager.h" #include "timer.h" - int main(int argc, char * args[]) { LoopManager gameLoop; gameLoop.setup(); diff --git a/mwe/gameloop/src/timer.cpp b/mwe/gameloop/src/timer.cpp index 03fb9eb..97baef6 100644 --- a/mwe/gameloop/src/timer.cpp +++ b/mwe/gameloop/src/timer.cpp @@ -1,68 +1,53 @@ #include "timer.h" -LoopTimer::LoopTimer(){} +LoopTimer::LoopTimer() {} -LoopTimer& LoopTimer::getInstance() { - static LoopTimer instance; - return instance; +LoopTimer & LoopTimer::getInstance() { + static LoopTimer instance; + return instance; } void LoopTimer::start() { - lastFrameTime = SDL_GetTicks64(); - elapsedTime = 0; - elapsedFixedTime = 0; - deltaTime = 0; + lastFrameTime = SDL_GetTicks64(); + elapsedTime = 0; + elapsedFixedTime = 0; + deltaTime = 0; } void LoopTimer::update() { - uint64_t currentFrameTime = SDL_GetTicks64(); - deltaTime = (currentFrameTime - lastFrameTime) / 1000.0; + uint64_t currentFrameTime = SDL_GetTicks64(); + deltaTime = (currentFrameTime - lastFrameTime) / 1000.0; - if (deltaTime > maximumDeltaTime) { - deltaTime = maximumDeltaTime; - } + if (deltaTime > maximumDeltaTime) { + deltaTime = maximumDeltaTime; + } deltaTime *= gameScale; - elapsedTime += deltaTime; - lastFrameTime = currentFrameTime; + elapsedTime += deltaTime; + lastFrameTime = currentFrameTime; } -double LoopTimer::getDeltaTime() const { - return deltaTime; -} -int LoopTimer::getCurrentTime() const { - return SDL_GetTicks(); -} +double LoopTimer::getDeltaTime() const { return deltaTime; } +int LoopTimer::getCurrentTime() const { return SDL_GetTicks(); } -void LoopTimer::advanceFixedUpdate() { - elapsedFixedTime += fixedDeltaTime; -} +void LoopTimer::advanceFixedUpdate() { elapsedFixedTime += fixedDeltaTime; } -double LoopTimer::getFixedDeltaTime() const { - return fixedDeltaTime; -} +double LoopTimer::getFixedDeltaTime() const { return fixedDeltaTime; } void LoopTimer::setFPS(int FPS) { - this->FPS = FPS; - frameTargetTime = 1.0 / FPS; + this->FPS = FPS; + frameTargetTime = 1.0 / FPS; } -int LoopTimer::getFPS() const { - return FPS; -} -void LoopTimer::setGameScale(double value){ - gameScale = value; -}; -double LoopTimer::getGameScale(){ - return gameScale; -} +int LoopTimer::getFPS() const { return FPS; } +void LoopTimer::setGameScale(double value) { gameScale = value; }; +double LoopTimer::getGameScale() { return gameScale; } void LoopTimer::enforceFrameRate() { - uint64_t currentFrameTime = SDL_GetTicks64(); - double frameDuration = (currentFrameTime - lastFrameTime) / 1000.0; + uint64_t currentFrameTime = SDL_GetTicks64(); + double frameDuration = (currentFrameTime - lastFrameTime) / 1000.0; - if (frameDuration < frameTargetTime) { - uint32_t delayTime = (uint32_t)((frameTargetTime - frameDuration) * 1000.0); - SDL_Delay(delayTime); - } -} -double LoopTimer::getLag() const { - return elapsedTime - elapsedFixedTime; + if (frameDuration < frameTargetTime) { + uint32_t delayTime + = (uint32_t) ((frameTargetTime - frameDuration) * 1000.0); + SDL_Delay(delayTime); + } } +double LoopTimer::getLag() const { return elapsedTime - elapsedFixedTime; } @@ -7,7 +7,8 @@ This repository contains: |`lib/`|third-party libraries as git submodules| |`mwe/`|minimal working examples and proof-of-concepts| |`src/crepe/`|game engine source code| -|`test/`|game engine unit tests| +|`src/test/`|unit tests| +|`src/example`|standalone examples using game engine| ## Compilation diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b8ed359..f11402a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,13 +7,35 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1) set(CMAKE_BUILD_TYPE Debug) add_compile_definitions(DEBUG) +add_subdirectory(../lib/soloud soloud) +add_subdirectory(../lib/googletest googletest) + project(crepe C CXX) -find_package(SDL2 REQUIRED) -include_directories(${SDL2_INCLUDE_DIRS}) +add_library(crepe SHARED) +add_executable(test_main EXCLUDE_FROM_ALL) + +target_include_directories(crepe + PUBLIC SYSTEM INTERFACE . +) + +# TODO: libraries should be linked as PRIVATE +target_link_libraries(crepe + PUBLIC soloud +) + +add_subdirectory(crepe) +add_subdirectory(test) +add_subdirectory(example) -add_executable(main) +install( + TARGETS crepe + FILE_SET HEADERS DESTINATION include/crepe +) -target_link_libraries(main ${SDL2_LIBRARIES}) +target_link_libraries(test_main + PRIVATE gtest_main + PUBLIC crepe +) add_subdirectory(crepe)
\ No newline at end of file diff --git a/src/crepe/Asset.cpp b/src/crepe/Asset.cpp new file mode 100644 index 0000000..15ddc27 --- /dev/null +++ b/src/crepe/Asset.cpp @@ -0,0 +1,14 @@ +#include <filesystem> + +#include "Asset.h" + +using namespace crepe; + +Asset::Asset(const std::string & src) { + this->src = std::filesystem::canonical(src); + this->file = std::ifstream(this->src, std::ios::in | std::ios::binary); +} + +const std::istream & Asset::read() { return this->file; } + +const char * Asset::canonical() { return this->src.c_str(); } diff --git a/src/crepe/Asset.h b/src/crepe/Asset.h new file mode 100644 index 0000000..0cb5834 --- /dev/null +++ b/src/crepe/Asset.h @@ -0,0 +1,33 @@ +#pragma once + +#include <fstream> +#include <iostream> +#include <string> + +namespace crepe { + +/** + * \brief Asset location helper + * + * This class is used to locate and canonicalize paths to game asset files, and + * should *always* be used when retrieving files from disk. + */ +class Asset { +public: + /** + * \param src Unique identifier to asset + */ + Asset(const std::string & src); + +public: + //! Get an input stream to the contents of this resource + const std::istream & read(); + //! Get the canonical path to this resource + const char * canonical(); + +private: + std::string src; + std::ifstream file; +}; + +} // namespace crepe diff --git a/src/crepe/CMakeLists.txt b/src/crepe/CMakeLists.txt index 43a3835..c7f18cd 100644 --- a/src/crepe/CMakeLists.txt +++ b/src/crepe/CMakeLists.txt @@ -1,9 +1,25 @@ -target_sources(main PUBLIC - main.cpp + +target_sources(crepe PUBLIC + Asset.cpp + Sound.cpp + SoundContext.cpp + main.cpp Particle.cpp ParticleEmitter.cpp ParticleSystem.cpp SDLApp.cpp ) -target_include_directories(main PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) +target_sources(crepe PUBLIC FILE_SET HEADERS FILES + Asset.h + Sound.h + SoundContext.h + ParticleEmitter.hpp + ParticleSystem.hpp + Particle.hpp + SDLApp.hpp +) + +add_subdirectory(api) +add_subdirectory(util) + diff --git a/src/crepe/Sound.cpp b/src/crepe/Sound.cpp new file mode 100644 index 0000000..64fa281 --- /dev/null +++ b/src/crepe/Sound.cpp @@ -0,0 +1,60 @@ +#include "util/log.h" + +#include "Sound.h" +#include "SoundContext.h" + +using namespace crepe; + +Sound::Sound(std::unique_ptr<Asset> res) { + dbg_trace(); + this->load(std::move(res)); +} + +Sound::Sound(const char * src) { + dbg_trace(); + this->load(std::make_unique<Asset>(src)); +} + +void Sound::load(std::unique_ptr<Asset> res) { + this->sample.load(res->canonical()); +} + +void Sound::play() { + SoundContext & ctx = SoundContext::get_instance(); + if (ctx.engine.getPause(this->handle)) { + // resume if paused + ctx.engine.setPause(this->handle, false); + } else { + // or start new sound + this->handle = ctx.engine.play(this->sample, this->volume); + ctx.engine.setLooping(this->handle, this->looping); + } +} + +void Sound::pause() { + SoundContext & ctx = SoundContext::get_instance(); + if (ctx.engine.getPause(this->handle)) return; + ctx.engine.setPause(this->handle, true); +} + +void Sound::rewind() { + SoundContext & ctx = SoundContext::get_instance(); + if (!ctx.engine.isValidVoiceHandle(this->handle)) return; + ctx.engine.seek(this->handle, 0); +} + +void Sound::set_volume(float volume) { + this->volume = volume; + + SoundContext & ctx = SoundContext::get_instance(); + if (!ctx.engine.isValidVoiceHandle(this->handle)) return; + ctx.engine.setVolume(this->handle, this->volume); +} + +void Sound::set_looping(bool looping) { + this->looping = looping; + + SoundContext & ctx = SoundContext::get_instance(); + if (!ctx.engine.isValidVoiceHandle(this->handle)) return; + ctx.engine.setLooping(this->handle, this->looping); +} diff --git a/src/crepe/Sound.h b/src/crepe/Sound.h new file mode 100644 index 0000000..1ac20a7 --- /dev/null +++ b/src/crepe/Sound.h @@ -0,0 +1,82 @@ +#pragma once + +#include <soloud.h> +#include <soloud_wav.h> + +#include <memory> + +#include "Asset.h" + +namespace crepe { + +class Sound { +public: + /** + * \brief Pause this sample + * + * Pauses this sound if it is playing, or does nothing if it is already + * paused. The playhead position is saved, such that calling \c play() after + * this function makes the sound resume. + */ + void pause(); + /** + * \brief Play this sample + * + * Resume playback if this sound is paused, or start from the beginning of + * the sample. + * + * \note This class only saves a reference to the most recent 'voice' of this + * sound. Calling \c play() while the sound is already playing causes + * multiple instances of the sample to play simultaniously. The sample + * started last is the one that is controlled afterwards. + */ + void play(); + /** + * \brief Reset playhead position + * + * Resets the playhead position so that calling \c play() after this function + * makes it play from the start of the sample. If the sound is not paused + * before calling this function, this function will stop playback. + */ + void rewind(); + /** + * \brief Set playback volume / gain + * + * \param volume Volume (0 = muted, 1 = full volume) + */ + void set_volume(float volume); + /** + * \brief Get playback volume / gain + * + * \return Volume + */ + float get_volume() const { return this->volume; } + /** + * \brief Set looping behavior for this sample + * + * \param looping Looping behavior (false = one-shot, true = loop) + */ + void set_looping(bool looping); + /** + * \brief Get looping behavior + * + * \return true if looping, false if one-shot + */ + bool get_looping() const { return this->looping; } + +public: + Sound(const char * src); + Sound(std::unique_ptr<Asset> res); + +private: + void load(std::unique_ptr<Asset> res); + +private: + SoLoud::Wav sample; + SoLoud::handle handle; + + float volume = 1.0f; + bool looping = false; +}; + +} // namespace crepe diff --git a/src/crepe/SoundContext.cpp b/src/crepe/SoundContext.cpp new file mode 100644 index 0000000..72047d2 --- /dev/null +++ b/src/crepe/SoundContext.cpp @@ -0,0 +1,20 @@ +#include "util/log.h" + +#include "SoundContext.h" + +using namespace crepe; + +SoundContext & SoundContext::get_instance() { + static SoundContext instance; + return instance; +} + +SoundContext::SoundContext() { + dbg_trace(); + engine.init(); +} + +SoundContext::~SoundContext() { + dbg_trace(); + engine.deinit(); +} diff --git a/src/crepe/SoundContext.h b/src/crepe/SoundContext.h new file mode 100644 index 0000000..090966d --- /dev/null +++ b/src/crepe/SoundContext.h @@ -0,0 +1,26 @@ +#pragma once + +#include <soloud.h> + +#include "Sound.h" + +namespace crepe { + +class SoundContext { +private: + SoundContext(); + virtual ~SoundContext(); + + // singleton + static SoundContext & get_instance(); + SoundContext(const SoundContext &) = delete; + SoundContext(SoundContext &&) = delete; + SoundContext & operator=(const SoundContext &) = delete; + SoundContext & operator=(SoundContext &&) = delete; + +private: + SoLoud::Soloud engine; + friend class Sound; +}; + +} // namespace crepe diff --git a/src/crepe/api/AudioSource.cpp b/src/crepe/api/AudioSource.cpp new file mode 100644 index 0000000..b512d27 --- /dev/null +++ b/src/crepe/api/AudioSource.cpp @@ -0,0 +1,22 @@ +#include "AudioSource.h" + +#include "../Sound.h" +#include <memory> + +using namespace crepe::api; + +AudioSource::AudioSource(std::unique_ptr<Asset> audio_clip) { + this->sound = std::make_unique<crepe::Sound>(std::move(audio_clip)); +} + +void AudioSource::play() { return this->play(false); } + +void AudioSource::play(bool looping) { + this->sound->set_looping(looping); + this->sound->play(); +} + +void AudioSource::stop() { + this->sound->pause(); + this->sound->rewind(); +} diff --git a/src/crepe/api/AudioSource.h b/src/crepe/api/AudioSource.h new file mode 100644 index 0000000..2d26cda --- /dev/null +++ b/src/crepe/api/AudioSource.h @@ -0,0 +1,41 @@ +#pragma once + +#include <memory> + +#include "Asset.h" +#include "Component.h" + +namespace crepe { +class Sound; +} + +namespace crepe::api { + +//! Audio source component +class AudioSource : Component { +public: + AudioSource(std::unique_ptr<Asset> audio_clip); + virtual ~AudioSource() = default; + +public: + //! Start or resume this audio source + void play(); + void play(bool looping); + //! Stop this audio source + void stop(); + +public: + //! Sample file location + std::unique_ptr<Asset> audio_clip; + //! TODO: ????? + bool play_on_awake; + //! Repeat the current audio clip during playback + bool loop; + //! Normalized volume (0.0 - 1.0) + float volume; + +private: + std::unique_ptr<crepe::Sound> sound; +}; + +} // namespace crepe::api diff --git a/src/crepe/api/CMakeLists.txt b/src/crepe/api/CMakeLists.txt new file mode 100644 index 0000000..9548594 --- /dev/null +++ b/src/crepe/api/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources(crepe PUBLIC + # AudioSource.cpp +) + +target_sources(crepe PUBLIC FILE_SET HEADERS FILES + AudioSource.h + Component.h +) + diff --git a/src/crepe/api/Component.h b/src/crepe/api/Component.h new file mode 100644 index 0000000..d5e0499 --- /dev/null +++ b/src/crepe/api/Component.h @@ -0,0 +1,10 @@ +#pragma once + +namespace crepe::api { + +class Component { +public: + bool active; +}; + +} // namespace crepe::api diff --git a/src/crepe/util/CMakeLists.txt b/src/crepe/util/CMakeLists.txt new file mode 100644 index 0000000..100f028 --- /dev/null +++ b/src/crepe/util/CMakeLists.txt @@ -0,0 +1,9 @@ +target_sources(crepe PUBLIC + log.cpp +) + +target_sources(crepe PUBLIC FILE_SET HEADERS FILES + color.h + log.h +) + diff --git a/src/crepe/util/color.h b/src/crepe/util/color.h new file mode 100644 index 0000000..066c9d3 --- /dev/null +++ b/src/crepe/util/color.h @@ -0,0 +1,41 @@ +#pragma once + +namespace crepe::util::color { + +constexpr const char * RESET = "\e[0m"; + +constexpr const char * FG_BLACK = "\e[30m"; +constexpr const char * FG_RED = "\e[31m"; +constexpr const char * FG_GREEN = "\e[32m"; +constexpr const char * FG_YELLOW = "\e[33m"; +constexpr const char * FG_BLUE = "\e[34m"; +constexpr const char * FG_MAGENTA = "\e[35m"; +constexpr const char * FG_CYAN = "\e[36m"; +constexpr const char * FG_WHITE = "\e[37m"; +constexpr const char * BG_BLACK = "\e[40m"; +constexpr const char * BG_RED = "\e[41m"; +constexpr const char * BG_GREEN = "\e[42m"; +constexpr const char * BG_YELLOW = "\e[43m"; +constexpr const char * BG_BLUE = "\e[44m"; +constexpr const char * BG_MAGENTA = "\e[45m"; +constexpr const char * BG_CYAN = "\e[46m"; +constexpr const char * BG_WHITE = "\e[47m"; + +constexpr const char * FG_BLACK_BRIGHT = "\e[90m"; +constexpr const char * FG_RED_BRIGHT = "\e[91m"; +constexpr const char * FG_GREEN_BRIGHT = "\e[92m"; +constexpr const char * FG_YELLOW_BRIGHT = "\e[93m"; +constexpr const char * FG_BLUE_BRIGHT = "\e[94m"; +constexpr const char * FG_MAGENTA_BRIGHT = "\e[95m"; +constexpr const char * FG_CYAN_BRIGHT = "\e[96m"; +constexpr const char * FG_WHITE_BRIGHT = "\e[97m"; +constexpr const char * BG_BLACK_BRIGHT = "\e[100m"; +constexpr const char * BG_RED_BRIGHT = "\e[101m"; +constexpr const char * BG_GREEN_BRIGHT = "\e[102m"; +constexpr const char * BG_YELLOW_BRIGHT = "\e[103m"; +constexpr const char * BG_BLUE_BRIGHT = "\e[104m"; +constexpr const char * BG_MAGENTA_BRIGHT = "\e[105m"; +constexpr const char * BG_CYAN_BRIGHT = "\e[106m"; +constexpr const char * BG_WHITE_BRIGHT = "\e[107m"; + +} // namespace crepe::util::color diff --git a/src/crepe/util/log.cpp b/src/crepe/util/log.cpp new file mode 100644 index 0000000..6829ec3 --- /dev/null +++ b/src/crepe/util/log.cpp @@ -0,0 +1,50 @@ +#include <cstdarg> +#include <cstdio> +#include <cstdlib> +#include <string> + +#include "log.h" + +using namespace crepe::util; + +static const char * const LOG_PREFIX[] = { + [log_level::debug] = "[DBG] ", + [log_level::info] = "[INFO] ", + [log_level::warning] = "[WARN] ", + [log_level::error] = "[ERR] ", +}; + +static void va_logf(enum log_level level, va_list args, const std::string fmt) { + va_list args_copy; + va_copy(args_copy, args); + + // prepend log level and ensure newline + std::string format_fixed = LOG_PREFIX[level] + fmt; + if (!format_fixed.ends_with("\n")) format_fixed += "\n"; + + size_t sz = vsnprintf(NULL, 0, format_fixed.c_str(), args_copy) + 1; + char * msg = (char *) malloc(sz); + va_end(args_copy); + + vsnprintf(msg, sz, format_fixed.c_str(), args); + + // TODO: also log to file or smth + printf("%s", msg); + fflush(stdout); + + free(msg); +} + +void crepe::util::logf(const char * fmt, ...) { + va_list args; + va_start(args, fmt); + va_logf(crepe::util::log_level::debug, args, fmt); + va_end(args); +} + +void crepe::util::logf(log_level level, const char * fmt, ...) { + va_list args; + va_start(args, fmt); + va_logf(level, args, fmt); + va_end(args); +} diff --git a/src/crepe/util/log.h b/src/crepe/util/log.h new file mode 100644 index 0000000..bfe7291 --- /dev/null +++ b/src/crepe/util/log.h @@ -0,0 +1,36 @@ +#pragma once + +// allow user to disable debug macros +#ifndef CREPE_DISABLE_MACROS + +#include "color.h" + +// utility macros +#define _crepe_logf_here(fmt, ...) \ + crepe::util::logf(util::log_level::debug, "%s%s (%s:%d)" fmt "\n", \ + crepe::util::color::FG_WHITE, __PRETTY_FUNCTION__, \ + __FILE_NAME__, __LINE__, crepe::util::color::RESET, \ + __VA_ARGS__) + +// very illegal global function-style macros +// NOLINTBEGIN +#define dbg_logf(fmt, ...) _crepe_logf_here(": " fmt, __VA_ARGS__) +#define dbg_log(str) _crepe_logf_here(": %s", str) +#define dbg_trace() _crepe_logf_here("%s", "") +// NOLINTEND + +#endif + +namespace crepe::util { + +enum log_level { + debug, + info, + warning, + error, +}; + +void logf(const char * fmt, ...); +void logf(enum log_level level, const char * fmt, ...); + +} // namespace crepe::util diff --git a/src/example/CMakeLists.txt b/src/example/CMakeLists.txt new file mode 100644 index 0000000..17e995b --- /dev/null +++ b/src/example/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(audio_internal EXCLUDE_FROM_ALL audio_internal.cpp) +target_link_libraries(audio_internal PUBLIC crepe) + +add_executable(particel EXCLUDE_FROM_ALL particel.cpp) +target_link_libraries(particel PUBLIC crepe) + diff --git a/src/example/audio_internal.cpp b/src/example/audio_internal.cpp new file mode 100644 index 0000000..1199e2d --- /dev/null +++ b/src/example/audio_internal.cpp @@ -0,0 +1,45 @@ +/** \file + * + * Standalone example for usage of the internal \c Sound class. + */ + +#include <crepe/Sound.h> +#include <crepe/util/log.h> + +#include <chrono> +#include <thread> + +using namespace crepe; +using namespace std; +using namespace std::chrono_literals; +using std::make_unique; + +int main() { + dbg_trace(); + + auto bgm = Sound("../mwe/audio/bgm.ogg"); + auto sfx1 = Sound("../mwe/audio/sfx1.wav"); + auto sfx2 = Sound("../mwe/audio/sfx2.wav"); + auto sfx3 = Sound("../mwe/audio/sfx3.wav"); + + bgm.play(); + + // play each sample sequentially + this_thread::sleep_for(500ms); + sfx1.play(); + this_thread::sleep_for(500ms); + sfx2.play(); + bgm.pause(); + this_thread::sleep_for(500ms); + sfx3.play(); + bgm.play(); + this_thread::sleep_for(500ms); + + // play all samples simultaniously + sfx1.play(); + sfx2.play(); + sfx3.play(); + this_thread::sleep_for(1000ms); + + return 0; +} diff --git a/src/crepe/main.cpp b/src/example/particel.cpp index 58480a9..58480a9 100644 --- a/src/crepe/main.cpp +++ b/src/example/particel.cpp diff --git a/src/makefile b/src/makefile new file mode 100644 index 0000000..c1ef601 --- /dev/null +++ b/src/makefile @@ -0,0 +1,6 @@ +.PHONY: FORCE + +FMT += $(shell git ls-files '*.c' '*.cpp' '*.h' '*.hpp') +format: FORCE + clang-tidy -p build/compile_commands.json --fix-errors $(FMT) + diff --git a/src/readme.md b/src/readme.md new file mode 100644 index 0000000..1c5d3a2 --- /dev/null +++ b/src/readme.md @@ -0,0 +1,8 @@ +# engine source + +This folder contains the crêpe engine source files, unit tests, and some toy +examples. The only target built by default by the CMakeLists.txt in this folder +is the crêpe shared library object. Unit tests can be built by explicitly +specifying the target `test_main` when running the build command. Each source +file in the example/ folder corresponds to a CMake target as well. + diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt new file mode 100644 index 0000000..0d316d6 --- /dev/null +++ b/src/test/CMakeLists.txt @@ -0,0 +1,5 @@ +target_sources(test_main PUBLIC + dummy.cpp + # audio.cpp +) + diff --git a/src/test/audio.cpp b/src/test/audio.cpp new file mode 100644 index 0000000..d6ff689 --- /dev/null +++ b/src/test/audio.cpp @@ -0,0 +1,10 @@ +#include <gtest/gtest.h> + +using namespace std; +using namespace std::chrono_literals; + +// using namespace crepe; + +// TODO: mock internal audio class + +TEST(audio, play) { ASSERT_TRUE(true); } diff --git a/test/dummy.cpp b/src/test/dummy.cpp index a00a9c6..a00a9c6 100644 --- a/test/dummy.cpp +++ b/src/test/dummy.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index 26aa460..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -cmake_minimum_required(VERSION 3.28) - -set(CMAKE_C_STANDARD 11) -set(CMAKE_CXX_STANDARD 20) -set(CMAKE_EXPORT_COMPILE_COMMANDS 1) - -set(CMAKE_BUILD_TYPE Debug) - -project(test C CXX) - -add_subdirectory(../lib/googletest googletest) - -add_executable(test - dummy.cpp -) - -target_link_libraries(test - gtest_main - # TODO: add crepe engine -) - |