yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Ellie Hermaszewskaformatf65d756bf

master
7.4 KiB290 linesraw
1#include "slang-persistent-cache.h"
2
3#include "../core/slang-blob.h"
4#include "../core/slang-io.h"
5#include "../core/slang-stream.h"
6#include "../core/slang-string-util.h"
7
8namespace Slang
9{
10
11PersistentCache::PersistentCache(const Desc& desc)
12{
13    m_cacheDirectory = Path::simplify(desc.directory);
14    Path::createDirectory(m_cacheDirectory);
15
16    m_lockFileName = Path::simplify(m_cacheDirectory + "/lock");
17    m_indexFileName = Path::simplify(m_cacheDirectory + "/index");
18
19    m_lockFile.open(m_lockFileName);
20
21    m_maxEntryCount = desc.maxEntryCount;
22
23    resetStats();
24
25    initialize();
26}
27
28PersistentCache::~PersistentCache() {}
29
30SlangResult PersistentCache::clear()
31{
32    if (!m_lockFile.isOpen())
33    {
34        return SLANG_E_CANNOT_OPEN;
35    }
36
37    // Acquire the exclusive lock.
38    std::lock_guard<std::mutex> mutexLock(m_mutex);
39    LockFileGuard fileLock(m_lockFile);
40
41    struct Visitor : Path::Visitor
42    {
43        const String& directory;
44        const String& lockFileName;
45
46        Visitor(const String& directory, const String& lockFileName)
47            : directory(directory), lockFileName(lockFileName)
48        {
49        }
50
51        void accept(Path::Type type, const UnownedStringSlice& fileName) SLANG_OVERRIDE
52        {
53            String fullPath = Path::simplify(directory + "/" + fileName);
54            ;
55            if (type == Path::Type::File && lockFileName != fullPath)
56            {
57                Path::remove(fullPath);
58            }
59        }
60    };
61
62    Visitor visitor(m_cacheDirectory, m_lockFileName);
63    Path::find(m_cacheDirectory, nullptr, &visitor);
64
65    m_stats.entryCount = 0;
66
67    return SLANG_OK;
68}
69
70void PersistentCache::resetStats()
71{
72    m_stats.entryCount = 0;
73    m_stats.hitCount = 0;
74    m_stats.missCount = 0;
75}
76
77SlangResult PersistentCache::readEntry(const Key& key, ISlangBlob** outData)
78{
79    // Be pessimistic and assume we have a cache miss.
80    ++m_stats.missCount;
81
82    if (!m_lockFile.isOpen())
83    {
84        return SLANG_E_CANNOT_OPEN;
85    }
86
87    // Acquire the exclusive lock.
88    std::lock_guard<std::mutex> mutexLock(m_mutex);
89    LockFileGuard fileLock(m_lockFile);
90
91    // Return if index does not exist.
92    if (!File::exists(m_indexFileName))
93    {
94        return SLANG_E_NOT_FOUND;
95    }
96
97    // Read the cache index.
98    CacheIndex cacheIndex;
99    SLANG_RETURN_ON_FAIL(readIndex(m_indexFileName, cacheIndex));
100
101    // Increase the age of all entries in the cache.
102    for (auto& entry : cacheIndex)
103    {
104        ++entry.age;
105    }
106
107    // Find the entry.
108    Index entryIndex =
109        cacheIndex.findFirstIndex([&key](const CacheEntry& entry) { return entry.key == key; });
110    if (entryIndex == -1)
111    {
112        return SLANG_E_NOT_FOUND;
113    }
114
115    // Read the entry.
116    String entryFileName = getEntryFileName(key);
117    ScopedAllocation data;
118    SlangResult result = File::readAllBytes(entryFileName, data);
119    if (result == SLANG_OK)
120    {
121        --m_stats.missCount;
122        ++m_stats.hitCount;
123        cacheIndex[entryIndex].age = 0;
124        auto blob = RawBlob::moveCreate(data);
125        *outData = blob.detach();
126    }
127    else
128    {
129        cacheIndex.removeAt(entryIndex);
130    }
131
132    // Write the cache index.
133    SLANG_RETURN_ON_FAIL(writeIndex(m_indexFileName, cacheIndex));
134    m_stats.entryCount = (Count)cacheIndex.getCount();
135
136    return result;
137}
138
139SlangResult PersistentCache::writeEntry(const Key& key, ISlangBlob* data)
140{
141    SLANG_ASSERT(data);
142
143    if (!m_lockFile.isOpen())
144    {
145        return SLANG_E_CANNOT_OPEN;
146    }
147
148    // Acquire the exclusive lock.
149    std::lock_guard<std::mutex> mutexLock(m_mutex);
150    LockFileGuard fileLock(m_lockFile);
151
152    // Read the cache index.
153    // We ignore any errors when reading the index and just write a new one.
154    CacheIndex cacheIndex;
155    readIndex(m_indexFileName, cacheIndex);
156
157    // Increase the age of all entries in the cache and get the index of
158    // the oldest entry.
159    Index oldestEntryIndex = -1;
160    uint32_t oldestEntryAge = 0;
161    for (Index entryIndex = 0; entryIndex < cacheIndex.getCount(); ++entryIndex)
162    {
163        auto& entry = cacheIndex[entryIndex];
164        ++entry.age;
165        if (entry.age > oldestEntryAge)
166        {
167            oldestEntryIndex = entryIndex;
168            oldestEntryAge = entry.age;
169        }
170    }
171
172    // Write the cache entry.
173    String entryFileName = getEntryFileName(key);
174    SLANG_RETURN_ON_FAIL(
175        File::writeAllBytes(entryFileName, data->getBufferPointer(), data->getBufferSize()));
176
177    // Update the index.
178    if (m_maxEntryCount > 0 && cacheIndex.getCount() >= m_maxEntryCount)
179    {
180        // Replace oldest entry.
181        SLANG_ASSERT(oldestEntryIndex >= 0);
182        File::remove(getEntryFileName(cacheIndex[oldestEntryIndex].key));
183        cacheIndex[oldestEntryIndex] = CacheEntry{key, 0};
184    }
185    else
186    {
187        // Add new entry.
188        cacheIndex.add(CacheEntry{key, 0});
189    }
190
191    // Write the cache index.
192    SlangResult result = writeIndex(m_indexFileName, cacheIndex);
193    if (result == SLANG_OK)
194    {
195        m_stats.entryCount = (Count)cacheIndex.getCount();
196    }
197    else
198    {
199        // If writing the index failed, remove the entry file to avoid growing the cache.
200        Path::remove(entryFileName);
201    }
202
203    return result;
204}
205
206SlangResult PersistentCache::initialize()
207{
208    if (!m_lockFile.isOpen())
209    {
210        return SLANG_E_CANNOT_OPEN;
211    }
212
213    // Acquire the exclusive lock.
214    std::lock_guard<std::mutex> mutexLock(m_mutex);
215    LockFileGuard fileLock(m_lockFile);
216
217    CacheIndex cacheIndex;
218    if (SLANG_SUCCEEDED(readIndex(m_indexFileName, cacheIndex)))
219    {
220        m_stats.entryCount = (Count)cacheIndex.getCount();
221    }
222
223    return SLANG_OK;
224}
225
226String PersistentCache::getEntryFileName(const Key& key)
227{
228    StringBuilder str;
229    str << m_cacheDirectory << "/" << key.toString();
230    return str;
231}
232
233struct CacheIndexHeader
234{
235    char magic[4];
236    uint32_t version;
237    uint32_t count;
238    uint32_t reserved;
239};
240
241static const char* kMagic = "SLS$";
242static const uint32_t kVersion = 1;
243
244SlangResult PersistentCache::readIndex(const String& fileName, CacheIndex& outIndex)
245{
246    FileStream fs;
247    SLANG_RETURN_ON_FAIL(fs.init(fileName, FileMode::Open));
248
249    // Get file size.
250    SLANG_RETURN_ON_FAIL(fs.seek(SeekOrigin::End, 0));
251    size_t fileSize = (size_t)fs.getPosition();
252    SLANG_RETURN_ON_FAIL(fs.seek(SeekOrigin::Start, 0));
253
254    CacheIndexHeader header;
255    SLANG_RETURN_ON_FAIL(fs.readExactly(&header, sizeof(header)));
256    if (::memcmp(header.magic, kMagic, 4) != 0 || header.version != kVersion)
257    {
258        return SLANG_E_INTERNAL_FAIL;
259    }
260
261    // Return if payload does not have the right size.
262    if (header.count * sizeof(CacheEntry) != fileSize - sizeof(header))
263    {
264        return SLANG_E_INTERNAL_FAIL;
265    }
266
267    outIndex.setCount(header.count);
268    SLANG_RETURN_ON_FAIL(fs.readExactly(outIndex.getBuffer(), header.count * sizeof(CacheEntry)));
269
270    return SLANG_OK;
271}
272
273SlangResult PersistentCache::writeIndex(const String& fileName, const CacheIndex& index)
274{
275    FileStream fs;
276    SLANG_RETURN_ON_FAIL(fs.init(fileName, FileMode::Create));
277
278    CacheIndexHeader header;
279    ::memcpy(header.magic, kMagic, 4);
280    header.version = kVersion;
281    header.count = (uint32_t)index.getCount();
282    header.reserved = 0;
283    SLANG_RETURN_ON_FAIL(fs.write(&header, sizeof(header)));
284
285    SLANG_RETURN_ON_FAIL(fs.write(index.getBuffer(), index.getCount() * sizeof(CacheEntry)));
286
287    return SLANG_OK;
288}
289
290} // namespace Slang