forked from dolphin-emu/dolphin
		
	
		
			
				
	
	
		
			193 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			193 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
// Copyright 2010 Dolphin Emulator Project
 | 
						|
// Licensed under GPLv2+
 | 
						|
// Refer to the license.txt file included.
 | 
						|
 | 
						|
#pragma once
 | 
						|
 | 
						|
#include <cstring>
 | 
						|
#include <fstream>
 | 
						|
#include <string>
 | 
						|
#include <type_traits>
 | 
						|
 | 
						|
#include "Common/Common.h"
 | 
						|
#include "Common/CommonTypes.h"
 | 
						|
#include "Common/FileUtil.h"
 | 
						|
 | 
						|
// 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 std::string& filename, LinearDiskCacheReader<K, V>& reader)
 | 
						|
  {
 | 
						|
    using std::ios_base;
 | 
						|
 | 
						|
// Since we're reading/writing directly to the storage of K instances,
 | 
						|
// K must be trivially copyable. TODO: Remove #if once GCC 5.0 is a
 | 
						|
// minimum requirement.
 | 
						|
#if defined(__GNUC__) && !defined(__clang__) && __GNUC__ < 5
 | 
						|
    static_assert(std::has_trivial_copy_constructor<K>::value,
 | 
						|
                  "K must be a trivially copyable type");
 | 
						|
#else
 | 
						|
    static_assert(std::is_trivially_copyable<K>::value, "K must be a trivially copyable type");
 | 
						|
#endif
 | 
						|
 | 
						|
    // 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;
 | 
						|
 | 
						|
    m_header.Init();
 | 
						|
    if (m_file.is_open() && ValidateHeader())
 | 
						|
    {
 | 
						|
      // good header, read some key/value pairs
 | 
						|
      K key;
 | 
						|
 | 
						|
      V* value = nullptr;
 | 
						|
      u32 value_size = 0;
 | 
						|
      u32 entry_number = 0;
 | 
						|
 | 
						|
      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
 | 
						|
  {
 | 
						|
    void Init()
 | 
						|
    {
 | 
						|
      // Null-terminator is intentionally not copied.
 | 
						|
      std::memcpy(&id, "DCAC", sizeof(u32));
 | 
						|
      std::memcpy(ver, scm_rev_git_str.c_str(), std::min(scm_rev_git_str.size(), sizeof(ver)));
 | 
						|
    }
 | 
						|
 | 
						|
    u32 id;
 | 
						|
    const u16 key_t_size = sizeof(K);
 | 
						|
    const u16 value_t_size = sizeof(V);
 | 
						|
    char ver[40] = {};
 | 
						|
 | 
						|
  } m_header;
 | 
						|
 | 
						|
  std::fstream m_file;
 | 
						|
  u32 m_num_entries;
 | 
						|
};
 |