Draconis++ includes a sophisticated caching system that reduces redundant system calls and external API requests.
CacheManager overview
The CacheManager class provides a thread-safe, multi-tier caching system:
#include <Drac++/Utils/CacheManager.hpp>
using namespace draconis::utils::cache;
using namespace draconis::utils::types;
CacheManager cache;
Cache tiers
- In-memory cache: Fastest, volatile (lost on exit)
- Temporary directory: Persists until reboot or system cleanup
- Persistent directory: Stored in user cache directory
The cache automatically checks all tiers in order: in-memory → disk → fetch.
Cache locations
enum class CacheLocation : u8 {
InMemory, // Volatile, fastest
TempDirectory, // Persists until reboot
Persistent // User-level cache directory
};
| Platform | Persistent Cache Directory |
|---|
| Linux/BSD | ~/.cache/draconis++ |
| macOS | ~/Library/Caches/draconis++ |
| Windows | %LOCALAPPDATA%/draconis++/cache |
Cache policies
A CachePolicy defines where and how long to cache data:
struct CachePolicy {
CacheLocation location = CacheLocation::Persistent;
Option<seconds> ttl = days(1); // Time-to-live (default: 1 day)
};
Predefined policies
// In-memory only, no expiration
auto policy = CachePolicy::inMemory();
// Persistent, never expires
auto policy = CachePolicy::neverExpire();
// Temporary directory, no expiration
auto policy = CachePolicy::tempDirectory();
Custom policies
using std::chrono::hours;
using std::chrono::minutes;
// Cache for 2 hours in persistent storage
CachePolicy shortLived{
.location = CacheLocation::Persistent,
.ttl = hours(2)
};
// Cache for 30 minutes in temp directory
CachePolicy tempShortLived{
.location = CacheLocation::TempDirectory,
.ttl = minutes(30)
};
Use shorter TTLs for frequently changing data (e.g., weather) and longer TTLs for static data (e.g., OS version).
Using the cache
Basic usage with getOrSet
The getOrSet method checks the cache or calls a fetcher function:
auto fetchWeather() -> Result<WeatherData> {
CacheManager cache;
return cache.getOrSet<WeatherData>(
"weather_data",
[&]() -> Result<WeatherData> {
// This only runs on cache miss
return fetchFromWeatherAPI();
}
);
}
With custom policies
auto fetchWeather() -> Result<WeatherData> {
CacheManager cache;
// Override default policy
CachePolicy weatherPolicy{
.location = CacheLocation::Persistent,
.ttl = minutes(30) // Weather updates every 30 minutes
};
return cache.getOrSet<WeatherData>(
"weather_data",
weatherPolicy,
[&]() -> Result<WeatherData> {
return fetchFromWeatherAPI();
}
);
}
In-memory caching
auto getSystemInfo() -> Result<SystemInfo> {
CacheManager cache;
// Cache only in memory (fast, volatile)
return cache.getOrSet<SystemInfo>(
"system_info",
CachePolicy::inMemory(),
[&]() -> Result<SystemInfo> {
return collectSystemInfo();
}
);
}
Global cache policy
Set a default policy for all cache operations:
CacheManager cache;
// Set global default
CachePolicy globalPolicy{
.location = CacheLocation::Persistent,
.ttl = hours(6)
};
cache.setGlobalPolicy(globalPolicy);
// This uses the global policy
auto result = cache.getOrSet<Data>("key", fetcher);
// This overrides the global policy
auto result2 = cache.getOrSet<Data>("key", CachePolicy::inMemory(), fetcher);
Cache invalidation
Invalidate specific entry
cache.invalidate("weather_data");
Invalidate all entries
// Clear all caches (in-memory and on-disk)
u8 removedCount = cache.invalidateAll();
std::println("Removed {} cache entries", removedCount);
// With logging
u8 removedCount = cache.invalidateAll(true);
invalidateAll() removes all cache files from both persistent and temporary directories. Use with caution.
Bypassing the cache
Disable caching globally at runtime:
// Disable all caching (useful for --ignore-cache CLI flag)
CacheManager::ignoreCache = true;
// All getOrSet calls now skip the cache
auto result = cache.getOrSet<Data>("key", fetcher);
// This always calls fetcher, never reads/writes cache
This is perfect for implementing a --no-cache or --fresh command-line option.
Thread safety
All cache operations are thread-safe:
CacheManager cache; // Shared across threads
// Thread 1
std::thread t1([&]() {
auto data = cache.getOrSet<Data>("key1", fetcher1);
});
// Thread 2
std::thread t2([&]() {
auto data = cache.getOrSet<Data>("key2", fetcher2);
});
Internal mutex protection ensures safe concurrent access without external synchronization.
Cache serialization
The cache uses BEVE (Binary Encoding for Variant-like Entities) format for efficient serialization:
template <typename T>
struct CacheEntry {
T data; // The cached value
Option<u64> expires; // UNIX timestamp (None = no expiry)
};
Your cached types must be serializable with glaze. Most standard types and simple structs work automatically.
| Operation | In-Memory | Temp Directory | Persistent |
|---|
| Read | ~10 ns | ~1-5 ms | ~1-5 ms |
| Write | ~50 ns | ~5-20 ms | ~5-20 ms |
| Persistence | No | Until reboot | Indefinite |
Real-world example
using namespace draconis::utils::cache;
using namespace draconis::utils::types;
class WeatherService {
public:
auto getWeather(StringView city) -> Result<WeatherData> {
String cacheKey = std::format("weather_{}", city);
CachePolicy policy{
.location = CacheLocation::Persistent,
.ttl = minutes(30) // Weather data stale after 30 min
};
return m_cache.getOrSet<WeatherData>(
cacheKey,
policy,
[&]() -> Result<WeatherData> {
// Only called on cache miss
return fetchFromAPI(city);
}
);
}
auto clearCache() -> void {
m_cache.invalidateAll();
}
private:
CacheManager m_cache;
auto fetchFromAPI(StringView city) -> Result<WeatherData> {
// Network request to weather API
// ...
}
};
Best practices
Choose appropriate TTLs
// Static data: long or no TTL
CachePolicy osInfo{
.ttl = days(7) // OS version rarely changes
};
// Dynamic data: short TTL
CachePolicy weather{
.ttl = minutes(15) // Weather updates frequently
};
Use in-memory cache for session data
// Data only needed during current run
cache.getOrSet<SessionData>(
"session",
CachePolicy::inMemory(),
fetcher
);
Cache expensive operations
// Network requests
cache.getOrSet<ApiResponse>("api_data", fetchFromNetwork);
// Heavy computations
cache.getOrSet<ComputedResult>("computation", expensiveCompute);
// File I/O
cache.getOrSet<FileData>("file_data", readLargeFile);
Provide cache invalidation
class Service {
public:
auto refresh() -> void {
m_cache.invalidate("my_key");
}
auto clearAll() -> void {
m_cache.invalidateAll();
}
};
Compile-time caching control
Caching can be disabled at compile time:
#if DRAC_ENABLE_CACHING
// Caching enabled
#else
// All cache operations become no-ops
#endif
When caching is disabled, getOrSet always calls the fetcher function directly.
CacheManager implementation is in /home/daytona/workspace/source/include/Drac++/Utils/CacheManager.hpp:45.