rathena/src/common/database.hpp
Aleos 5e6fd03848
Fixes a potential crash with database reloading (#7061)
* Fixes #7060.
* The cached YAML content will now properly reallocate the memory when the clear command is called.
Thanks to eppc0330!
2022-06-24 15:43:45 -04:00

205 lines
5.9 KiB
C++

// Copyright (c) rAthena Dev Teams - Licensed under GNU GPL
// For more information, see LICENCE in the main folder
#ifndef DATABASE_HPP
#define DATABASE_HPP
#include <unordered_map>
#include <vector>
#include <ryml_std.hpp>
#include <ryml.hpp>
#include "../config/core.hpp"
#include "cbasetypes.hpp"
#include "core.hpp"
#include "utilities.hpp"
class YamlDatabase{
// Internal stuff
private:
std::string type;
uint16 version;
uint16 minimumVersion;
std::string currentFile;
bool verifyCompatibility( const ryml::Tree& rootNode );
bool load( const std::string& path );
void parse( const ryml::Tree& rootNode );
void parseImports( const ryml::Tree& rootNode );
template <typename R> bool asType( const ryml::NodeRef& node, const std::string& name, R& out );
// These should be visible/usable by the implementation provider
protected:
ryml::Parser parser;
// Helper functions
bool nodeExists( const ryml::NodeRef& node, const std::string& name );
bool nodesExist( const ryml::NodeRef& node, std::initializer_list<const std::string> names );
int32 getLineNumber(const ryml::NodeRef& node);
int32 getColumnNumber(const ryml::NodeRef& node);
void invalidWarning( const ryml::NodeRef& node, const char* fmt, ... );
std::string getCurrentFile();
// Conversion functions
bool asBool(const ryml::NodeRef& node, const std::string &name, bool &out);
bool asInt16(const ryml::NodeRef& node, const std::string& name, int16& out );
bool asUInt16(const ryml::NodeRef& node, const std::string& name, uint16& out);
bool asInt32(const ryml::NodeRef& node, const std::string &name, int32 &out);
bool asUInt32(const ryml::NodeRef& node, const std::string &name, uint32 &out);
bool asInt64(const ryml::NodeRef& node, const std::string &name, int64 &out);
bool asUInt64(const ryml::NodeRef& node, const std::string &name, uint64 &out);
bool asFloat(const ryml::NodeRef& node, const std::string &name, float &out);
bool asDouble(const ryml::NodeRef& node, const std::string &name, double &out);
bool asString(const ryml::NodeRef& node, const std::string &name, std::string &out);
bool asUInt16Rate(const ryml::NodeRef& node, const std::string& name, uint16& out, uint16 maximum=10000);
bool asUInt32Rate(const ryml::NodeRef& node, const std::string& name, uint32& out, uint32 maximum=10000);
virtual void loadingFinished();
public:
YamlDatabase( const std::string& type_, uint16 version_, uint16 minimumVersion_ ){
this->type = type_;
this->version = version_;
this->minimumVersion = minimumVersion_;
}
YamlDatabase( const std::string& type_, uint16 version_ ) : YamlDatabase( type_, version_, version_ ){
// Empty since everything is handled by the real constructor
}
bool load();
bool reload();
// Functions that need to be implemented for each type
virtual void clear() = 0;
virtual const std::string getDefaultLocation() = 0;
virtual uint64 parseBodyNode( const ryml::NodeRef& node ) = 0;
};
template <typename keytype, typename datatype> class TypesafeYamlDatabase : public YamlDatabase{
protected:
std::unordered_map<keytype, std::shared_ptr<datatype>> data;
public:
TypesafeYamlDatabase( const std::string& type_, uint16 version_, uint16 minimumVersion_ ) : YamlDatabase( type_, version_, minimumVersion_ ){
}
TypesafeYamlDatabase( const std::string& type_, uint16 version_ ) : YamlDatabase( type_, version_, version_ ){
}
void clear() override{
this->data.clear();
}
bool empty(){
return this->data.empty();
}
bool exists( keytype key ){
return this->find( key ) != nullptr;
}
virtual std::shared_ptr<datatype> find( keytype key ){
auto it = this->data.find( key );
if( it != this->data.end() ){
return it->second;
}else{
return nullptr;
}
}
void put( keytype key, std::shared_ptr<datatype> ptr ){
this->data[key] = ptr;
}
typename std::unordered_map<keytype, std::shared_ptr<datatype>>::iterator begin(){
return this->data.begin();
}
typename std::unordered_map<keytype, std::shared_ptr<datatype>>::iterator end(){
return this->data.end();
}
size_t size(){
return this->data.size();
}
std::shared_ptr<datatype> random(){
if( this->empty() ){
return nullptr;
}
return rathena::util::umap_random( this->data );
}
void erase(keytype key) {
this->data.erase(key);
}
};
template <typename keytype, typename datatype> class TypesafeCachedYamlDatabase : public TypesafeYamlDatabase<keytype, datatype>{
private:
std::vector<std::shared_ptr<datatype>> cache;
public:
TypesafeCachedYamlDatabase( const std::string& type_, uint16 version_, uint16 minimumVersion_ ) : TypesafeYamlDatabase<keytype, datatype>( type_, version_, minimumVersion_ ){
}
TypesafeCachedYamlDatabase( const std::string& type_, uint16 version_ ) : TypesafeYamlDatabase<keytype, datatype>( type_, version_, version_ ){
}
void clear() override{
TypesafeYamlDatabase<keytype, datatype>::clear();
cache.clear();
cache.shrink_to_fit();
}
std::shared_ptr<datatype> find( keytype key ) override{
if( this->cache.empty() || key >= this->cache.size() ){
return TypesafeYamlDatabase<keytype, datatype>::find( key );
}else{
return cache[this->calculateCacheKey( key )];
}
}
std::vector<std::shared_ptr<datatype>> getCache() {
return this->cache;
}
virtual size_t calculateCacheKey( keytype key ){
return key;
}
void loadingFinished() override{
// Cache all known values
for (auto &pair : *this) {
// Calculate the key that should be used
size_t key = this->calculateCacheKey(pair.first);
// Check if the key fits into the current cache size
if (this->cache.capacity() <= key) {
// Double the current size, so we do not have to resize that often
size_t new_size = key * 2;
// Very important => initialize everything to nullptr
this->cache.resize(new_size, nullptr);
}
// Insert the value into the cache
this->cache[key] = pair.second;
}
// Free the memory that was allocated too much
this->cache.shrink_to_fit();
}
};
void do_init_database();
#endif /* DATABASE_HPP */