// Copyright 2013 Dolphin Emulator Project // Licensed under GPLv2 // Refer to the license.txt file included. #ifndef _LINEAR_DISKCACHE #define _LINEAR_DISKCACHE #include "common/common.h" #include <fstream> // defined in Version.cpp extern const char *scm_rev_git_str; // On disk format: //header{ // u32 'DCAC'; // u32 version; // svn_rev // u16 sizeof(key_type); // u16 sizeof(value_type); //} //key_value_pair{ // u32 value_size; // key_type key; // value_type[value_size] value; //} template <typename K, typename V> class LinearDiskCacheReader { public: virtual void Read(const K &key, const V *value, u32 value_size) = 0; }; // Dead simple unsorted key-value store with append functionality. // No random read functionality, all reading is done in OpenAndRead. // Keys and values can contain any characters, including \0. // // Suitable for caching generated shader bytecode between executions. // Not tuned for extreme performance but should be reasonably fast. // Does not support keys or values larger than 2GB, which should be reasonable. // Keys must have non-zero length; values can have zero length. // K and V are some POD type // K : the key type // V : value array type template <typename K, typename V> class LinearDiskCache { public: // return number of read entries u32 OpenAndRead(const char *filename, LinearDiskCacheReader<K, V> &reader) { using std::ios_base; // close any currently opened file Close(); m_num_entries = 0; // try opening for reading/writing OpenFStream(m_file, filename, ios_base::in | ios_base::out | ios_base::binary); m_file.seekg(0, std::ios::end); std::fstream::pos_type end_pos = m_file.tellg(); m_file.seekg(0, std::ios::beg); std::fstream::pos_type start_pos = m_file.tellg(); std::streamoff file_size = end_pos - start_pos; if (m_file.is_open() && ValidateHeader()) { // good header, read some key/value pairs K key; V *value = NULL; u32 value_size; u32 entry_number; std::fstream::pos_type last_pos = m_file.tellg(); while (Read(&value_size)) { std::streamoff next_extent = (last_pos - start_pos) + sizeof(value_size) + value_size; if (next_extent > file_size) break; delete[] value; value = new V[value_size]; // read key/value and pass to reader if (Read(&key) && Read(value, value_size) && Read(&entry_number) && entry_number == m_num_entries+1) { reader.Read(key, value, value_size); } else { break; } m_num_entries++; last_pos = m_file.tellg(); } m_file.seekp(last_pos); m_file.clear(); delete[] value; return m_num_entries; } // failed to open file for reading or bad header // close and recreate file Close(); m_file.open(filename, ios_base::out | ios_base::trunc | ios_base::binary); WriteHeader(); return 0; } void Sync() { m_file.flush(); } void Close() { if (m_file.is_open()) m_file.close(); // clear any error flags m_file.clear(); } // Appends a key-value pair to the store. void Append(const K &key, const V *value, u32 value_size) { // TODO: Should do a check that we don't already have "key"? (I think each caller does that already.) Write(&value_size); Write(&key); Write(value, value_size); m_num_entries++; Write(&m_num_entries); } private: void WriteHeader() { Write(&m_header); } bool ValidateHeader() { char file_header[sizeof(Header)]; return (Read(file_header, sizeof(Header)) && !memcmp((const char*)&m_header, file_header, sizeof(Header))); } template <typename D> bool Write(const D *data, u32 count = 1) { return m_file.write((const char*)data, count * sizeof(D)).good(); } template <typename D> bool Read(const D *data, u32 count = 1) { return m_file.read((char*)data, count * sizeof(D)).good(); } struct Header { Header() : id(*(u32*)"DCAC") , key_t_size(sizeof(K)) , value_t_size(sizeof(V)) { memcpy(ver, scm_rev_git_str, 40); } const u32 id; const u16 key_t_size, value_t_size; char ver[40]; } m_header; std::fstream m_file; u32 m_num_entries; }; #endif // _LINEAR_DISKCACHE