Global: adds general cache layer

This commit is contained in:
李通洲
2026-09-21 22:46:40 +08:00
parent 2f74d6be4d
commit 7f3209181e
14 changed files with 728 additions and 207 deletions
+48
View File
@@ -0,0 +1,48 @@
#pragma once
#include "fastfetch.h"
// A cache for a value that is built at most once per generation.
//
// The value itself is owned by the entry owner (usually a file scope `static`); the entry only
// remembers whether that value is still valid and knows how to drop it. `--dynamic-interval`
// starts a new generation for every round, dropping all the values that have been built so far,
// so the modules re-detect them instead of replaying the first round's snapshot.
//
// An entry registers itself the first time it is used. There is therefore no central list to keep
// in sync, and a cache cannot be forgotten in it. Do not use this type for state that is carried
// across calls to derive a rate (cpuusage, diskio, netio, top): that is a baseline rather than a
// snapshot, and dropping it makes the first sample of a round read as zero.
//
// The storage pointed to by `storage` must stay valid for the whole run; only the value behind it
// is dropped by an invalidation.
//
// Not thread safe: entries are only touched from a single thread, on a frame boundary, while the
// modules are not running.
typedef struct FFcacheEntry {
const char* name; // For diagnostics.
void* storage; // Owned by the entry owner. May be nullptr.
void (*init)(void* storage); // Optional. Runs whenever the entry is stale.
void (*destroy)(void* storage); // Optional. Must be idempotent, and tolerate a partially initialized storage.
bool initialized;
bool registered;
struct FFcacheEntry* next; // Internal: registry link.
} FFcacheEntry;
// Returns `entry->storage`, running `init` on it first when the entry is stale. The entry is marked
// initialized before `init` runs, so a call made from within `init` returns the partially built
// value instead of recursing.
[[gnu::nonnull(1)]] void* ffCacheGet(FFcacheEntry* entry);
// Staleness test for entries whose initialization needs arguments that are not part of the cache
// identity (such as the cover file request of `ffDetectMedia`) and therefore cannot live behind
// `init(void*)`. Returns true exactly once per generation: the caller must then build the value
// itself.
[[gnu::nonnull(1), nodiscard]] bool ffCacheBeginInit(FFcacheEntry* entry);
// Drops the cached value of one entry, so that the next request builds it again. Idempotent, and
// safe on an entry that has never been used.
[[gnu::nonnull(1)]] void ffCacheInvalidate(FFcacheEntry* entry);
// Drops every value that has been built so far. Called once per `--dynamic-interval` round.
void ffCacheInvalidateAll(void);
+53
View File
@@ -0,0 +1,53 @@
#include "common/FFcache.h"
// Entries are file scope statics owned by the cache owner, so they can be linked into a list
// without any allocation. The list holds every entry that has been used at least once: an entry
// that was never used has nothing to drop.
static FFcacheEntry* entries = nullptr;
static void registerEntry(FFcacheEntry* entry) {
if (entry->registered) {
return;
}
entry->registered = true;
entry->next = entries;
entries = entry;
}
void* ffCacheGet(FFcacheEntry* entry) {
if (ffCacheBeginInit(entry) && entry->init) {
entry->init(entry->storage);
}
return entry->storage;
}
bool ffCacheBeginInit(FFcacheEntry* entry) {
registerEntry(entry);
if (entry->initialized) {
return false;
}
entry->initialized = true;
return true;
}
void ffCacheInvalidate(FFcacheEntry* entry) {
if (!entry->initialized) {
return;
}
entry->initialized = false;
if (entry->destroy) {
entry->destroy(entry->storage);
}
}
void ffCacheInvalidateAll(void) {
for (FFcacheEntry* entry = entries; entry; entry = entry->next) {
ffCacheInvalidate(entry);
}
}
+39 -14
View File
@@ -1,22 +1,47 @@
#include "common/netif.h"
#include "common/FFcache.h"
#ifndef _WIN32
#include <net/if.h>
#include <netinet/in.h>
#endif
#include <string.h>
// netio, localip and dns resolve the default route on every round, and it can change while a
// `--dynamic-interval` run is going on: a laptop switching from Wi-Fi to Ethernet, a VPN coming up.
static FFNetifDefaultRouteResult defaultRouteV4;
static FFNetifDefaultRouteResult defaultRouteV6;
static void resetDefaultRoute(void* storage) {
// The impls only write the fields they found, so without this a round that finds no default
// route would keep reporting the interface of the previous one.
memset(storage, 0, sizeof(FFNetifDefaultRouteResult));
}
static void initDefaultRouteV4(void* storage) {
FFNetifDefaultRouteResult* result = storage;
result->status = ffNetifGetDefaultRouteImplV4(result) ? FF_NETIF_OK : FF_NETIF_INVALID;
}
static void initDefaultRouteV6(void* storage) {
FFNetifDefaultRouteResult* result = storage;
result->status = ffNetifGetDefaultRouteImplV6(result) ? FF_NETIF_OK : FF_NETIF_INVALID;
}
static FFcacheEntry ffCacheEntryDefaultRouteV4 = {
.name = "default route V4",
.storage = &defaultRouteV4,
.init = initDefaultRouteV4,
.destroy = resetDefaultRoute,
};
static FFcacheEntry ffCacheEntryDefaultRouteV6 = {
.name = "default route V6",
.storage = &defaultRouteV6,
.init = initDefaultRouteV6,
.destroy = resetDefaultRoute,
};
const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV4(void) {
static FFNetifDefaultRouteResult result;
if (result.status == FF_NETIF_UNINITIALIZED) {
result.status = ffNetifGetDefaultRouteImplV4(&result) ? FF_NETIF_OK : FF_NETIF_INVALID;
}
return &result;
return ffCacheGet(&ffCacheEntryDefaultRouteV4);
}
const FFNetifDefaultRouteResult* ffNetifGetDefaultRouteV6(void) {
static FFNetifDefaultRouteResult result;
if (result.status == FF_NETIF_UNINITIALIZED) {
result.status = ffNetifGetDefaultRouteImplV6(&result) ? FF_NETIF_OK : FF_NETIF_INVALID;
}
return &result;
return ffCacheGet(&ffCacheEntryDefaultRouteV6);
}