commit x64 compilation from lulu cause the other branch dont seems to compile properly at home
This commit is contained in:
280
include/platform/filesystem/io_cache.cpp
Normal file
280
include/platform/filesystem/io_cache.cpp
Normal file
@ -0,0 +1,280 @@
|
||||
/* -----------------------------------------------------------------------------
|
||||
GSFramework
|
||||
Copyright 2001-2013 Emmanuel Julien. All Rights Reserved.
|
||||
----------------------------------------------------------------------------- */
|
||||
|
||||
|
||||
#include "filesystem/io_cache.h"
|
||||
#include "hash/nsha1.h"
|
||||
#include "platform.h"
|
||||
#include "log/log.h"
|
||||
#include "ntypes.h"
|
||||
|
||||
using namespace GS::IO;
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
struct FreeEntry // should be in ReserveOnStore but local type on template is prohibited until C++0x
|
||||
{
|
||||
Cache::Entry *entry;
|
||||
int score;
|
||||
|
||||
FreeEntry(Cache::Entry *e, int s) : entry(e), score(s) {}
|
||||
|
||||
static int ComputeScore(const Cache::Entry *e, size_t request_size, const GS::Time &ctime)
|
||||
{
|
||||
int time_bonus = (int)(ctime - e->last_use).toSec();
|
||||
int size_bonus = e->size - request_size;
|
||||
|
||||
return time_bonus + size_bonus / 8;
|
||||
}
|
||||
static int CompareScore(FreeEntry *a, FreeEntry *b)
|
||||
{ return b->score - a->score; }
|
||||
};
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
GS::String Cache::ReserveOnStore(size_t size)
|
||||
{
|
||||
String id = store.Reserve(size);
|
||||
if (!id.IsEmpty())
|
||||
return id;
|
||||
|
||||
// Build a list of reference free entries.
|
||||
Time ctime = Platform::Get().GetTime();
|
||||
|
||||
// No need to drop anything if the request can't fit anyway...
|
||||
size_t total_freeable_store = 0;
|
||||
ListForeachPtr(Entry *, e, entries)
|
||||
if (e->refc == 0)
|
||||
total_freeable_store += e->size;
|
||||
|
||||
if (size > (store.GetFreeStore() + total_freeable_store))
|
||||
return "";
|
||||
|
||||
// Drop entries until the request fits in the store.
|
||||
List <FreeEntry *> free_entries;
|
||||
ListForeachPtr(Entry *, e, entries)
|
||||
if (e->refc == 0)
|
||||
free_entries.Add(new FreeEntry(e, FreeEntry::ComputeScore(e, size, ctime)));
|
||||
|
||||
free_entries.MergeSort(FreeEntry::CompareScore);
|
||||
|
||||
ListForeachPtr(FreeEntry *, e, free_entries)
|
||||
{
|
||||
__LOG_V__ << "IO::Cache: Disposing of cache entry '" << e->entry->path << "' (score: " << e->score << ").\n";
|
||||
|
||||
DeleteCacheEntry(e->entry);
|
||||
|
||||
id = store.Reserve(size);
|
||||
if (!id.IsEmpty())
|
||||
break;
|
||||
}
|
||||
|
||||
ListDeleteAllPtr(FreeEntry *, free_entries)
|
||||
|
||||
store.Save();
|
||||
return id;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
Cache::Entry *Cache::GetCacheEntry(const char *path) const
|
||||
{
|
||||
ListForeachPtr(Entry *, entry, entries)
|
||||
if (entry->path == path)
|
||||
return entry;
|
||||
return NULL;
|
||||
}
|
||||
Cache::Entry *Cache::CreateCacheEntry(const char *path)
|
||||
{
|
||||
__LOG_V__ << "Create cache entry for '" << path << "'.\n";
|
||||
|
||||
Array <char> data;
|
||||
if (!io->FileLoad(path, data))
|
||||
return NULL;
|
||||
|
||||
AutoPtr <Entry> entry(new Entry);
|
||||
if (entry.IsNull())
|
||||
return NULL;
|
||||
|
||||
entry->path = path;
|
||||
entry->id = ReserveOnStore(data.GetSize());
|
||||
if (entry->id.IsEmpty())
|
||||
return NULL; // store full
|
||||
|
||||
Entry *e = entry.Detach();
|
||||
entries.Add(e);
|
||||
|
||||
return UpdateCacheEntry(e, &data) ? e : NULL;
|
||||
}
|
||||
bool Cache::UpdateCacheEntry(Entry *entry, GS::Array <char> *preloaded_data)
|
||||
{
|
||||
__LOG_V__ << "Update cache entry for '" << entry->path << "'.\n";
|
||||
|
||||
Array <char> data;
|
||||
if (preloaded_data)
|
||||
data.Transfer(*preloaded_data);
|
||||
else
|
||||
if (!io->FileLoad(entry->path, data))
|
||||
return false;
|
||||
|
||||
// Check current store entry size.
|
||||
if (store.GetEntrySize(entry->id) != data.GetSize())
|
||||
{
|
||||
store.Free(entry->id);
|
||||
entry->id = ReserveOnStore(data.GetSize());
|
||||
|
||||
if (entry->id.IsEmpty())
|
||||
return false; // store is full
|
||||
}
|
||||
|
||||
// Update store data.
|
||||
if (!store.Store(entry->id, data.c_ptr(), data.GetSize(), entry->path))
|
||||
return false;
|
||||
store.Save();
|
||||
|
||||
entry->hash = SHA1::ComputeHexa(data);
|
||||
entry->size = data.GetSize();
|
||||
return true;
|
||||
}
|
||||
bool Cache::DeleteCacheEntry(Entry *entry)
|
||||
{
|
||||
if (entry->refc > 0)
|
||||
return false;
|
||||
|
||||
store.Free(entry->id);
|
||||
store.Save();
|
||||
|
||||
return entries.Remove(entry);
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
uint Cache::GetCaps() const { return io->GetCaps(); }
|
||||
|
||||
Handle *Cache::Open(const char *path, Mode mode)
|
||||
{
|
||||
if (mode == ModeRead)
|
||||
{
|
||||
Threading::MutexLock lock(&mutex);
|
||||
|
||||
// Update cache.
|
||||
Entry *entry = GetCacheEntry(path);
|
||||
|
||||
if (entry)
|
||||
{
|
||||
__LOG_V__ << "Entry match for '" << path << "'.\n";
|
||||
|
||||
if (entry->hash != io->Hash(path))
|
||||
{
|
||||
__LOG_V__ << "Hash mismatch for '" << path << "'.\n";
|
||||
|
||||
if (entry->refc > 0)
|
||||
__LOG_W__ << "IO::Cache: Support file '" << entry->path << "' has changed but its cached version is in use and cannot be updated.\n";
|
||||
else
|
||||
{
|
||||
if (!UpdateCacheEntry(entry)) // contention risk here due to the mutex lock and a potentially long update (eg. networked fs)
|
||||
DeleteCacheEntry(entry);
|
||||
|
||||
// Check for a match in updated cache.
|
||||
entry = GetCacheEntry(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
entry = CreateCacheEntry(path);
|
||||
|
||||
// Return handler to store fs.
|
||||
if (entry)
|
||||
{
|
||||
__LOG_V__ << "Opening '" << path << "' from cache.\n";
|
||||
|
||||
entry->last_use = Platform::Get().GetTime();
|
||||
entry->refc++;
|
||||
|
||||
return new CacheHandle(this, path, store.GetIO()->Open(entry->id, mode));
|
||||
}
|
||||
}
|
||||
|
||||
// Direct access to the underlying fs.
|
||||
return io->Open(path, mode);
|
||||
}
|
||||
void Cache::Close(Handle *h)
|
||||
{
|
||||
if (CacheHandle *c_h = (CacheHandle *)h)
|
||||
{
|
||||
Threading::MutexLock lock(&mutex);
|
||||
|
||||
if (Entry *entry = GetCacheEntry(c_h->path))
|
||||
entry->refc--;
|
||||
c_h->handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
bool Cache::Delete(const char *path)
|
||||
{
|
||||
/*
|
||||
[EJ] Minor synchronization issue warning.
|
||||
|
||||
If a cached handle is already in use and the support fs file is deleted
|
||||
the cached handle will remain valid. Further open requests will then
|
||||
unexpectedly succeed as long as a single cached handler remains open.
|
||||
|
||||
The correct fix would be to prevent deleting a support file as long as
|
||||
a cached entry with a non-zero reference count exists for it.
|
||||
*/
|
||||
return io->Delete(path);
|
||||
}
|
||||
|
||||
size_t Cache::Tell(Handle *h)
|
||||
{ return ((CacheHandle *)h)->handle->Tell(); }
|
||||
size_t Cache::Seek(Handle *h, ptrdiff_t offset, SeekRef seek)
|
||||
{ return ((CacheHandle *)h)->handle->Seek(offset, seek); }
|
||||
|
||||
size_t Cache::Read(Handle *h, void *data, size_t size)
|
||||
{ return ((CacheHandle *)h)->handle->Read(data, size); }
|
||||
size_t Cache::Write(Handle *h, const void *data, size_t size)
|
||||
{ return ((CacheHandle *)h)->handle->Write(data, size); }
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool Cache::SynchronizeWithStore(const char *store_path)
|
||||
{
|
||||
if (!store.Load(store_path))
|
||||
return false;
|
||||
|
||||
entries.Clear();
|
||||
|
||||
__LOG_H__ << "IO::Cache: Synchronizing with store.\n";
|
||||
|
||||
ListForeachPtr(DataStore::Entry *, e, store.GetEntries())
|
||||
{
|
||||
Entry *entry = new Entry;
|
||||
|
||||
entry->id = e->id;
|
||||
entry->size = e->size;
|
||||
entry->path = e->user;
|
||||
entry->hash = store.GetIO()->Hash(entry->id);
|
||||
|
||||
entries.Add(entry);
|
||||
}
|
||||
|
||||
__LOG__ << "Done, " << entries.GetCount() << " entries synchronized.\n";
|
||||
return true;
|
||||
}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
bool Cache::MkDir(const char *path)
|
||||
{ return io->MkDir(path); }
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
Cache::Cache(Base *base, Base *store_io, size_t store_size) : io(base), store(store_io, store_size) {}
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
CacheHandle::~CacheHandle()
|
||||
{ GetIOSystem()->Close(this); }
|
||||
//-----------------------------------------------------------------------------
|
||||
Reference in New Issue
Block a user