optimised loading the mod cache (#1222)
Some checks are pending
Build coop / build-linux (push) Waiting to run
Build coop / build-steamos (push) Waiting to run
Build coop / build-windows-opengl (push) Waiting to run
Build coop / build-windows-directx (push) Waiting to run
Build coop / build-macos-arm (push) Waiting to run
Build coop / build-macos-intel (push) Waiting to run

* use hashmaps in mod cache

* use templates, suggested by peachy

an attempt to do what peachy is talking about

* that looks unusual

* change data to key parameter
This commit is contained in:
Isaac0-dev 2026-05-10 01:13:08 +10:00 committed by GitHub
parent 1e4ede799b
commit ec28e164db
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 173 additions and 178 deletions

View file

@ -3,186 +3,164 @@
#include <cstdint>
#include <cstddef>
#include <memory>
enum class MapType {
Ordered,
Unordered
};
#include <string>
// Ordered maps can be iterated by key order
// Unordered maps have the fastest lookup times (also called a hash map)
class HMap {
template <typename KeyType>
class IHMap {
public:
HMap(MapType type = MapType::Ordered) : mMapType(type) {
switch (mMapType) {
case MapType::Ordered:
mOrderedMap = std::make_unique<std::map<int64_t, void*>>();
break;
case MapType::Unordered:
mUnorderedMap = std::make_unique<std::unordered_map<int64_t, void*>>();
break;
}
virtual ~IHMap() = default;
virtual void* get(const KeyType& key) = 0;
virtual void put(const KeyType& key, void* value) = 0;
virtual void erase(const KeyType& key) = 0;
virtual void clear() = 0;
virtual size_t size() const = 0;
virtual void* begin() = 0;
virtual void* next() = 0;
};
template <typename KeyType, bool UseUnordered>
class HMap final : public IHMap<KeyType> {
private:
using MapType = typename std::conditional<
UseUnordered,
std::unordered_map<KeyType, void*>,
std::map<KeyType, void*>
>::type;
public:
void* get(const KeyType& key) override {
auto it = mMap.find(key);
return (it != mMap.end()) ? it->second : nullptr;
}
void* get(int64_t key) {
switch (mMapType) {
case MapType::Ordered: {
auto it = mOrderedMap->find(key);
if (it != mOrderedMap->end()) {
return it->second;
}
break;
}
case MapType::Unordered: {
auto it = mUnorderedMap->find(key);
if (it != mUnorderedMap->end()) {
return it->second;
}
break;
}
}
return nullptr;
void put(const KeyType& key, void* value) override {
mMap.insert_or_assign(key, value);
}
void put(int64_t key, void* value) {
switch (mMapType) {
case MapType::Ordered:
mOrderedMap->insert_or_assign(key, value);
break;
case MapType::Unordered:
mUnorderedMap->insert_or_assign(key, value);
break;
}
void erase(const KeyType& key) override {
mMap.erase(key);
}
void erase(int64_t key) {
switch (mMapType) {
case MapType::Ordered:
mOrderedMap->erase(key);
break;
case MapType::Unordered:
mUnorderedMap->erase(key);
break;
}
void clear() override {
mMap.clear();
}
void clear() {
switch (mMapType) {
case MapType::Ordered:
mOrderedMap->clear();
break;
case MapType::Unordered:
mUnorderedMap->clear();
break;
}
size_t size() const override {
return mMap.size();
}
size_t size() const {
switch (mMapType) {
case MapType::Ordered:
return mOrderedMap->size();
case MapType::Unordered:
return mUnorderedMap->size();
}
return 0;
void* begin() override {
if (mMap.empty()) return nullptr;
mIterator = mMap.begin();
return mIterator->second;
}
void* begin() {
switch (mMapType) {
case MapType::Ordered: {
auto& orderedMap = *mOrderedMap;
if (orderedMap.empty()) { return nullptr; }
mOrderedIterator = mOrderedMap->begin();
return mOrderedIterator->second;
}
case MapType::Unordered: {
auto& unorderedMap = *mUnorderedMap;
if (unorderedMap.empty()) { return nullptr; }
mUnorderedIterator = mUnorderedMap->begin();
return mUnorderedIterator->second;
}
}
return nullptr;
}
void* next() {
switch (mMapType) {
case MapType::Ordered: {
if (++mOrderedIterator != mOrderedMap->end()) {
return mOrderedIterator->second;
}
break;
}
case MapType::Unordered: {
if (++mUnorderedIterator != mUnorderedMap->end()) {
return mUnorderedIterator->second;
}
break;
}
void* next() override {
if (++mIterator != mMap.end()) {
return mIterator->second;
}
return nullptr;
}
private:
MapType mMapType;
std::unique_ptr<std::map<int64_t, void*>> mOrderedMap;
typename std::map<int64_t, void*>::iterator mOrderedIterator;
std::unique_ptr<std::unordered_map<int64_t, void*>> mUnorderedMap;
typename std::unordered_map<int64_t, void*>::iterator mUnorderedIterator;
MapType mMap;
typename MapType::iterator mIterator;
};
extern "C" {
void* hmap_create(bool useUnordered) {
return new HMap(useUnordered ? MapType::Unordered : MapType::Ordered);
if (useUnordered) {
return new HMap<int64_t, true>();
}
return new HMap<int64_t, false>();
}
void* hmap_get(void* map, int64_t key) {
if (!map) { return NULL; }
HMap* hmap = reinterpret_cast<HMap*>(map);
IHMap<int64_t>* hmap = static_cast<IHMap<int64_t>*>(map);
return hmap->get(key);
}
void hmap_put(void* map, int64_t key, void* value) {
if (!map) { return; }
HMap* hmap = reinterpret_cast<HMap*>(map);
IHMap<int64_t>* hmap = static_cast<IHMap<int64_t>*>(map);
hmap->put(key, value);
}
void hmap_del(void* map, int64_t key) {
if (!map) { return; }
HMap* hmap = reinterpret_cast<HMap*>(map);
IHMap<int64_t>* hmap = static_cast<IHMap<int64_t>*>(map);
hmap->erase(key);
}
void hmap_clear(void* map) {
if (!map) { return; }
HMap* hmap = reinterpret_cast<HMap*>(map);
IHMap<int64_t>* hmap = static_cast<IHMap<int64_t>*>(map);
hmap->clear();
}
void hmap_destroy(void* map) {
if (!map) { return; }
delete reinterpret_cast<HMap*>(map);
delete static_cast<IHMap<int64_t>*>(map);
}
size_t hmap_len(void* map) {
if (!map) { return 0; }
HMap* hmap = reinterpret_cast<HMap*>(map);
IHMap<int64_t>* hmap = static_cast<IHMap<int64_t>*>(map);
return hmap->size();
}
void* hmap_begin(void* map) {
if (!map) { return NULL; }
HMap* hmap = reinterpret_cast<HMap*>(map);
IHMap<int64_t>* hmap = static_cast<IHMap<int64_t>*>(map);
return hmap->begin();
}
void* hmap_next(void* map) {
if (!map) { return NULL; }
HMap* hmap = reinterpret_cast<HMap*>(map);
IHMap<int64_t>* hmap = static_cast<IHMap<int64_t>*>(map);
return hmap->next();
}
// Data/String map (for larger keys)
void* hmap_data_create(void) {
return new HMap<std::string, true>();
}
void* hmap_data_get(void* map, const char* key, size_t len) {
if (!map) { return NULL; }
std::string keyString(key, len);
return static_cast<IHMap<std::string>*>(map)->get(keyString);
}
void hmap_data_put(void* map, const char* key, size_t len, void* value) {
if (!map) { return; }
std::string keyString(key, len);
static_cast<IHMap<std::string>*>(map)->put(keyString, value);
}
void hmap_data_del(void* map, const char* key, size_t len) {
if (!map) { return; }
std::string keyString(key, len);
static_cast<IHMap<std::string>*>(map)->erase(keyString);
}
void hmap_data_clear(void* map) {
if (!map) { return; }
static_cast<IHMap<std::string>*>(map)->clear();
}
void hmap_data_destroy(void* map) {
if (!map) { return; }
delete static_cast<IHMap<std::string>*>(map);
}
size_t hmap_data_len(void* map) {
if (!map) { return 0; }
return static_cast<IHMap<std::string>*>(map)->size();
}
}

View file

@ -1,5 +1,7 @@
#ifndef DYNOS_CMAP_CPP_H
#define DYNOS_CMAP_CPP_H
#endif
#ifndef __cplusplus
void* hmap_create(bool useUnordered);
@ -13,5 +15,12 @@ size_t hmap_len(void* map);
void* hmap_begin(void* map);
void* hmap_next(void* map);
void* hmap_data_create(void);
void* hmap_data_get(void* map, const char* key, size_t len);
void hmap_data_put(void* map, const char* key, size_t len, void* value);
void hmap_data_del(void* map, const char* key, size_t len);
void hmap_data_clear(void* map);
void hmap_data_destroy(void* map);
size_t hmap_data_len(void* map);
#endif
#endif

View file

@ -10,27 +10,58 @@
#include "pc/utils/md5.h"
#include "pc/lua/smlua_hooks.h"
#include "pc/loading.h"
#include "data/dynos_cmap.cpp.h"
#define MOD_CACHE_FILENAME "mod.cache"
#define MOD_CACHE_VERSION 7
#define MD5_BUFFER_SIZE 1024
static struct ModCacheEntry* sModCacheEntries = NULL;
static struct ModCacheEntry** sModCacheEntries = NULL;
static size_t sModCacheLength = 0;
static size_t sModLengthCapacity = 0;
static void* sPathMap = NULL; // lookup by file path
static void* sDataMap = NULL; // lookup by file data md5 hash
static void mod_cache_remove_node(struct ModCacheEntry* node) {
// remove from hashmaps
if (node->path) {
hmap_data_del(sPathMap, node->path, strlen(node->path));
}
hmap_data_del(sDataMap, (const char*)node->dataHash, 16);
if (node->path) {
free(node->path);
node->path = NULL;
}
if (node != &sModCacheEntries[sModCacheLength - 1])
memcpy(node, &sModCacheEntries[sModCacheLength - 1], sizeof(struct ModCacheEntry));
size_t index = node->arrayIndex;
size_t lastIndex = sModCacheLength - 1;
if (index != lastIndex) {
struct ModCacheEntry* lastNode = sModCacheEntries[lastIndex];
sModCacheEntries[index] = lastNode;
lastNode->arrayIndex = index;
}
sModCacheEntries[lastIndex] = NULL;
sModCacheLength--;
free(node);
}
void mod_cache_shutdown(void) {
LOG_INFO("Shutting down mod cache.");
// destroy maps
hmap_data_destroy(sPathMap);
hmap_data_destroy(sDataMap);
sPathMap = NULL;
sDataMap = NULL;
for (size_t i = 0; i < sModCacheLength; i++) {
if (sModCacheEntries[i]->path) {
free(sModCacheEntries[i]->path);
}
free(sModCacheEntries[i]);
}
sModCacheLength = 0;
sModLengthCapacity = 0;
free(sModCacheEntries);
@ -78,15 +109,6 @@ void mod_cache_md5(const char* inPath, u8* outDataPath) {
MD5_Final(outDataPath, &ctx);
}
static u64 mod_cache_fnv1a(const char* str) {
u64 hash = 0xCBF29CE484222325;
while (*str) {
hash *= 0x100000001B3;
hash ^= *str++;
}
return hash;
}
static bool mod_cache_is_valid(struct ModCacheEntry* node) {
if (node == NULL || node->path == NULL || strlen(node->path) == 0) {
return false;
@ -98,38 +120,24 @@ static bool mod_cache_is_valid(struct ModCacheEntry* node) {
struct ModCacheEntry* mod_cache_get_from_hash(u8* dataHash) {
if (dataHash == NULL) { return NULL; }
for (size_t i = 0; i < sModCacheLength;) {
struct ModCacheEntry* node = &sModCacheEntries[i];
if (!memcmp(node->dataHash, dataHash, 16)) {
if (mod_cache_is_valid(node)) {
return node;
} else {
mod_cache_remove_node(node);
continue;
}
}
i++;
}
struct ModCacheEntry* node = hmap_data_get(sDataMap, (const char*) dataHash, 16);
if (!node) { return NULL; }
if (mod_cache_is_valid(node)) { return node; }
mod_cache_remove_node(node);
return NULL;
}
struct ModCacheEntry* mod_cache_get_from_path(const char* path, bool validate) {
if (path == NULL || strlen(path) == 0) { return NULL; }
u64 pathHash = mod_cache_fnv1a(path);
for (size_t i = 0; i < sModCacheLength;) {
struct ModCacheEntry* node = &sModCacheEntries[i];
if (node->pathHash == pathHash && !strcmp(node->path, path)) {
if (!validate) {
return node;
} else if (mod_cache_is_valid(node)) {
return node;
} else {
mod_cache_remove_node(node);
continue;
}
}
i++;
}
struct ModCacheEntry* node = hmap_data_get(sPathMap, path, strlen(path));
if (!node) { return NULL; }
if (!validate) { return node; }
if (mod_cache_is_valid(node)) { return node; }
mod_cache_remove_node(node);
return NULL;
}
@ -148,7 +156,6 @@ void mod_cache_add_internal(u8* dataHash, u64 lastLoaded, char* inPath) {
return;
}
normalize_path((char*)path);
u64 pathHash = mod_cache_fnv1a(path);
bool foundNonZero = false;
for (u8 i = 0; i < 16; i++) {
@ -163,34 +170,35 @@ void mod_cache_add_internal(u8* dataHash, u64 lastLoaded, char* inPath) {
return;
}
if (!sPathMap) { sPathMap = hmap_data_create(); }
if (!sDataMap) { sDataMap = hmap_data_create(); }
struct ModCacheEntry* existing = hmap_data_get(sPathMap, path, strlen(path));
if (existing) {
mod_cache_remove_node(existing);
}
if (sModCacheEntries == NULL) {
sModLengthCapacity = 16;
sModCacheLength = 0;
sModCacheEntries = calloc(sModLengthCapacity, sizeof(struct ModCacheEntry));
sModCacheEntries = calloc(sModLengthCapacity, sizeof(struct ModCacheEntry *));
} else if (sModCacheLength == sModLengthCapacity) {
sModLengthCapacity *= 2;
sModCacheEntries = realloc(sModCacheEntries, sizeof(struct ModCacheEntry) * sModLengthCapacity);
sModCacheEntries = realloc(sModCacheEntries, sizeof(struct ModCacheEntry *) * sModLengthCapacity);
}
struct ModCacheEntry node = {};
memcpy(node.dataHash, dataHash, sizeof(u8) * 16);
struct ModCacheEntry *node = malloc(sizeof(struct ModCacheEntry));
sModCacheEntries[sModCacheLength] = node;
memcpy(node->dataHash, dataHash, sizeof(u8) * 16);
if (lastLoaded == 0) { lastLoaded = clock(); }
node.lastLoaded = lastLoaded;
node.path = (char*)path;
node.pathHash = pathHash;
node->lastLoaded = lastLoaded;
node->path = path;
node->arrayIndex = sModCacheLength;
sModCacheLength++;
for (size_t i = 0; i < sModCacheLength;) {
struct ModCacheEntry* n = &sModCacheEntries[i];
// found old hash, remove it
if (n->pathHash == pathHash && !strcmp(n->path, path)) {
LOG_INFO("Removing old node: %s", node->path);
mod_cache_remove_node(n);
} else {
i++;
}
}
memcpy(&sModCacheEntries[sModCacheLength++], &node, sizeof(node));
// insert into hashmaps
hmap_data_put(sPathMap, path, strlen(path), node);
hmap_data_put(sDataMap, (const char*) dataHash, 16, node);
}
void mod_cache_add(struct Mod* mod, struct ModFile* file, bool useFilePath) {
@ -325,7 +333,7 @@ void mod_cache_save(void) {
fwrite(&t, sizeof(u8), 1, fp);
for (size_t i = 0; i < sModCacheLength; i++) {
struct ModCacheEntry* node = &sModCacheEntries[i];
struct ModCacheEntry *node = sModCacheEntries[i];
if (node->path == NULL) { continue; }
u16 pathLen = strlen(node->path);
if (pathLen == 0) { continue; }

View file

@ -7,7 +7,7 @@ struct ModCacheEntry {
u8 dataHash[16];
u64 lastLoaded;
char* path;
u64 pathHash;
size_t arrayIndex;
};
void mod_cache_md5(const char* inPath, u8* outDataPath);