diff --git a/src/Animation.cpp b/src/Animation.cpp index 5934abb..322a478 100644 --- a/src/Animation.cpp +++ b/src/Animation.cpp @@ -2,7 +2,7 @@ Animation::Animation() { - timer.toggle(sb::Timer::OFF); + timer.off(); } void Animation::play(float delay, bool play_once) @@ -10,12 +10,12 @@ void Animation::play(float delay, bool play_once) this->delay = delay; play_state = true; paused = false; - previous_step_time = timer.ms_elapsed(); + previous_step_time = timer.elapsed(); overflow = 0; ending = play_once; if (delay <= 0) { - timer.toggle(true); + timer.on(); } } @@ -31,19 +31,19 @@ void Animation::play_once(float delay) void Animation::pause() { - timer.toggle(false); + timer.off(); paused = true; } void Animation::unpause() { - timer.toggle(true); + timer.on(); paused = false; } void Animation::reset() { - timer.toggle(false); + timer.off(); play_state = false; timer.reset(); } @@ -53,25 +53,25 @@ bool Animation::playing(bool include_delay) const return play_state && !paused && (include_delay || delay <= 0); } -void Animation::update() +void Animation::update(float timestamp) { - timer.update(); + timer.update(timestamp); if (playing() && containing_object->is_active()) { if (delay > 0) { - delay -= timer.ms_last_frame(); + delay -= timer.frame(); if (delay <= 0) { - timer.toggle(true); + timer.on(); } } if (delay <= 0) { - if (timer.ms_elapsed() - previous_step_time + overflow > frame_length) + if (timer.elapsed() - previous_step_time + overflow > frame_length) { - overflow = timer.ms_elapsed() - previous_step_time + overflow - frame_length; - previous_step_time = timer.ms_elapsed(); + overflow = timer.elapsed() - previous_step_time + overflow - frame_length; + previous_step_time = timer.elapsed(); step(); if (ending) { diff --git a/src/Animation.hpp b/src/Animation.hpp index 74fdb8b..f84008b 100644 --- a/src/Animation.hpp +++ b/src/Animation.hpp @@ -25,8 +25,7 @@ class Animation private: bool play_state = false, ending = false, paused = false; - int previous_step_time = 0; - float delay = 0, overflow = 0, frame_length = 0; + float delay = 0.0f, overflow = 0.0f, frame_length = 0.0f, previous_step_time = 0.0f; callback step = nullptr; Node* containing_object = nullptr; sb::Timer timer = sb::Timer(); @@ -35,19 +34,22 @@ public: Animation(); - /* Constructor that allows passing a pointer to an object and a pointer to one of its + /*! + * Constructor that allows passing a pointer to an object and a pointer to one of its * member functions that will be used as the animation function. Frame length can be supplied - * in milliseconds, representing how often the animation function will run. If omitted, the - * animation function will run every time the Animation object is updated (generally, once per - * frame of the application). */ + * in seconds, representing how often the animation function will run. If omitted, the + * animation function will run every time the Animation object is updated. + */ template Animation(void(T::*member_function)(), T* object, float frame_length = 0) : frame_length(frame_length) { bind(member_function, object); - timer.toggle(sb::Timer::OFF); + timer.off(); } - /* Set the animation function by supplying an object and one of its member functions */ + /*! + * Set the animation function by supplying an object and one of its member functions. + */ template void bind(void(T::*f)(), T* o) { @@ -62,7 +64,7 @@ public: void unpause(); void reset(); bool playing(bool = true) const; - void update(); + void update(float timestamp); }; diff --git a/src/Audio.cpp b/src/Audio.cpp index 884d1a7..209987e 100644 --- a/src/Audio.cpp +++ b/src/Audio.cpp @@ -104,9 +104,9 @@ bool SoundEffect::playing(bool include_delay) const return false; } -void SoundEffect::update() +void SoundEffect::update(float timestamp) { - play_animation.update(); + play_animation.update(timestamp); } SoundEffect::~SoundEffect() @@ -126,10 +126,10 @@ void Audio::load_bgm() load_directory(configuration()["audio"]["default-bgm-root"], bgm, this); } -void Audio::update() +void Audio::update(float timestamp) { for (auto& [name, sound_effect] : sfx) { - sound_effect.update(); + sound_effect.update(timestamp); } } diff --git a/src/Audio.hpp b/src/Audio.hpp index 7409525..75e3787 100644 --- a/src/Audio.hpp +++ b/src/Audio.hpp @@ -56,7 +56,7 @@ struct SoundEffect : Node void set_loop_count(int); void set_loop_forever(); bool playing(bool = false) const; - void update(); + void update(float timestamp); ~SoundEffect(); }; @@ -70,7 +70,7 @@ struct Audio : Node Audio(Node*); void load_sfx(); void load_bgm(); - void update(); + void update(float timestamp); template void load_directory(const fs::path& root, std::map& storage, Node* parent = nullptr) diff --git a/src/Carousel.hpp b/src/Carousel.hpp index 579cd49..494812c 100644 --- a/src/Carousel.hpp +++ b/src/Carousel.hpp @@ -17,8 +17,6 @@ #include "glm/common.hpp" #include "glm/gtx/integer.hpp" -#include "utility.hpp" - namespace sb { diff --git a/src/Configuration.cpp b/src/Configuration.cpp index 3e85900..44c2a32 100644 --- a/src/Configuration.cpp +++ b/src/Configuration.cpp @@ -34,7 +34,7 @@ void Configuration::set_defaults() {"suppress-any-key-on-mods", true}, {"system-any-key-ignore-commands", {"fullscreen", "screenshot", "record", "quit"}}, {"any-key-ignore-commands", nlohmann::json::array()}, - {"default-unsuppress-delay", 700}, + {"default-unsuppress-delay", 0.7}, {"ignore-repeat-keypress", true} }; config["display"] = { @@ -69,10 +69,10 @@ void Configuration::set_defaults() {"screenshot-extension", ".png"}, {"screenshot-zfill", 5}, {"screenshot-directory", "."}, - {"gif-frame-length", 100}, + {"gif-frame-length", 0.1}, {"video-directory", "."}, {"write-mp4", false}, - {"max-stash-length", 5000}, + {"max-stash-length", 5.0}, {"max-in-game-stashes", 3}, {"max-video-stashes", 40}, {"max-video-memory", 1000}, @@ -92,7 +92,7 @@ void Configuration::set_defaults() }; config["configuration"] = { {"auto-refresh", false}, - {"auto-refresh-interval", 1000} + {"auto-refresh-interval", 1.0} }; } @@ -156,6 +156,7 @@ void Configuration::merge(const fs::path& path) sb::Log::log(message, sb::Log::WARN); } #ifndef __ANDROID__ + config_file_modification_time = fs::last_write_time(path); } else { @@ -208,9 +209,9 @@ void Configuration::refresh() } } -void Configuration::update() +void Configuration::update(float timestamp) { - auto_refresher.update(); + auto_refresher.update(timestamp); } std::ostream& std::operator<<(std::ostream& out, const Configuration& configuration) diff --git a/src/Configuration.hpp b/src/Configuration.hpp index db17654..9cc6e67 100644 --- a/src/Configuration.hpp +++ b/src/Configuration.hpp @@ -102,10 +102,10 @@ public: /*! * Enable auto refresh. Auto refresh watches the file at the given path for changes and loads them automatically every interval given - * in milliseconds. + * in seconds. * * @param file_to_refresh path to a configuration JSON - * @param interval amount of milliseconds between each refresh + * @param interval amount of seconds between each refresh */ void enable_auto_refresh(const fs::path& file_to_refresh, float interval); @@ -120,9 +120,11 @@ public: void refresh(); /*! - * Update the auto refresher. + * Update the auto refresher with the given timestamp, which should be the timestamp passed to Game::update. + * + * @param timestamp seconds elapsed since the program started */ - void update(); + void update(float timestamp); }; diff --git a/src/Game.cpp b/src/Game.cpp index 7a1d19f..445df7b 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -565,26 +565,27 @@ void Game::run() #endif } -void Game::frame(float ticks) +void Game::frame(float timestamp) { - if (ticks - last_frame_timestamp + frame_time_overflow >= frame_length) + if (timestamp - last_frame_timestamp + frame_time_overflow >= frame_length) { - last_frame_length = ticks - last_frame_timestamp; + last_frame_length = timestamp - last_frame_timestamp; if (frame_length_history.size() == 5000) { frame_length_history.pop_back(); } frame_length_history.insert(frame_length_history.begin(), last_frame_length); frame_time_overflow = last_frame_length + frame_time_overflow - frame_length; - last_frame_timestamp = ticks; + last_frame_timestamp = timestamp; if (last_frame_length < 1000) { - recorder.update(); + float timestamp_seconds = timestamp / 1000.0f; + recorder.update(timestamp_seconds); _delegate.dispatch(); - audio.update(); - input.unsuppress_animation.update(); - update(); - _configuration.update(); + audio.update(timestamp_seconds); + input.unsuppress_animation.update(timestamp_seconds); + update(timestamp_seconds); + _configuration.update(timestamp_seconds); if (!is_gl_context) { SDL_SetRenderTarget(renderer, nullptr); @@ -597,10 +598,10 @@ void Game::frame(float ticks) // frame_time_overflow = 0; // } frame_count_this_second++; - if (ticks - last_frame_count_timestamp >= 1000) + if (timestamp - last_frame_count_timestamp >= 1000) { current_frames_per_second = frame_count_this_second; - last_frame_count_timestamp = ticks; + last_frame_count_timestamp = timestamp; frame_count_this_second = 0; std::ostringstream message; message << "Counted " << current_frames_per_second << " frames last second"; diff --git a/src/Game.hpp b/src/Game.hpp index 62442e4..88ef77c 100644 --- a/src/Game.hpp +++ b/src/Game.hpp @@ -148,7 +148,7 @@ public: void run(); void frame(float); void flag_to_end(); - virtual void update() {}; + virtual void update(float timestamp) {}; void set_framerate(int); float get_frame_length() const; void handle_quit_event(SDL_Event&); @@ -165,9 +165,9 @@ public: * @return weighted value */ template - T weight(T amount) + T weight(T amount) const { - return (last_frame_length / (1000.0 / 60)) * amount; + return (last_frame_length / 1000.0f) * amount; } }; diff --git a/src/Node.cpp b/src/Node.cpp index 808389f..9d4550b 100644 --- a/src/Node.cpp +++ b/src/Node.cpp @@ -125,10 +125,10 @@ void Node::suppress_input() get_root()->get_input().suppress(); } -void Node::suppress_input_temporarily(int length) +void Node::suppress_input_temporarily(float length) { suppress_input(); - if (length == 0) + if (length == 0.0f) { length = configuration()["input"]["default-unsuppress-delay"]; } diff --git a/src/Node.hpp b/src/Node.hpp index f6a8cf4..2014f83 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -78,7 +78,7 @@ public: const Game* get_root() const; Box window_box(bool = false); void suppress_input(); - void suppress_input_temporarily(int = 0); + void suppress_input_temporarily(float length = 0.0f); void unsuppress_input(); const std::string get_branch_as_string() const; virtual std::string class_name() const { return "Node"; }; diff --git a/src/Recorder.cpp b/src/Recorder.cpp index 3a9f248..a9bfd06 100644 --- a/src/Recorder.cpp +++ b/src/Recorder.cpp @@ -17,7 +17,7 @@ Recorder::Recorder(Node* parent) : Node(parent) * been configured by the user. */ float Recorder::frame_length() { - return configuration()["recording"].value("video-frame-length", get_root()->get_frame_length()); + return configuration()["recording"].value("video-frame-length", get_root()->get_frame_length() / 1000.0f); } /* Handle commands for screenshot, record video and save video */ @@ -259,7 +259,7 @@ void Recorder::write_stash_frames(Stash* stash) if (ii == stash->frame_offset) { GifBegin(&gif_writer, gif_path.string().c_str(), frame->w, - frame->h, gif_frame_length / 10); + frame->h, gif_frame_length * 100); } else { @@ -269,7 +269,7 @@ void Recorder::write_stash_frames(Stash* stash) SDL_Surface* converted = SDL_ConvertSurfaceFormat( frame, SDL_PIXELFORMAT_ABGR8888, 0); GifWriteFrame(&gif_writer, (const uint8_t*) converted->pixels, - frame->w, frame->h, gif_frame_length / 10); + frame->w, frame->h, gif_frame_length * 100); } elapsed += frame_length(); delete[] stash->pixel_buffers.front(); @@ -343,7 +343,7 @@ void Recorder::write_mp4() std::string pixel_format = configuration()["recording"]["mp4-pixel-format"].get(); fs::path images_match = current_video_directory / "%05d.png"; mp4_command << "ffmpeg -f s16le -ac 2 -ar 22050 -i " << current_audio_path.string() << - " -f image2 -framerate " << (1000 / frame_length()) << + " -f image2 -framerate " << (1.0f / frame_length()) << " -i " << images_match.string() << " -s " << size.x << "x" << size.y << " -c:v libx264 -crf 17 -pix_fmt " << pixel_format << " " << current_video_directory.string() << ".mp4"; @@ -357,14 +357,14 @@ void Recorder::write_audio(Uint8* stream, int len) audio_file.write(reinterpret_cast(stream), len); } -void Recorder::update() +void Recorder::update(float timestamp) { if (is_recording and get_memory_size() > configuration()["recording"]["max-video-memory"]) { end_recording(); } animation.set_frame_length(frame_length()); - animation.update(); + animation.update(timestamp); } void Recorder::process_audio(void* context, Uint8* stream, int len) diff --git a/src/Recorder.hpp b/src/Recorder.hpp index b42a63f..9c55931 100644 --- a/src/Recorder.hpp +++ b/src/Recorder.hpp @@ -75,7 +75,7 @@ public: void finish_writing_video(); void write_mp4(); void write_audio(Uint8*, int); - void update(); + void update(float timestamp); virtual std::string class_name() const { return "Recorder"; } }; diff --git a/src/Segment.hpp b/src/Segment.hpp index 7bfc51e..ab1f785 100644 --- a/src/Segment.hpp +++ b/src/Segment.hpp @@ -51,13 +51,6 @@ public: std::vector subsegments(int) const; inline bool operator<(const Segment& segment) const { return operator<(segment.length()); } - /* - std::cout << (a < b) << (a > b) << (b < c) << (b <= c) << (c > b) << (c >= b) << (d < 1) << (d < 2) << - (d >= a) << (d > c) << std::endl; - - should print 0101010101 - */ - template inline bool operator<(const N& other) const { return length() < other; } @@ -76,3 +69,11 @@ namespace std { std::ostream& operator<<(std::ostream&, const Segment&); } + +/* Add Segment class to the sb namespace. This should be the default location, but Segment is left in the global namespace + * for backward compatibility. + */ +namespace sb +{ + using ::Segment; +} diff --git a/src/Selection.hpp b/src/Selection.hpp index ea56367..bc99d5d 100644 --- a/src/Selection.hpp +++ b/src/Selection.hpp @@ -17,8 +17,6 @@ #include "glm/common.hpp" #include "glm/gtx/integer.hpp" -#include "utility.hpp" - namespace sb { diff --git a/src/Sprite.cpp b/src/Sprite.cpp index daf7690..c4cbcdd 100644 --- a/src/Sprite.cpp +++ b/src/Sprite.cpp @@ -830,7 +830,7 @@ void Sprite::set_draw_children_on_frame(bool on_frame) draw_children_on_frame = on_frame; } -void Sprite::update(const std::vector& subsections) +void Sprite::update(float timestamp, const std::vector& subsections) { if (is_active()) { @@ -838,10 +838,10 @@ void Sprite::update(const std::vector& subsections) { move_weighted(step); } - frame_animation.update(); - blink_animation.update(); - wipe_animation.update(); - toggle_hidden_animation.update(); + frame_animation.update(timestamp); + blink_animation.update(timestamp); + wipe_animation.update(timestamp); + toggle_hidden_animation.update(timestamp); SDL_Texture* texture = get_current_frame(); if (is_loaded() && !is_hidden() && get_current_frameset().get_frame_count()) { @@ -892,7 +892,7 @@ void Sprite::update(const std::vector& subsections) child_relative_position = child.sprite.get_nw(); child.sprite.move(get_nw()); } - child.sprite.update(); + child.sprite.update(timestamp); if (!draw_children_on_frame) { child.sprite.set_nw(child_relative_position); @@ -901,9 +901,9 @@ void Sprite::update(const std::vector& subsections) } } -void Sprite::update() +void Sprite::update(float timestamp) { - update({}); + update(timestamp, {}); } void Sprite::render_subsection(SDL_Renderer* renderer, SDL_Texture* texture, const Box& subsection, const Box& box) diff --git a/src/Sprite.hpp b/src/Sprite.hpp index b761d3f..e82e86b 100644 --- a/src/Sprite.hpp +++ b/src/Sprite.hpp @@ -140,8 +140,8 @@ public: bool has_child(std::string) const; void remove_child(std::string); void set_draw_children_on_frame(bool); - virtual void update(const std::vector&); - virtual void update(); + virtual void update(float timestamp, const std::vector&); + virtual void update(float timestamp); void render_subsection(SDL_Renderer*, SDL_Texture*, const Box&, const Box&); void set_to_leave_memory_allocated(); void set_to_deallocate_memory(); diff --git a/src/Switch.hpp b/src/Switch.hpp index c251673..e6b900d 100644 --- a/src/Switch.hpp +++ b/src/Switch.hpp @@ -84,10 +84,8 @@ namespace sb private: using Reaction = std::function; - inline static const bool STATE_OFF = false; - inline static const bool STATE_ON = true; - bool state = STATE_OFF; + bool state = false; Reaction reaction; public: @@ -102,6 +100,12 @@ namespace sb Switch(bool state, Reaction reaction = Reaction()) : state(state), reaction(reaction) {} + /*! + * Toggle state, triggering the reaction function. + * + * @param args arbitrary list of arguments that are expected by the reaction function + * @return return the return value of the reaction function + */ return_type flip(arguments... args) { state = !state; @@ -121,13 +125,14 @@ namespace sb /*! * @return when called as a boolean, return state */ - operator bool() + operator bool() const { - return on(); + return state; } /*! - * Assign the switch object's state using the assignment operator with a boolean value. + * Assign the switch object's state using the assignment operator with a boolean value, without triggering the reaction function. + * To trigger the reaction function, use `Switch::flip` instead. * * @param state state the switch will be set to */ @@ -137,14 +142,6 @@ namespace sb return *this; } - /*! - * @return true if state is Switch::STATE_ON, false otherwise - */ - bool on() - { - return state; - } - operator std::string() const { return state ? "true" : "false"; diff --git a/src/Timer.cpp b/src/Timer.cpp index 88fd62c..291b6f7 100644 --- a/src/Timer.cpp +++ b/src/Timer.cpp @@ -1,77 +1,55 @@ #include "Timer.hpp" -/* Initialize a Timer object for keeping a general time count in milliseconds or seconds which can be paused - * arbitrarily. The time initializes to zero. By default, the timer begins timing at initialization (this - * may change in the future). The ticks and previous_ticks parameters are initially synchronized with the SDL - * ticks function, which returns the number of milliseconds since the program began, and the SDL ticks function - * is used to keep the time updated. */ -sb::Timer::Timer() -{ - ticks = SDL_GetTicks(); - ticks_previous = ticks; -} - -/* Check whether Timer is keeping time or not */ -bool sb::Timer::state() +sb::Timer::operator bool() const { return timing; } -/* Toggle timing on/off */ void sb::Timer::toggle() { - toggle(!state()); + toggle(!*this); } -/* Set state explicitly to sb::Timer::ON (true) or sb::Timer::OFF (false) */ void sb::Timer::toggle(bool state) { timing = state; } -/* Reset time elapsed to zero */ +void sb::Timer::on() +{ + toggle(ON); +} + +void sb::Timer::off() +{ + toggle(OFF); +} + void sb::Timer::reset() { - ticks_elapsed = 0; - ticks = SDL_GetTicks(); - ticks_previous = ticks; - frame_duration = 0; + _elapsed = 0; } -/* Return milliseconds elapsed on timer */ -float sb::Timer::ms_elapsed() +float sb::Timer::elapsed() const { - return ticks_elapsed; + return _elapsed; } -/* Return seconds elapsed on timer */ -float sb::Timer::seconds_elapsed() -{ - return ms_elapsed() / 1000.0f; -} - -/* Return the length of the previous frame in milliseconds (the time between the last two calls to - * the timer update function) */ -float sb::Timer::ms_last_frame() +float sb::Timer::frame() const { return frame_duration; } -/* Return the length of the previous frame in seconds (the time between the last two calls to the - * timer update function) */ -float sb::Timer::seconds_last_frame() +void sb::Timer::update(float timestamp) { - return ms_last_frame() / 1000.0f; -} - -/* Add time to the timer by measuring the time between this call and the previous call to update */ -void sb::Timer::update() -{ - ticks = SDL_GetTicks(); - frame_duration = ticks - ticks_previous; - if (state()) + if (previous_is_recorded) { - ticks_elapsed += frame_duration; + frame_duration = timestamp - timestamp_previous; + if (*this) + { + _elapsed += frame(); + } } - ticks_previous = ticks; + timestamp_previous = timestamp; + previous_is_recorded = true; } diff --git a/src/Timer.hpp b/src/Timer.hpp index 2fd9e9a..7be3031 100644 --- a/src/Timer.hpp +++ b/src/Timer.hpp @@ -10,17 +10,20 @@ #pragma once -#include "SDL.h" - namespace sb { + /*! + * Timer in seconds which can be paused arbitrarily. + * + * It must be updated every frame with the timestamp passed to Game::update, regardless of whether it is actively timing or not. + */ class Timer { private: - bool timing = Timer::ON; - int ticks = 0, ticks_previous = 0, frame_duration = 0, ticks_elapsed = 0; + bool timing = false, previous_is_recorded = false; + float timestamp = 0.0f, timestamp_previous = 0.0f, frame_duration = 0.0f, _elapsed = 0.0f; public: @@ -30,16 +33,58 @@ namespace sb ON }; - Timer(); - bool state(); + /*! + * @return boolean indicating whether timer object is keeping time or not + */ + operator bool() const; + + /*! + * Toggle timing on/off + */ void toggle(); - void toggle(bool); + + /*! + * @param state set timing state explictly by passing a boolean + */ + void toggle(bool state); + + /*! + * Start timing. + */ + void on(); + + /*! + * Stop timing. + */ + void off(); + + /*! + * Reset time elapsed to zero. + */ void reset(); - float ms_elapsed(); - float seconds_elapsed(); - float ms_last_frame(); - float seconds_last_frame(); - void update(); + + /*! + * @return seconds elapsed on timer + */ + float elapsed() const; + + + /*! + * @return length of the previous frame in seconds + */ + float frame() const; + + /*! + * Update the timer's elapsed time, using the amount of seconds elapsed in the program at the start of the frame to keep track. + * + * The timer should be updated every frame to keep track of the timestamp, regardless of whether it is currently timing or not. + * + * The timestamp should be forwarded from the value passed to Game::update, so that it is consistently the timestamp at the + * beginning of the frame. + * + * @param timestamp seconds elapsed since the start of the program + */ + void update(float timestamp); /*! * Applies delta timing to a value: returns the value as weighted by the amount of time passed since the @@ -50,9 +95,9 @@ namespace sb * @return weighted value */ template - Type weigh(Type amount) + Type delta(Type amount) const { - return seconds_last_frame() * amount; + return frame() * amount; } };