Move highscores to DB.

This commit is contained in:
Christian Speckner 2021-01-03 22:41:59 +01:00
parent 3171114a47
commit 8204d0fb6e
22 changed files with 193 additions and 119 deletions

View File

@ -64,6 +64,12 @@ HighScoresManager::HighScoresManager(OSystem& osystem)
{
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void HighScoresManager::setRepository(shared_ptr<CompositeKeyValueRepositoryAtomic> repo)
{
myHighscoreRepository = repo;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int16 HighScoresManager::peek(uInt16 addr) const
{
@ -559,97 +565,7 @@ Int32 HighScoresManager::fromBCD(uInt8 bcd) const
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void HighScoresManager::saveHighScores(const string& cartName, ScoresData& data) const
{
ostringstream buf;
buf << myOSystem.stateDir() << cartName << ".hs" << data.variation;
// Make sure the file can be opened for writing
FilesystemNode node(buf.str());
if(!node.isWritable())
{
buf.str("");
buf << "Can't open/save to high scores file for variation " << data.variation;
myOSystem.frameBuffer().showTextMessage(buf.str());
}
// Do a complete high data save
if(!save(node, data))
{
buf.str("");
buf << "Error saving high scores for variation" << data.variation;
myOSystem.frameBuffer().showTextMessage(buf.str());
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void HighScoresManager::loadHighScores(const string& cartName, ScoresData& data)
{
for(uInt32 r = 0; r < NUM_RANKS; ++r)
{
data.scores[r].score = 0;
data.scores[r].special = 0;
data.scores[r].name = "";
data.scores[r].date = "";
}
ostringstream buf;
buf << myOSystem.stateDir() << cartName << ".hs" << data.variation;
FilesystemNode node(buf.str());
stringstream in;
// Make sure the file can be opened
try {
node.read(in);
}
catch(...) { return; }
bool invalid = false;
try {
string highscores;
buf.str("");
if(getline(in, highscores) && highscores.length() != 0)
{
const json hsObject = json::parse(highscores);
if(hsObject.contains(DATA))
{
const json hsData = hsObject.at(DATA);
// First test if we have a valid header
// If so, do a complete high data load
if(!hsData.contains(VERSION) || hsData.at(VERSION) != HIGHSCORE_HEADER)
buf << "Error: Incompatible high scores file for variation "
<< data.variation << ".";
else
{
if(!load(hsData, data)
|| !hsData.contains(PROPCHECK) || hsData.at(PROPCHECK) != md5Props()
|| !hsObject.contains(CHECKSUM) || hsObject.at(CHECKSUM) != MD5::hash(hsData.dump()))
invalid = true;
else
return;
}
}
else
invalid = true;
}
}
catch(...) { invalid = true; }
if(invalid)
buf << "Error: Invalid data in high scores file for variation " << data.variation << ".";
myOSystem.frameBuffer().showTextMessage(buf.str());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool HighScoresManager::save(FilesystemNode& node, const ScoresData& data) const
void HighScoresManager::saveHighScores(ScoresData& data) const
{
try
{
@ -681,15 +597,64 @@ bool HighScoresManager::save(FilesystemNode& node, const ScoresData& data) const
hsObject[DATA] = hsData;
hsObject[CHECKSUM] = MD5::hash(hsData.dump());
stringstream ss(hsObject.dump());
node.write(ss);
myHighscoreRepository->save(data.md5, to_string(data.variation), hsObject.dump(2));
}
catch(...)
{
cerr << "ERROR: HighScoresManager::save() exception\n";
return false;
}
return true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void HighScoresManager::loadHighScores(ScoresData& data)
{
for(uInt32 r = 0; r < NUM_RANKS; ++r)
{
data.scores[r].score = 0;
data.scores[r].special = 0;
data.scores[r].name = "";
data.scores[r].date = "";
}
ostringstream buf;
bool invalid = false;
try {
Variant serializedHighscore;
if(myHighscoreRepository->get(data.md5, to_string(data.variation), serializedHighscore))
{
const json hsObject = json::parse(serializedHighscore.toString());
if(hsObject.contains(DATA))
{
const json hsData = hsObject.at(DATA);
// First test if we have a valid header
// If so, do a complete high data load
if(!hsData.contains(VERSION) || hsData.at(VERSION) != HIGHSCORE_HEADER)
buf << "Error: Incompatible high scores file for variation "
<< data.variation << ".";
else
{
if(!load(hsData, data)
|| !hsData.contains(PROPCHECK) || hsData.at(PROPCHECK) != md5Props()
|| !hsObject.contains(CHECKSUM) || hsObject.at(CHECKSUM) != MD5::hash(hsData.dump()))
invalid = true;
else
return;
}
}
else
invalid = true;
}
}
catch(...) { invalid = true; }
if (invalid)
buf << "Error: Invalid data in high scores file for variation " << data.variation << ".";
myOSystem.frameBuffer().showTextMessage(buf.str());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -25,6 +25,8 @@ class OSystem;
#include "Props.hxx"
#include "json_lib.hxx"
#include "FSNode.hxx"
#include "repository/CompositeKeyValueRepository.hxx"
#include "repository/CompositeKeyValueRepositoryNoop.hxx"
using json = nlohmann::json;
@ -95,6 +97,7 @@ class HighScoresManager
explicit HighScoresManager(OSystem& osystem);
virtual ~HighScoresManager() = default;
void setRepository(shared_ptr<CompositeKeyValueRepositoryAtomic> repo);
// check if high score data has been defined
bool enabled() const;
@ -147,8 +150,8 @@ class HighScoresManager
// Peek into memory
Int16 peek(uInt16 addr) const;
void saveHighScores(const string& cartName, HSM::ScoresData& scores) const;
void loadHighScores(const string& cartName, HSM::ScoresData& scores);
void loadHighScores(HSM::ScoresData& scores);
void saveHighScores(HSM::ScoresData& scores) const;
private:
static const string VARIATIONS_COUNT;
@ -230,16 +233,6 @@ class HighScoresManager
uInt16 fromHexStr(const string& addr) const;
Int32 fromBCD(uInt8 bcd) const;
/**
Saves the current high scores for this game and variation to the given file system node.
@param node The file system node to save to.
@param scores The saved high score data
@return The result of the save. True on success, false on failure.
*/
bool save(FilesystemNode& node, const HSM::ScoresData& scores) const;
/**
Loads the current high scores for this game and variation from the given JSON object.
@ -254,6 +247,9 @@ class HighScoresManager
// Reference to the osystem object
OSystem& myOSystem;
shared_ptr<CompositeKeyValueRepositoryAtomic> myHighscoreRepository
= make_shared<CompositeKeyValueRepositoryNoop>();
private:
// Following constructors and assignment operators not supported
HighScoresManager() = delete;

View File

@ -35,7 +35,8 @@ MODULE_OBJS := \
src/common/repository/KeyValueRepositoryPropertyFile.o \
src/common/repository/KeyValueRepositoryJsonFile.o \
src/common/repository/KeyValueRepositoryConfigfile.o \
src/common/repository/CompositeKVRJsonAdapter.o
src/common/repository/CompositeKVRJsonAdapter.o \
src/common/repository/CompositeKeyValueRepository.o
MODULE_DIRS += \
src/common

View File

@ -30,10 +30,10 @@ namespace {
std::map<string, Variant> load() override {
if (!myKvr.has(myKey)) return std::map<string, Variant>();
string serialized;
Variant serialized;
myKvr.get(myKey, serialized);
stringstream in{serialized};
stringstream in{serialized.toString()};
return KeyValueRepositoryJsonFile::load(in);
}

View File

@ -0,0 +1,51 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "repository/CompositeKeyValueRepository.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CompositeKeyValueRepositoryAtomic::get(const string& key1, const string& key2, Variant& value)
{
return getAtomic(key1)->get(key2, value);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
shared_ptr<KeyValueRepositoryAtomic> CompositeKeyValueRepositoryAtomic::getAtomic(const string& key)
{
auto repo = get(key);
return shared_ptr<KeyValueRepositoryAtomic>(repo, repo->atomic());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CompositeKeyValueRepositoryAtomic::save(const string& key1, const string& key2, const Variant& value)
{
return getAtomic(key1)->save(key2, value);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CompositeKeyValueRepositoryAtomic::has(const string& key1, const string& key2)
{
return getAtomic(key1)->has(key2);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CompositeKeyValueRepositoryAtomic::remove(const string& key1, const string key2)
{
getAtomic(key1)->remove(key2);
}

View File

@ -23,6 +23,8 @@
#include "KeyValueRepository.hxx"
#include "bspf.hxx"
class CompositeKeyValueRepositoryAtomic;
class CompositeKeyValueRepository
{
public:
@ -34,6 +36,28 @@ class CompositeKeyValueRepository
virtual bool has(const string& key) = 0;
virtual void remove(const string& key) = 0;
virtual CompositeKeyValueRepositoryAtomic* atomic() { return nullptr; }
};
class CompositeKeyValueRepositoryAtomic : public CompositeKeyValueRepository
{
public:
using CompositeKeyValueRepository::get;
using CompositeKeyValueRepository::remove;
using CompositeKeyValueRepository::has;
virtual bool get(const string& key1, const string& key2, Variant& value);
virtual shared_ptr<KeyValueRepositoryAtomic> getAtomic(const string& key);
virtual bool save(const string& key1, const string& key2, const Variant& value);
virtual bool has(const string& key1, const string& key2);
virtual void remove(const string& key1, const string key2);
CompositeKeyValueRepositoryAtomic* atomic() override { return this; }
};
#endif // COMPOSITE_KEY_VALUE_REPOSITORY_HXX

View File

@ -22,9 +22,12 @@
#include "repository/KeyValueRepositoryNoop.hxx"
#include "bspf.hxx"
class CompositeKeyValueRepositoryNoop : public CompositeKeyValueRepository
class CompositeKeyValueRepositoryNoop : public CompositeKeyValueRepositoryAtomic
{
public:
using CompositeKeyValueRepositoryAtomic::has;
using CompositeKeyValueRepositoryAtomic::remove;
using CompositeKeyValueRepositoryAtomic::get;
shared_ptr<KeyValueRepository> get(const string& key) { return make_shared<KeyValueRepositoryNoop>(); }

View File

@ -44,7 +44,7 @@ class KeyValueRepositoryAtomic : public KeyValueRepository {
virtual bool has(const string& key) = 0;
virtual bool get(const string& key, string& value) = 0;
virtual bool get(const string& key, Variant& value) = 0;
virtual bool save(const string& key, const Variant& value) = 0;

View File

@ -30,7 +30,7 @@ class KeyValueRepositoryNoop : public KeyValueRepositoryAtomic
bool has(const string& key) override { return false; }
bool get(const string& key, string& value) override { return false; }
bool get(const string& key, Variant& value) override { return false; }
bool save(const std::map<string, Variant>& values) override { return false; }

View File

@ -61,7 +61,7 @@ bool AbstractKeyValueRepositorySqlite::has(const string& key)
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AbstractKeyValueRepositorySqlite::get(const string& key, string& value)
bool AbstractKeyValueRepositorySqlite::get(const string& key, Variant& value)
{
try {
SqliteStatement& stmt{stmtSelectOne(key)};

View File

@ -12,7 +12,7 @@ class AbstractKeyValueRepositorySqlite : public KeyValueRepositoryAtomic
bool has(const string& key) override;
bool get(const string& key, string& value) override;
bool get(const string& key, Variant& value) override;
std::map<string, Variant> load() override;

View File

@ -24,8 +24,11 @@
#include "SqliteStatement.hxx"
#include "AbstractKeyValueRepositorySqlite.hxx"
class CompositeKeyValueRepositorySqlite : public CompositeKeyValueRepository {
class CompositeKeyValueRepositorySqlite : public CompositeKeyValueRepositoryAtomic {
public:
using CompositeKeyValueRepositoryAtomic::has;
using CompositeKeyValueRepositoryAtomic::remove;
using CompositeKeyValueRepositoryAtomic::get;
CompositeKeyValueRepositorySqlite(
SqliteDatabase& db,

View File

@ -26,6 +26,7 @@
#include "repository/KeyValueRepositoryConfigfile.hxx"
#include "repository/KeyValueRepositoryPropertyFile.hxx"
#include "KeyValueRepositorySqlite.hxx"
#include "CompositeKeyValueRepositorySqlite.hxx"
#include "SqliteStatement.hxx"
#include "FSNode.hxx"
@ -59,6 +60,10 @@ void StellaDb::initialize()
propertyRepositoryHost->initialize();
myPropertyRepositoryHost = std::move(propertyRepositoryHost);
auto highscoreRepository = make_unique<CompositeKeyValueRepositorySqlite>(*myDb, "highscores", "md5", "variation", "highscore_data");
highscoreRepository->initialize();
myHighscoreRepository = std::move(highscoreRepository);
myPropertyRepository = make_unique<CompositeKVRJsonAdapter>(*myPropertyRepositoryHost);
if (myDb->getUserVersion() == 0) {

View File

@ -33,8 +33,8 @@ class StellaDb
void initialize();
KeyValueRepositoryAtomic& settingsRepository() const { return *mySettingsRepository; }
CompositeKeyValueRepository& propertyRepository() const { return *myPropertyRepository; }
CompositeKeyValueRepositoryAtomic& highscoreRepository() const { return *myHighscoreRepository; }
const string databaseFileName() const;
@ -59,6 +59,7 @@ class StellaDb
unique_ptr<KeyValueRepositoryAtomic> mySettingsRepository;
unique_ptr<KeyValueRepositoryAtomic> myPropertyRepositoryHost;
unique_ptr<CompositeKeyValueRepository> myPropertyRepository;
unique_ptr<CompositeKeyValueRepositoryAtomic> myHighscoreRepository;
};
#endif // STELLA_DB_HXX

View File

@ -181,6 +181,8 @@ bool OSystem::initialize(const Settings::Options& options)
myMessageMenu = make_unique<MessageMenu>(*this);
myTimeMachine = make_unique<TimeMachine>(*this);
myLauncher = make_unique<Launcher>(*this);
myHighScoresManager->setRepository(getHighscoreRepository());
#endif
#ifdef PNG_SUPPORT
@ -222,6 +224,7 @@ void OSystem::loadConfig(const Settings::Options& options)
mySettings->setRepository(getSettingsRepository());
myPropSet->setRepository(getPropertyRepository());
mySettings->load(options);
// userDir is NOT affected by '-baseDir'and '-basedirinapp' params

View File

@ -443,6 +443,8 @@ class OSystem
virtual shared_ptr<CompositeKeyValueRepository> getPropertyRepository() = 0;
virtual shared_ptr<CompositeKeyValueRepositoryAtomic> getHighscoreRepository() = 0;
protected:
//////////////////////////////////////////////////////////////////////

View File

@ -42,3 +42,9 @@ shared_ptr<CompositeKeyValueRepository> OSystemStandalone::getPropertyRepository
{
return shared_ptr<CompositeKeyValueRepository>(myStellaDb, &myStellaDb->propertyRepository());
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
shared_ptr<CompositeKeyValueRepositoryAtomic> OSystemStandalone::getHighscoreRepository()
{
return shared_ptr<CompositeKeyValueRepositoryAtomic>(myStellaDb, &myStellaDb->highscoreRepository());
}

View File

@ -32,6 +32,8 @@ class OSystemStandalone : public OSystem
shared_ptr<CompositeKeyValueRepository> getPropertyRepository() override;
shared_ptr<CompositeKeyValueRepositoryAtomic> getHighscoreRepository() override;
protected:
void initPersistence(FilesystemNode& basedir) override;

View File

@ -296,7 +296,7 @@ void HighScoresDialog::saveConfig()
instance().settings().setValue("initials", myInitials);
}
// save selected variation
instance().highScores().saveHighScores(cartName(), myScores);
instance().highScores().saveHighScores(myScores);
if(myScores.variation == instance().highScores().variation() || myUserDefVar)
myHighScoreSaved = true;
}
@ -360,7 +360,7 @@ void HighScoresDialog::handleVariation(bool init)
{
myScores.variation = myVariationPopup->getSelectedTag().toInt();
instance().highScores().loadHighScores(cartName(), myScores);
instance().highScores().loadHighScores(myScores);
myEditRank = -1;

View File

@ -55,3 +55,9 @@ shared_ptr<CompositeKeyValueRepository> OSystemLIBRETRO::getPropertyRepository()
{
return make_shared<CompositeKeyValueRepositoryNoop>();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
shared_ptr<CompositeKeyValueRepositoryAtomic> OSystemLIBRETRO::getHighscoreRepository()
{
return make_shared<CompositeKeyValueRepositoryNoop>();
}

View File

@ -53,6 +53,8 @@ class OSystemLIBRETRO : public OSystem
shared_ptr<CompositeKeyValueRepository> getPropertyRepository() override;
shared_ptr<CompositeKeyValueRepositoryAtomic> getHighscoreRepository() override;
protected:
void initPersistence(FilesystemNode& basedir) override;

View File

@ -745,6 +745,7 @@
E0A755792244294600101889 /* CartCDFInfoWidget.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0A755772244294600101889 /* CartCDFInfoWidget.cxx */; };
E0D4153C25A120340031A8D6 /* SettingsRepositoryMACOS.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0D4153A25A120340031A8D6 /* SettingsRepositoryMACOS.hxx */; };
E0D4153D25A120340031A8D6 /* SettingsRepositoryMACOS.mm in Sources */ = {isa = PBXBuildFile; fileRef = E0D4153B25A120340031A8D6 /* SettingsRepositoryMACOS.mm */; };
E0D7E6F425A271A0006991C7 /* CompositeKeyValueRepository.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0D7E6F325A271A0006991C7 /* CompositeKeyValueRepository.cxx */; };
E0DCD3A720A64E96000B614E /* LanczosResampler.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */; };
E0DCD3A820A64E96000B614E /* LanczosResampler.cxx in Sources */ = {isa = PBXBuildFile; fileRef = E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */; };
E0DCD3A920A64E96000B614E /* ConvolutionBuffer.hxx in Headers */ = {isa = PBXBuildFile; fileRef = E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */; };
@ -1552,6 +1553,7 @@
E0A755772244294600101889 /* CartCDFInfoWidget.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CartCDFInfoWidget.cxx; sourceTree = "<group>"; };
E0D4153A25A120340031A8D6 /* SettingsRepositoryMACOS.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = SettingsRepositoryMACOS.hxx; sourceTree = "<group>"; };
E0D4153B25A120340031A8D6 /* SettingsRepositoryMACOS.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = SettingsRepositoryMACOS.mm; sourceTree = "<group>"; };
E0D7E6F325A271A0006991C7 /* CompositeKeyValueRepository.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CompositeKeyValueRepository.cxx; sourceTree = "<group>"; };
E0DCD3A320A64E95000B614E /* LanczosResampler.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = LanczosResampler.hxx; path = audio/LanczosResampler.hxx; sourceTree = "<group>"; };
E0DCD3A420A64E95000B614E /* LanczosResampler.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LanczosResampler.cxx; path = audio/LanczosResampler.cxx; sourceTree = "<group>"; };
E0DCD3A520A64E96000B614E /* ConvolutionBuffer.hxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = ConvolutionBuffer.hxx; path = audio/ConvolutionBuffer.hxx; sourceTree = "<group>"; };
@ -2481,6 +2483,7 @@
E06508B72272447200B341AC /* repository */ = {
isa = PBXGroup;
children = (
E0D7E6F325A271A0006991C7 /* CompositeKeyValueRepository.cxx */,
DC2ABA5B259BD544007E57D3 /* CompositeKeyValueRepository.hxx */,
DC2ABA66259D466C007E57D3 /* CompositeKeyValueRepositoryNoop.hxx */,
DC2ABA6B25A0C9B1007E57D3 /* CompositeKVRJsonAdapter.cxx */,
@ -3070,6 +3073,7 @@
DC3EE8671E2C0E6D00905161 /* inftrees.c in Sources */,
2D91747609BA90380026E9FF /* Cart.cxx in Sources */,
2D91747709BA90380026E9FF /* Cart2K.cxx in Sources */,
E0D7E6F425A271A0006991C7 /* CompositeKeyValueRepository.cxx in Sources */,
E034A5EE209FB25D00C89E9E /* EmulationTiming.cxx in Sources */,
2D91747809BA90380026E9FF /* Cart3F.cxx in Sources */,
2D91747909BA90380026E9FF /* Cart4K.cxx in Sources */,