cakefoot/src/Replay.hpp
Cocktail Frank 3c9bab42d5 Post replay data to remote receiver after writing the replay
Add length, save data ID, and start time directly to the root of the
replay data.

Fix bug where profile index increase was being reset.
2025-09-16 23:20:23 -04:00

238 lines
7.9 KiB
C++

#pragma once
#include <optional>
#include <vector>
#include <chrono>
#include <sstream>
#include "json/json.hpp"
#include "progress.hpp"
#include "extension.hpp"
namespace cakefoot
{
class Replay
{
public:
const static int time_precision {100'000};
const static int position_precision {10'000};
enum Event
{
none = 0,
collision = 1,
coin = 3,
checkpoint = 4,
collect = 5,
end = 6
};
struct KeyFrame
{
float time;
glm::vec2 translation;
bool mirrored = false;
Event event = none;
float speed = 0.0f;
};
/* Exposed for get and set by the client */
bool coin_taken = false;
bool coin_collected = false;
int checkpoints = 0;
protected:
/* Track where the data is stored. If not stored yet, the path will be empty. Protected so that it can be mocked
* in test programs. */
fs::path most_recent_path;
private:
sb::progress::Progress progress;
std::size_t playback_index = 0;
std::string key_frames_key {"key frames"};
std::string metadata_key {"metadata"};
std::string level_metadata_key {"level"};
std::string length_metadata_key {"length"};
std::string save_data_id_key {"save data id"};
std::string start_time_key {"start"};
KeyFrame _last_read;
bool end_event_encountered = false;
/*!
* @return All key frames stored in the progress object
*/
inline nlohmann::json& key_frames()
{
return progress.get(key_frames_key);
};
/*!
* @return All key frames stored in the progress object
*/
inline const nlohmann::json& key_frames() const
{
return progress.read(key_frames_key);
};
/*!
* @return The most recently stored key frame data in the progress object
*/
KeyFrame latest_key_frame() const;
/*!
* @param index Key frame index to check the time elapsed from the start of the replay
*
* @return Time elapsed since the start of the replay at this frame timestamp
*/
float elapsed(int index) const;
public:
/*!
* Read the frames of replay data contained in a JSON file into this object. Any data currently stored in the
* object will be overwritten.
*
* The path will be stored in the object, so that the next call to `Replay::remove` will delete this path,
* unless `Replay::save` or `Replay::load` are called first.
*
* @param path JSON file containing replay key frame data
*
* @throw nlohmann::json::parse_error If the file is unreadable as JSON
*/
void load(const fs::path& path);
/*!
* Add a key frame to the replay object. If this is the first frame, the given frame time will represent the
* zero time of the replay.
*
* @warning The end event is meant to only be recorded once per replay file, but currently there is nothing
* enforcing this.
*
* @param frame Key frame data to add
*/
void record(KeyFrame frame);
/*!
* Write the replay object's frames as JSON to a given directory. This always creates a new file.
*
* The file name will be generated using the format `{level}_{date}_{id}.json`. The file path will be stored in
* this object, so the next call to `Replay::remove()` will delete this path unless `Replay::save` or
* `Replay::load` are called first.
*
* The save data ID is an optional ID string that can associate the replay with a save progress file.
*
* @param level Index of the level the replay is for
* @param metadata Custom JSON data to store with the file
* @param directory Path to a directory to create the file in
* @param save_data_id Identifies the save progress file associated with this replay
*
* @return Path to the newly created file
*/
void save(
int level,
const nlohmann::json& metadata = {},
const fs::path& directory = ".",
const std::string save_data_id = "");
/*!
* Delete the data in storage for this replay. The data is the most recent stored with `Replay::save` or loaded
* with `Replay::load`. If there is no stored data associated with the replay, this function doesn't do
* anything.
*/
void remove();
/*!
* Get a vector containing all unread frames in the replay object up until a certain timestamp. This will
* increment the playback index, so a subsequent call using the same time parameter would return an empty list.
*
* @param time Get unread frames up to this timestamp in seconds
*
* @return A vector of key frames
*/
std::vector<KeyFrame> unread(float time);
/*!
* @return The most recently read frame, which is the same as the last frame in the list returned by the most
* recent call to Replay::unread.
*/
KeyFrame last_read() const;
/*!
* Reset playback index to zero, effectively marking all key frames as unread and restarting the replay.
*/
void reset();
/*!
* @return Length of the replay in seconds
*/
float length() const;
/*!
* @return True if the replay doesn't contain any frames, false otherwise.
*/
bool empty() const;
/*!
* @param frame Key frame to check the time elapsed from the start of the replay
*
* @return Time elapsed since the start of the replay at this frame timestamp
*/
float elapsed(KeyFrame frame) const;
/*!
* @return The ID associated with the replay's progress object and file
*/
std::string id() const;
/*!
* This will return true if the end event has been encounter during a read.
*
* @warning There is currently nothing preventing adding or reading frames past the end event, so this
* function basically assumes the end event is the last frame in the replay.
*/
bool ended() const;
/*!
* Get the metadata associated with the replay data, which can be any custom JSON data saved along with the
* replay file. This can be set when saving the replay data with Replay::save, using the metadata parameter.
* Before the replay is saved or loaded, this will always return an empty string.
*
* @return JSON metadata associated with the replay data
*/
nlohmann::json metadata() const;
/*!
* @return Read-only access to the replay data as a JSON object
*/
const nlohmann::json& json() const;
/*!
* Remove all frames from the replay. Replay::empty will return true afterward.
*/
void clear();
};
/*!
* Convert a KeyFrame object into a JSON array. This is called internally by the JSON library when
* Replay::save is called.
*
* @param json Array for storing KeyFrame data
* @param key_frame The object containing frame data to convert to JSON
*/
void to_json(nlohmann::json& json, const Replay::KeyFrame& key_frame);
/*!
* Convert a JSON array into a KeyFrame object. This is called internally by the JSON library when
* json::get<KeyFrame> is called.
*
* @param json Array containing KeyFrame data
* @param key_frame The object to store the data in
*/
void from_json(const nlohmann::json& json, Replay::KeyFrame& key_frame);
}