<repo_name>Rayer/Iris<file_sep>/src/Main/MoManager.h
/**
 * Project Iris
 */


#ifndef _MOMANAGER_H
#define _MOMANAGER_H

#include <list>
#include <boost/uuid/uuid.hpp>
#include "MoMLayer.h"
#include "Loopable.h"

namespace Iris {
    class MoManager : public MoMLayer {
    private:
        std::list<Loopable *> module_list;
    public:
        Iris_State update(double delta, Context *context) override;

        boost::uuids::uuid register_module(Loopable *module) override;

        bool remove_module(boost::uuids::uuid id) override;
    };
}


#endif //_MOMANAGER_H<file_sep>/src/Main/Iris_State.h
/**
 * Project Iris
 */


#ifndef _IRIS_STATE_H
#define _IRIS_STATE_H

enum Iris_State {
    RUNNING, STOP, PAUSE
};

#endif //_IRIS_STATE_H<file_sep>/src/Main/Loopable.h
/**
 * Project Iris
 */


#ifndef _LOOPABLE_H
#define _LOOPABLE_H

#include "IDModule.h"

namespace Iris {
    class Context;

    class Loopable : public IDModule {
    public:

        /**
         * @param delta
         */
        virtual void update(double delta, Context* context) = 0;

        virtual std::string name() = 0;

        virtual ~Loopable() = default;
    };
}


#endif //_LOOPABLE_H<file_sep>/README.md
note :

- run `git pull --recurse-submodules` `git submodule update --init`
- For Mariadbpp testing, change `deps/mariadbpp/test/CMakeFile.txt` user and password entry
- In deps/mariadbpp/test/CMakeFile.txt, change line to this : `target_link_libraries(mariadbpp_tests mariadbclientpp gtest_main mariadbclient)`

todo : 
- Use configuration based design for test
- Use configuration based design for main
- Use standalone CMakeFile for test<file_sep>/src/Data/SqlKVDatabase.h
//
// Created by <NAME> on 2018/6/5.
//

#ifndef IRIS_SQLDATABASE_H
#define IRIS_SQLDATABASE_H

#include "KVDataPersistenceLayer.h"
#include <mariadb++/connection.hpp>

namespace Iris {
    class SqlKVDatabase : public KVDataPersistenceLayer {

        mariadb::account_ref m_account_setup;
        std::string m_db;
        mariadb::connection_ref m_con;
        std::map<std::string, std::shared_ptr<KVSpace> > m_space_map;

    public:
        SqlKVDatabase(std::string host, std::string user, std::string pass, std::string database, long port = 3306);

        ~SqlKVDatabase() override;

        std::shared_ptr<KVSpace> get_space(const std::string &name) override;
        void wipe(bool force) override;
    };


}



#endif //IRIS_SQLDATABASE_H
<file_sep>/src/Exceptions/DPLException.cpp
//
// Created by Rayer on 2018/6/19.
//

#include <string>
#include <boost/format.hpp>
#include "DPLException.h"

using namespace Iris;


const char *DPLException::what() const noexcept {
    return what_output.c_str();
}

//This wrapped_exception will cause unnecessery copy....
//But wrapped exception cant be reference, hence wrapped exception will be out of scope and invalid soon.
DPLException::DPLException(const std::string &reason, const std::string &driver_output) noexcept : wrapped_exception(*this), std::exception() {
    what_output = generate_message(reason, driver_output);
}

std::string DPLException::generate_message(const std::string &reason, const std::string &driver_output) {
    DPLException::reason = reason;
    DPLException::driver_output = driver_output;
    boost::format output =
            boost::format("DPL Exception raised\nOS message : %1%\nreason : %2%\ndriver_output : %3%\n")
            % wrapped_exception.what()
            % reason
            % driver_output;
    return output.str();
}

DPLException::DPLException(const std::exception &wrapped, const std::string &reason,
                           const std::string &driver_output) noexcept : wrapped_exception(wrapped), std::exception()  {

}



<file_sep>/src/Exceptions/SqlException.cpp
//
// Created by Rayer on 2018/6/22.
//

#include "SqlException.h"
#include <boost/format.hpp>


const char *Iris::SqlException::what() const noexcept {
    return what_output.c_str();
}



Iris::SqlException::SqlException(const std::string &reason, const std::string &sql_command,
                                 const std::string &driver_output) noexcept : DPLException(reason, driver_output) {
    what_output = generate_message(reason, sql_command, driver_output);
}

Iris::SqlException::SqlException(const std::exception &wrapped, const std::string &reason,
                                 const std::string &sql_command, const std::string &driver_output) noexcept : DPLException(wrapped, reason, driver_output) {
    what_output = generate_message(reason, sql_command, driver_output);
}

std::string Iris::SqlException::generate_message(const std::string &reason, const std::string &sql_command,
                                    const std::string &driver_output) {
    this->sql = sql_command;
    boost::format output =
            boost::format(
                    "\nSQL Exception raised\nOS message : %1%\nsql_command : %2%\nreason : %3%\ndriver_output : %4%\n")
            %
            wrapped_exception.what() //Don't use what(), it will cause loop. Dont use DPLException::what() too, will include duplicated message
            % this->sql
            % reason
            % driver_output;
    return output.str();
}




<file_sep>/src/Exceptions/DPLException.h
//
// Created by Rayer on 2018/6/19.
//

#ifndef IRIS_DPLEXCEPTION_H
#define IRIS_DPLEXCEPTION_H

#include <exception>

namespace Iris {
    class DPLException : public std::exception {
    protected:
        //Use this to
        std::exception wrapped_exception;
        std::string what_output;
        std::string reason;
        std::string driver_output;

    public:
        DPLException(const std::exception& wrapped_exception, const std::string &reason, const std::string &driver_output = "") noexcept;
        DPLException(const std::string &reason, const std::string &driver_output = "") noexcept;

        const char *what() const noexcept override;

        //Provide a noexcept copy constructor
        DPLException(const DPLException &) = default;

    public:
        ~DPLException() override = default;

        std::string generate_message(const std::string &reason, const std::string &driver_output);
    };
}


#endif //IRIS_DPLEXCEPTION_H
<file_sep>/src/Main/Context.h
/**
 * Project Iris
 */


#ifndef _CONTEXT_H
#define _CONTEXT_H

namespace Iris {
    class Context {
        bool running;
    public:
        bool isRunning() const;

        void setRunning(bool running);
    };
}


#endif //_CONTEXT_H<file_sep>/src/Main/MoMLayer.h
/**
 * Project Iris
 */


#ifndef _MOMLAYER_H
#define _MOMLAYER_H

#include "Iris_State.h"
#include <string>
#include <boost/uuid/uuid.hpp>


namespace Iris {
    class Context;

    class Loopable;

    class MoMLayer {
    public:

        /**
         * @param delta
         * @param context
         */
        virtual Iris_State update(double delta, Context *context) = 0;

        /**
         * @param module
         */
        virtual boost::uuids::uuid register_module(Loopable *module) = 0;

        /**
         * @param id
         */
        virtual bool remove_module(boost::uuids::uuid id) = 0;

        virtual ~MoMLayer() = default;
    };

}

#endif //_MOMLAYER_H<file_sep>/src/Main/IDModule.h
/**
 * Project Iris
 */


#ifndef _IDMODULE_H
#define _IDMODULE_H

#include <boost/uuid/uuid.hpp>

namespace Iris {
    class IDModule {
    public:

        /**
         * @param tag
         */
        virtual void set_tag(boost::uuids::uuid tag) = 0;

        virtual boost::uuids::uuid tag() = 0;
    };
}


#endif //_IDMODULE_H<file_sep>/src/Exceptions/SqlException.h
//
// Created by Rayer on 2018/6/22.
//

#ifndef IRIS_SQLEXCEPTION_H
#define IRIS_SQLEXCEPTION_H

#include <string>
#include "DPLException.h"

namespace Iris {
    class SqlException : public DPLException {


        std::string sql;
        std::string generate_message(const std::string &reason, const std::string &sql_command, const std::string &driver_output);

    public:
        SqlException(const std::exception& wrapped, const std::string &reason, const std::string &sql_command = "",
                     const std::string &driver_output = "")  noexcept;
        SqlException(const std::string &reason, const std::string &sql_command = "",
                     const std::string &driver_output = "")  noexcept;


        const char *what() const noexcept override;


        ~SqlException() override = default;

    };
}


#endif //IRIS_SQLEXCEPTION_H
<file_sep>/test/LoopSystemTest.cpp
//
// Created by <NAME> on 14/11/2017.
//

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <Main/Loopable.h>
#include <Main/MoMLayer.h>
#include <Main/MoManager.h>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <utility>
#include <Main/Context.h>

using namespace Iris;

int update_count = 0;

class TemplateModule : public Loopable {
    boost::uuids::uuid m_tag;
    std::string m_name;
public:
    explicit TemplateModule(std::string name) : m_name(std::move(name)), m_tag(boost::uuids::nil_uuid()) {
    }

    void update(double delta, Context *context) override {
        update_count++;
    }

    void set_tag(boost::uuids::uuid uuid) override {
        m_tag = uuid;
    }

    boost::uuids::uuid tag() override {
        return m_tag;
    }

    std::string name() override {
        return m_name;
    }
};


TEST(LoopSystemTest, Register_UUID_Test) {
    Loopable *clazz1, *clazz2;
    clazz1 = new TemplateModule("TestModule1");
    clazz2 = new TemplateModule("TestModule2");

    EXPECT_EQ(clazz1->tag(), boost::uuids::nil_uuid());
    EXPECT_EQ(clazz2->tag(), boost::uuids::nil_uuid());

    MoMLayer *ml = new MoManager();
    ml->register_module(clazz1);
    ml->register_module(clazz2);

    EXPECT_NE(clazz1->tag(), boost::uuids::nil_uuid());
    EXPECT_NE(clazz2->tag(), boost::uuids::nil_uuid());
    EXPECT_NE(clazz1->tag(), clazz2->tag());
    delete ml;
    delete clazz1;
    delete clazz2;
}

TEST(LoopSystemTest, Reg_Remove_Module_Test) {
    Loopable *clazz1, *clazz2;
    clazz1 = new TemplateModule("TestModule1");
    clazz2 = new TemplateModule("TestModule2");

    Context* context = new Context();

    update_count = 0;

    typedef boost::uuids::uuid UUID;

    MoMLayer *ml = new MoManager();
    UUID u1 = ml->register_module(clazz1);
    UUID u2 = ml->register_module(clazz2);

    ml->update(0, context);

    EXPECT_EQ(update_count, 2);

    ml->remove_module(u2);
    ml->update(0, context);

    EXPECT_EQ(update_count, 3);

    ml->remove_module(u1);
    ml->update(0, context);
    EXPECT_EQ(update_count, 3);

    delete ml;
    delete clazz1;
    delete clazz2;
}
<file_sep>/src/Data/DataPersistenceManager.cpp
//
// Created by Rayer on 17/08/2017.
//

#include "DataPersistenceManager.h"
#include "SimpleKVDevDB.h"

using namespace Iris;


KVDataPersistenceLayer *DataPersistenceManager::dataPersistenceLayer = nullptr;

//TODO: Add pre-compiler to determine debug and release for different instance....or load from file.
KVDataPersistenceLayer *DataPersistenceManager::getInstance() {
    if (dataPersistenceLayer == nullptr) {
        dataPersistenceLayer = new SimpleKVDevDB();
    }
    return dataPersistenceLayer;
}

<file_sep>/src/Data/SqlKVDatabaseSpace.cpp
//
// Created by <NAME> on 2018/6/5.
//

#include "SqlKVDatabaseSpace.h"
#include <boost/format.hpp>
#include "Exceptions/SqlException.h"
#include <boost/serialization/variant.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <mariadb++/exceptions.hpp>
#include "Utils/SqlCmd.h"

using namespace mariadb;
using namespace where_clause;

Iris::KVSpace::ValueType Iris::SqlKVDatabaseSpace::get_value(const std::string &key) {

    //std::string sql_string = (boost::format("select `value` from %1% where `key`='%2%'") % space_name % key).str();
    std::string sql_string = SqlQuery(space_name).addQuery("`value`").where(Eq("`key`", key)).generate();

    try {
        result_set_ref result = sql_connect->query(sql_string);
        if (result->row_count() == 0) throw SqlException{"get_value not found key!", sql_string, sql_connect->error()};

        result->next();
        std::string data = result->get_string("value");

        ValueType ret;
        std::stringstream ss;
        ss << data;
        boost::archive::text_iarchive ar(ss);
        ar >> ret;

        return ret;
    } catch(mariadb::exception::base& ex) {

        //wrap up as DPL/SQL exceptions

        throw SqlException{ex, "set_value", sql_string, sql_connect->error()};

    } catch(Iris::DPLException& dpl) {
        throw dpl;
    }

}

void Iris::SqlKVDatabaseSpace::set_value(const std::string &key, const Iris::KVSpace::ValueType &value) {

    //Use sql_string on demend....
    //std::string sql_string = (boost::format("select 1 from %1% where `key` = '%2%';") % space_name % key).str();
    std::string sql_string = SqlQuery(space_name).addQuery("1").where(Eq("`key`", key)).generate();
    try {
        sql_connect->query(sql_string);
        bool update = 0 != sql_connect->query(sql_string)->row_count();

        std::stringstream blob;
        boost::archive::text_oarchive ar(blob);
        ar << value;

        //static boost::format update_string = boost::format("update `%1%` set `value` = '%3%' where `key` = '%2%';");
        //static boost::format insert_string = boost::format("insert into `%1%` (`key`, `value`) values ('%2%','%3%');");
        //sql_string = ((update ? update_string : insert_string) % space_name % key % blob.str()).str();
        if (update) {
            sql_string = SqlUpdate(space_name).setValue("`value`", blob.str()).where(Eq("`key`", key)).generate();
        } else {
            sql_string = SqlInsert(space_name).insertValue("`key`", key).insertValue("`value`", blob.str()).generate();
        }
        if (!sql_connect->execute(sql_string)) throw SqlException{"set_value failed", sql_string, sql_connect->error()};
    } catch(mariadb::exception::base& ex) {

        //wrap up as DPL/SQL exceptions

        throw SqlException{ex, "set_value", sql_string, sql_connect->error()};

    } catch(Iris::DPLException& dpl) {
        throw dpl;
    }


}

Iris::SqlKVDatabaseSpace::SqlKVDatabaseSpace(const std::string &name, mariadb::connection_ref conn) {
    space_name = name;
    this->sql_connect = conn;
}

bool Iris::SqlKVDatabaseSpace::remove(const std::string &key) {
    return false;
}

void Iris::SqlKVDatabaseSpace::wipe() {
    std::string sql_string = (boost::format("drop table %1%;") % space_name).str();
    if (!sql_connect->execute(sql_string)) throw SqlException{"wipe failed", sql_string, sql_connect->error()};
}

void Iris::SqlKVDatabaseSpace::refresh() {
    //not used now.
}
<file_sep>/src/Data/SqlKVDatabase.cpp
//
// Created by <NAME> on 2018/6/5.
//

#include "SqlKVDatabase.h"
#include "SqlKVDatabaseSpace.h"
#include <boost/format.hpp>
#include <Exceptions/SqlException.h>
#include <mariadb++/exceptions.hpp>
#include "Utils/SqlCmd.h"

using namespace Iris;
using namespace mariadb;
using namespace where_clause;

std::shared_ptr<KVSpace> SqlKVDatabase::get_space(const std::string &name) {

    //Find space map first
    if (m_space_map.find(name) != m_space_map.end())
        return m_space_map[name];

    //std::string sql_string = (
    //        boost::format("select 1 from information_schema.tables where table_schema='%1%' and table_name='%2%';") %
    //        m_db % name).str();
    std::string sql_string = SqlQuery("information_schema.tables").addQuery("1").where(
            Eq("table_schema", m_db) & Eq("table_name", name)).generate();

    try {
        result_set_ref result = m_con->query(sql_string);

        if (result->row_count() == 0) {
            sql_string = (boost::format("CREATE TABLE %1%\n"
                                             "(\n"
                                             "    `key` varchar(256) PRIMARY KEY NOT NULL,\n"
                                             "    value varchar(1024)\n"
                                             ");\n"
                                             "CREATE UNIQUE INDEX %1%_key_uindex ON %1% (`key`);") % name).str();
            if (m_con->execute(sql_string) == 0xffffffff) throw SqlException{"Fail to create in get_space", sql_string, m_con->error()};
        }

        std::shared_ptr<KVSpace> space = std::make_shared<SqlKVDatabaseSpace>(name, m_con);
        m_space_map.insert(std::make_pair(name, space));

        return space;
    } catch(mariadb::exception::base& ex) {

        //wrap up as DPL/SQL exceptions
        throw SqlException{ex, "set_value", sql_string, m_con->error()};

    } catch(Iris::DPLException& dpl) {
        throw dpl;
    }

}

void SqlKVDatabase::wipe(bool force) {
    //TODO: Dangerous operation, need a WARN log
    //std::string sql_String = (
    //        boost::format("select TABLE_NAME from information_schema.tables where table_schema='%1%';") % m_db).str();
    std::string sql_String = SqlQuery("information_schema.tables").addQuery("TABLE_NAME").where(
            Eq("table_schema", m_db)).generate();

    result_set_ref result = m_con->query(sql_String);

    while (result->next()) {
        m_con->execute((boost::format("drop table %1%;") % result->get_string("TABLE_NAME")).str());
    }


}

SqlKVDatabase::SqlKVDatabase(std::string host, std::string user, std::string pass, std::string database, long port) {

    m_account_setup = account::create(host, user, pass, database, (u32) port);
    m_db = database;
    m_account_setup->set_auto_commit(true);
    m_con = connection::create(m_account_setup);
    if (!m_con->connect()) throw SqlException{"Fail to initial SqlDataBase!", "", m_con->error()};
}

SqlKVDatabase::~SqlKVDatabase() {
    if (m_con) m_con->disconnect();
}
<file_sep>/test/ItemTest.cpp
//
// Created by <NAME> on 23/08/2017.
//

<file_sep>/src/Data/SimpleKVDevDB.h
//
// Created by Rayer on 15/08/2017.
//

#ifndef IRIS_SIMPLEDEVDB_H
#define IRIS_SIMPLEDEVDB_H


#include "KVDataPersistenceLayer.h"
#include "SimpleKVDevDBSpace.h"
#include <boost/filesystem.hpp>

/**
 * DPL Implementation for development
 */

namespace Iris {
    class SimpleKVDevDB : public KVDataPersistenceLayer {
    protected:
        std::map<std::string, std::shared_ptr<SimpleKVDevDBSpace>> space_map;

    public:
        void serialize(std::string folder = boost::filesystem::complete(
                boost::filesystem::temp_directory_path()).generic_string());

        void deserialize(std::string folder = boost::filesystem::complete(
                boost::filesystem::temp_directory_path()).generic_string());

        std::shared_ptr<KVSpace> get_space(const std::string &name) override;

        void wipe(bool force) override;

        SimpleKVDevDB() = default;

        ~SimpleKVDevDB() = default;
    };

}
#endif //IRIS_SIMPLEDEVDB_H
<file_sep>/test/CharacterManagerTest.cpp
//
// Created by <NAME> on 28/07/2017.
//

<file_sep>/src/Data/DataPersistenceManager.h
//
// Created by Rayer on 17/08/2017.
//

#ifndef IRIS_DATAPERSISTENCEMANAGER_H
#define IRIS_DATAPERSISTENCEMANAGER_H

namespace Iris {

    class KVDataPersistenceLayer;

/**
 * Data Persistence Selector, will deliver different DPL implementation by compiling time
 */
    class DataPersistenceManager {
    private:
        static KVDataPersistenceLayer *dataPersistenceLayer;

        DataPersistenceManager() = default;

    public:
        static KVDataPersistenceLayer *getInstance();

    };
}

#endif //IRIS_DATAPERSISTENCEMANAGER_H
<file_sep>/src/Utils/SqlCmd.cpp
#include "SqlCmd.h"
#include <boost/algorithm/string.hpp>


SqlQuery::SqlQuery(const std::string &table) {
    m_tablename = table;
}

SqlQuery &SqlQuery::addQuery(const std::string &query) {
    m_queryItemList.push_back(query);
    return *this;
}

std::string SqlQuery::generate() {
    std::stringstream buffer;
    buffer << "SELECT ";
    for (auto iter = m_queryItemList.begin(); iter != m_queryItemList.end(); ++iter) {
        if (iter != m_queryItemList.begin()) {
            buffer << "," << *iter;
        } else {
            buffer << *iter;
        }
    }

    buffer << " FROM " << m_tablename;
    if (!m_whereString.empty())
        buffer << " WHERE " << m_whereString;

    if (!m_orderString.empty())
        buffer << " " << m_orderString;

    buffer << ";";

    return buffer.str();
}

SqlQuery &SqlQuery::where(const std::string &condition) {
    m_whereString = condition;
    return *this;
}

SqlQuery &SqlQuery::where(const where_clause::Exp &exp) {
    return where(exp.str());
}


SqlUpdate::SqlUpdate(const std::string &tablename) {
    m_tablename = tablename;
}


SqlUpdate &SqlUpdate::setValue(const std::string &key, const std::string &value) {
    m_kvList.emplace_back(key, value);
    return *this;
}

SqlUpdate &SqlUpdate::setValue(const std::string &key, int value) {
    m_kvList.emplace_back(key, std::to_string(value));
    return *this;
}

SqlUpdate &SqlUpdate::setValue(const std::string &key, double value) {
    std::stringstream buf;
    buf << value;
    m_kvList.emplace_back(key, buf.str());
    return *this;
}

SqlUpdate &SqlUpdate::where(const std::string &where_clouse) {
    m_whereString = where_clouse;
    return *this;
}

SqlUpdate &SqlUpdate::where(const where_clause::Exp &exp) {
    return where(exp.str());
}


std::string SqlUpdate::generate() {
    std::stringstream buffer;
    //UPDATE TABLE_NAME SET KEY = VALUE WHERE (WHERE_CLOUSE)
    std::stringstream set_value_buf;

    std::list<std::string> set_result;
//    std::transform(m_kvList.begin(), m_kvList.end(), set_result.begin(), [](const KVPair& kvPair)->std::string {
//        return kvPair.first + "='" + kvPair.second + "'";
//    });

    for (KVPair kv : m_kvList) {
        set_result.push_back(kv.first + "='" + kv.second + "'");
    }

    std::string set_string = boost::join(set_result, ",");

    buffer << "UPDATE " << m_tablename << " SET " << set_string;
    if (!m_whereString.empty()) buffer << " WHERE " << m_whereString;
    buffer << ";";
    return buffer.str();
}


SqlInsert::SqlInsert(const std::string &tablename) : m_tablename(tablename) {
}

SqlInsert &SqlInsert::insertValue(const std::string &key, const std::string &value) {
    m_kvList.emplace_back(key, value);
    return *this;
}

SqlInsert &SqlInsert::insertValue(const std::string &key, int value) {
    m_kvList.emplace_back(key, std::to_string(value));
    return *this;
}

SqlInsert &SqlInsert::insertValue(const std::string &key, double value) {
    std::stringstream buf;
    buf << value;
    m_kvList.emplace_back(key, buf.str());
    return *this;
}

std::string SqlInsert::generate() {
    std::stringstream buf;
    std::list<std::string> columns;
    std::list<std::string> values;

    for (KVPair kv : m_kvList) {
        columns.push_back(kv.first);
        values.push_back(kv.second);
    }

    buf << "INSERT INTO " << m_tablename << " (" << boost::join(columns, ",") << ") VALUES ('"
        << boost::join(values, "','") << "');";
    return buf.str();
}

where_clause::And where_clause::Exp::operator&(const Exp &exp) const {
    return And(*this, exp);
}


where_clause::Eq::Eq(const std::string &var, const std::string &val)
        : m_var(var), m_val(val) {};

/*
//This implementation requires C++11 which is not yet supported by current build toolchain
where_clause::Eq::Eq(const std::string &var, const int val)
        : m_var(var), m_val(std::to_string((long long) val)) {};

where_clause::Eq::Eq(const std::string &var, const double val)
        : m_var(var), m_val(std::to_string((long double) val)) {};
*/

where_clause::Eq::Eq(const std::string& var, const int val)
        : m_var(var){
    std::stringstream buf;
    buf << val;
    m_val = buf.str();
};

where_clause::Eq::Eq(const std::string& var, const double val)
        : m_var(var){
    std::stringstream buf;
    buf << val;
    m_val = buf.str();
};


std::string where_clause::Eq::str() const {

    //trim_copy costs, so m_val.empty() first.
    if (m_val.empty() || boost::trim_copy(m_val).empty()) {
        return m_var + " is null";
    } else {
        return m_var + "='" + m_val + "'";
    }
}

where_clause::Like::Like(const std::string &var, const std::string &val)
        : m_var(var), m_val(val) {};

std::string where_clause::Like::str() const {
    return m_var + " Like '" + m_val + "'";
}

where_clause::And::And(const Exp &lhs, const Exp &rhs) : m_lhs(lhs), m_rhs(rhs) {
}

std::string where_clause::And::str() const {
    std::stringstream output;
    output << m_lhs.str() << " AND " << m_rhs.str();
    return output.str();
}

SqlQuery &SqlQuery::orderBy(const std::string &column, Order order) {
    m_orderString = "ORDER BY " + column + (order == ASCENDING ? "" : " DESC");
    return *this;
}

where_clause::Not::Not(const std::string &var, const std::string &val) : m_var(var), m_val(val) {
}

where_clause::Not::Not(const std::string &var, int val) : m_var(var), m_val(std::to_string(val)) {
}

where_clause::Not::Not(const std::string &var, double val) : m_var(var) {
    std::stringstream buf;
    buf << val;
    m_val = buf.str();
}

std::string where_clause::Not::str() const {
    //trim_copy costs, so m_val.empty() first.
    if (m_val.empty() || boost::trim_copy(m_val).empty()) {
        return m_var + " is not null";
    } else {
        return m_var + "<>'" + m_val + "'";
    }
}


<file_sep>/src/Data/KVSpace.h
//
// Created by Rayer on 15/08/2017.
//

#ifndef IRIS_SPACE_H
#define IRIS_SPACE_H


#include <string>
#include <boost/variant.hpp>

namespace Iris {
/**
 * Space (Namespace) interface for DPL
 */
    class KVSpace {
    public:
        typedef boost::variant<std::string, double, int> ValueType;

        virtual ValueType get_value(const std::string &key) = 0;

        virtual void set_value(const std::string &key, const ValueType &value) = 0;

        virtual bool remove(const std::string &key) = 0;

        virtual void wipe() = 0;


    };
}
#endif //IRIS_SPACE_H
<file_sep>/src/Data/KVDataPersistenceLayer.h
//
// Created by Rayer on 14/08/2017.
//

#ifndef IRIS_DATAPERSISTENCELAYER_H
#define IRIS_DATAPERSISTENCELAYER_H

#include <string>
#include <memory>
#include "KVSpace.h"

namespace Iris {


/**
 * This is data persistance interface.
 * \author : Rayer
 */
    class KVDataPersistenceLayer {
    public:
        KVDataPersistenceLayer() = default;

        /**
         * Get Space(namespace)
         * \param name : Space Name
         * \return Shared pointer of a single Space
         */
        virtual std::shared_ptr<KVSpace> get_space(const std::string &name) = 0;

        /**
         * Wipe all data
         */
        virtual void wipe(bool force) = 0;

        /**
         *
         */
        virtual ~KVDataPersistenceLayer() = default;


    };
}

#endif //IRIS_DATAPERSISTENCELAYER_H
<file_sep>/CMakeLists.txt
cmake_minimum_required(VERSION 3.6)
project(Iris)

set(CMAKE_CXX_STANDARD 11)
enable_testing()

# Boost dependency
# P.S. std::variant requires C++17, use boost::variant instead
FIND_PACKAGE(Boost REQUIRED serialization filesystem system)
INCLUDE_DIRECTORIES( ${Boost_INCLUDE_DIR} )

# Not yet implemented
option(GRAPHIC_CLIENT "Build graphic client" OFF)

if(GRAPHIC_CLIENT)
    execute_process(COMMAND 'git submodule update cocos2d-x' .)
    add_subdirectory(deps/cocos2d-x)
endif(GRAPHIC_CLIENT)

add_subdirectory(deps/googletest)
include_directories(deps/googletest/googletest/include)
include_directories(deps/googletest/googlemock/include)
include_directories(deps/mariadbpp/include)
include_directories(src)

set(GTEST_SRC_DIR deps/googletest/googletest)

add_subdirectory(deps/mariadbpp)
add_subdirectory(deps/mariadb-connector-c)


set(LIBS
        src/Data/KVDataPersistenceLayer.h
        src/Data/SimpleKVDevDB.cpp
        src/Data/SimpleKVDevDB.h
        src/Data/KVSpace.h
        src/Data/SimpleKVDevDBSpace.cpp
        src/Data/SimpleKVDevDBSpace.h
        src/Data/DataPersistenceManager.cpp
        src/Data/DataPersistenceManager.h
        src/Main/Context.cpp
        src/Main/Context.h
        src/Main/GameMain.cpp
        src/Main/GameMain.h
        src/Main/Iris_State.h
        src/Main/MoManager.cpp
        src/Main/MoManager.h
        src/Main/MoMLayer.h
        src/Main/Loopable.h
        src/Main/IDModule.h src/Data/SqlKVDatabase.cpp src/Data/SqlKVDatabase.h src/Data/SqlKVDatabaseSpace.cpp src/Data/SqlKVDatabaseSpace.h src/Exceptions/DPLException.cpp src/Exceptions/DPLException.h src/Exceptions/SqlException.cpp src/Exceptions/SqlException.h src/Utils/SqlCmd.cpp src/Utils/SqlCmd.h)

set(TEST_FILES
        test/CharacterManagerTest.cpp
        test/SimpleKVDevDBTest.cpp
        test/ItemTest.cpp test/LoopSystemTest.cpp test/SqlKVDatabaseTest.cpp test/UtilSqlCmdTest.cpp)

# message("${Boost_LIBRARIES} ${Boost_LIBRARY_DIR_RELEASE} ${Boost_LIBRARY_DIR_DEBUG} 123")

# add_executable(Iris ${LIBS} main.cpp)
add_library(Iris_Libs ${LIBS})
add_executable(Iris_Tests deps/googletest/googletest/src/gtest_main.cc ${TEST_FILES})
target_link_libraries(Iris_Tests gtest Iris_Libs ${Boost_LIBRARIES} mariadbclientpp mariadbclient)<file_sep>/src/Utils/SqlCmd.h
//
// Created by <NAME> on 2018/7/19.
//

#ifndef IRIS_SQLCMD_H
#define IRIS_SQLCMD_H

#pragma once

#include <sstream>
#include <list>
#include <map>

//Forward Declaration
namespace where_clause {
    class Eq;

    class Not;
    class And;

    class Or;

    class Like;

    class Bracket;

    class Parentheses;
    class Exp;
}

enum Order {
    ASCENDING,
    DESCENDING
};

//Not intent to be used by user. It is abstract class of all SQL Commands.
class SqlCmd {
public:
    /**
    Generate SQL String.
    \return Generated SQL String
    */
    virtual std::string generate() = 0;
    //virtual ADODB::_RecordsetPtr execute(ADODB::_ConnectionPtr ptr) = 0;

    virtual ~SqlCmd() = default;
};

/**
This class provides an easier way to access SQL DB. It is a Builder pattern based class.

```
QueryResult result = SqlQuery("TABLE_NAME").addQuery("COLUMN1").addQuery("COLUMN2").where(Eq("c_system_id", "awsrayer") & Eq("c_client_layout", "1272028")).query(m_OraCnn);
```

\sa QueryResult
*/
class SqlQuery : public SqlCmd {
    std::string m_tablename;
    std::list<std::string> m_queryItemList;
    std::string m_whereString;
    std::string m_orderString;

public:
    /**
    Main builder for Query
    \param table Table Name
    */
    SqlQuery(const std::string &table);

    ~SqlQuery(void) override = default;

    /**
    Add a query column. It works like SELECT "THIS" from "TABLE NAME"
    \param query Column name added to query list
    \return Builder pattern self reference
    */
    SqlQuery &addQuery(const std::string &query);

    /**
    Where clause. This can consume "text" version of where clause, like "A='01' AND B='02'" as raw stream
    \param condition Where-clause.
    \return Builder pattern self reference
    */
    SqlQuery &where(const std::string &condition);

    /**
    Where clause, but use where_clause utilities.
    For example, .where("A='01' AND B='02'") is equal to .where(Eq("A", "01") & EQ("B", "02"))
    \param exp where_clause utilities classes.
    \return Builder pattern self reference
    \sa where_clause
    */
    SqlQuery &where(const where_clause::Exp &exp);

    /**
     Sorting Order, it can provide ordering by specified column.
     \param column Column should be sorted by.
     \param order ASCENDING(default) and DESCENDING : as literal
     \return Builder pattern self reference
     */
    SqlQuery &orderBy(const std::string &column, Order order = ASCENDING);


    /**
    Generate SQL String.
    \return Generated SQL String
    */
    std::string generate() override;

};

/**
SqlUpdate is another helper for executing "UPDATE" command of SQL, it is a Builder Pattern, too.

<code>

SqlUpdate("TABLE_NAME").setValue("COLUMN1_INT", "123").setValue("COLUMN2_STR", "Hello").execute(m_pOraCnn);

</code>
*/
class SqlUpdate : public SqlCmd {
protected:
    std::string m_tablename;
    typedef std::pair<std::string, std::string> KVPair;
    std::list<KVPair> m_kvList;
    std::string m_whereString;

public:
    /**
    Main Builder for Update
    \param tablename Target table name
    */
    SqlUpdate(const std::string &tablename);

    ~SqlUpdate(void) override = default;

    /**
    Set a value to a column.
    \param key
    \param value Value use to set to the column. Currently only string is supported because all use cases use string as value.
    */
    SqlUpdate &setValue(const std::string &key, const std::string &value);

    SqlUpdate &setValue(const std::string &key, int value);

    SqlUpdate &setValue(const std::string &key, double value);

    /**
    Where clause. This can consume "text" version of where clause, like "A='01' AND B='02'" as raw stream
    \param condition Where-clause.
    \return Builder pattern self reference
    */
    SqlUpdate &where(const std::string &condition);

    /**
    Where clause, but use where_clause utilities.
    For example, .where("A='01' AND B='02'") is equal to .where(Eq("A", "01") & EQ("B", "02"))
    \param exp where_clause utilities classes.
    \return Builder pattern self reference
    \sa where_clause
    */
    SqlUpdate &where(const where_clause::Exp &exp);

    /**
    Generate SQL String.
    \return Generated SQL String
    */
    std::string generate() override;

};


class SqlInsert : public SqlCmd {
protected:
    std::string m_tablename;
    typedef std::pair<std::string, std::string> KVPair;
    std::list<KVPair> m_kvList;

public:
    /**
    Main Builder for Update
    \param tablename Target table name
    */
    SqlInsert(const std::string &tablename);

    ~SqlInsert(void) override = default;

    /**
    Set a value to a column.
    \param key
    \param value Value use to set to the column. Currently only string is supported because all use cases use string as value.
    */
    SqlInsert &insertValue(const std::string &key, const std::string &value);

    SqlInsert &insertValue(const std::string &key, int value);

    SqlInsert &insertValue(const std::string &key, double value);

    /**
    Generate SQL String.
    \return Generated SQL String
    */
    std::string generate() override;

};
/**
This is a helper class for generating where_clause.

For example, we usually use this to build a `where-clause`
```
std::stringstream where_clause;
where_clause << ORA_A::COLUMN_NAME << "=" << someValue << " AND "
	<< ORA_A::COLUMN_NAME2 << "=" << someValue2 << " AND "
	<< ORA_A::COLUMN_NAME3 << "=" << someValue3;

QueryResult result = SqlQuery(ORA_A::TABLE_NAME)
	.addQuery(ORA_A::COLUMN_OTHER)
	.where(where_clause.str())
	.query(m_pOraCnn);
```

It can be samplify to
```
QueryResult result = SqlQuery(ORA_A::TABLE_NAME)
	.addQuery(ORA_A::COLUMN_OTHER)
	.where(And(And(Eq(ORA_A::COLUMN_NAME, someValue), Eq(ORA_A::COLUMN_NAME2, someValue2)), Eq(ORA_A::COLUMN_NAME3, someValue3)))
	.query(m_pOraCnn);
```
`And` might still be not readable when there is a lot of And clause...

Even furthermore...
```
QueryResult result = SqlQuery(ORA_A::TABLE_NAME)
	.addQuery(ORA_A::COLUMN_OTHER)
	.where(Eq(ORA_A::COLUMN_NAME, someValue) & Eq(ORA_A::COLUMN_NAME2, someValue2) & Eq(ORA_A::COLUMN_NAME3, someValue3))
	.query(m_pOraCnn);
```

where_clause makes adding `where()` much more easier then before.
*/
namespace where_clause {
    class Exp {
    public :
        virtual ~Exp() = default;

        virtual std::string str() const { return ""; };

        /**
        This operator uses in two where_clause::Exp. It represent "AND"
        \sa And
        */
        And operator&(const Exp &exp) const;

        Or operator|(const Exp &exp) const;

        Parentheses operator()(const Exp &exp) const;
    };

    /**
    Express "like", Like
    */
    class Like : public Exp {
        std::string m_var;
        std::string m_val;
    public :
        ~Like() = default;

        Like(const std::string &var, const std::string &val);

        std::string str() const;
    };

    /**
    Express "=", Equal
    */
    class Eq : public Exp {
        std::string m_var;
        std::string m_val;
    public :
        ~Eq() override = default;
        Eq(const std::string &var, const std::string &val);
        Eq(const std::string &var, int val);
        Eq(const std::string &var, double val);
        std::string str() const override;
    };

    /**
     * Express "<>", NOT
     */
    class Not : public Exp {
        std::string m_var;
        std::string m_val;
    public:
        ~Not() override = default;

        Not(const std::string &var, const std::string &val);

        Not(const std::string &var, int val);

        Not(const std::string &var, double val);

        std::string str() const override;

    };

    /**
    Express "And", usually we can use operator& instead.
    */
    class And : public Exp {
        const Exp &m_lhs;
        const Exp &m_rhs;
    public:
        And(const Exp &, const Exp &);

        //And(const std::list<Exp>& exp);
        std::string str() const override;
    };


//    /**
//     * Express "Or", usually we can use operator| instead
//     */
//
//     class Or : public Exp {
//         const Exp &m_lhs;
//         const Exp &m_rhs;
//     public:
//         Or(const Exp &, const Exp &);
//         std::string str() const override;
//     };
//
//     /**
//      * Express "Parentheses", usually we can use operator() instead
//      */
//
//     class Parentheses : public Exp {
//         const Exp &m_lhs;
//         const Exp &m_rhs;
//     public:
//         Parentheses(const Exp&, const Exp&);
//         std::string str() const override;
//     };
//

}


#endif //IRIS_SQLCMD_H
<file_sep>/test/UtilSqlCmdTest.cpp
//
// Created by <NAME> on 2018/7/19.
//

#include <gtest/gtest.h>
#include "Utils/SqlCmd.h"

using where_clause::Eq;
using where_clause::And;
using where_clause::Not;

TEST(SqlCmdTest, BaseQuery) {
    std::string expected = "SELECT c1,c2 FROM table_name;";
    std::string generated = SqlQuery("table_name").addQuery("c1").addQuery("c2").generate();
    EXPECT_EQ(generated, expected);
}

TEST(SqlCmdTest, BaseWhere) {
    std::string expected = "SELECT c1,c2 FROM table_name WHERE a='b';";
    std::string generated = SqlQuery("table_name").addQuery("c1").addQuery("c2").where(
            where_clause::Eq("a", "b")).generate();
    EXPECT_EQ(generated, expected);
}

TEST(SqlCmdTest, BaseWhere2) {
    std::string expected = "SELECT c1,c2 FROM table_name WHERE a='b' AND c='2' AND d='2.55';";
    std::string generated = SqlQuery("table_name").addQuery("c1").addQuery("c2").where(
            Eq("a", "b") & Eq("c", 2) & Eq("d", 2.55)).generate();
    std::string generated2 = SqlQuery("table_name").addQuery("c1").addQuery("c2").where(
            And(And(Eq("a", "b"), Eq("c", 2)), Eq("d", 2.55))).generate();
    EXPECT_EQ(generated, expected);
    EXPECT_EQ(generated2, expected);
}

TEST(SqlCmdTest, WhereWithNull) {
    std::string expected = "SELECT c1,c2 FROM table_name WHERE c3 is null;";
    std::string generated = SqlQuery("table_name").addQuery("c1").addQuery("c2").where(Eq("c3", "")).generate();
    EXPECT_EQ(generated, expected);
}

TEST(SqlCmdTest, WhereWithNot) {
    std::string expected = "SELECT c1,c2 FROM table_name WHERE c3<>'c4';";
    std::string generated = SqlQuery("table_name").addQuery("c1").addQuery("c2").where(Not("c3", "c4")).generate();
    EXPECT_EQ(generated, expected);
}

TEST(SqlCmdTest, WhereWithNotNull) {
    std::string expected = "SELECT c1,c2 FROM table_name WHERE c3 is not null;";
    std::string generated = SqlQuery("table_name").addQuery("c1").addQuery("c2").where(Not("c3", "")).generate();
    EXPECT_EQ(generated, expected);
}

TEST(SqlCmdTest, QueryWithOrder) {
    std::string expected = "SELECT c1,c2 FROM table_name WHERE a='b' ORDER BY e;";
    std::string generated = SqlQuery("table_name").addQuery("c1").addQuery("c2").where(Eq("a", "b")).orderBy(
            "e").generate();
    EXPECT_EQ(generated, expected);
    expected = "SELECT c1,c2 FROM table_name WHERE a='b' ORDER BY e DESC;";
    generated = SqlQuery("table_name").addQuery("c1").addQuery("c2").where(Eq("a", "b"))
            .orderBy("e", DESCENDING).generate();
    EXPECT_EQ(generated, expected);
}

TEST(SqlCmdTest, BaseUpdate) {
    std::string expected = "UPDATE table_name SET c1='v1',c2='2',c3='3.5' WHERE c4='v4';";
    std::string generated = SqlUpdate("table_name").setValue("c1", "v1").setValue("c2", 2).setValue("c3", 3.5)
            .where(Eq("c4", "v4")).generate();
    EXPECT_EQ(generated, expected);
}

TEST(SqlCmdTest, BaseInsert) {
    std::string expected = "INSERT INTO table_name (c1,c2,c3,c4) VALUES ('v1','v2','3','2.7');";
    std::string generated = SqlInsert("table_name").insertValue("c1", "v1").insertValue("c2", "v2")
            .insertValue("c3", 3).insertValue("c4", 2.7).generate();
    EXPECT_EQ(generated, expected);
}<file_sep>/src/Main/Context.cpp
/**
 * Project Iris
 */


#include "Context.h"

using namespace Iris;
/**
 * Context implementation
 */
bool Context::isRunning() const {
    return running;
}

void Context::setRunning(bool running) {
    Context::running = running;
}
<file_sep>/src/Data/SimpleKVDevDBSpace.cpp
//
// Created by Rayer on 15/08/2017.
//

#include "SimpleKVDevDBSpace.h"
#include <sstream>
#include <iostream>
#include <boost/serialization/variant.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <fstream>

using namespace Iris;

SimpleKVDevDBSpace::ValueType SimpleKVDevDBSpace::get_value(const std::string &key) {
    return map.at(key);
}

void SimpleKVDevDBSpace::set_value(const std::string &key, const ValueType &value) {
    map.insert({key, value});
}

void SimpleKVDevDBSpace::serialize(std::string fullPath) {
    std::ofstream ofs(fullPath);
    boost::archive::text_oarchive ar(ofs);
    ar << map;
    ofs.close();
}

void SimpleKVDevDBSpace::deserialize(std::string fullPath) {
    std::ifstream ifs(fullPath);
    boost::archive::text_iarchive ar(ifs);
    ar >> map;
    ifs.close();
}

std::list<std::string> SimpleKVDevDBSpace::get_keys() {
    std::list<std::string> ret;
    for (auto const &pair : map) {
        ret.push_back(pair.first);
    }
    return ret;
}

bool SimpleKVDevDBSpace::remove(const std::string &key) {
    return map.erase(key) > 0;
}

void SimpleKVDevDBSpace::wipe() {

}
<file_sep>/test/SqlKVDatabaseTest.cpp
//
// Created by Rayer on 20/06/2018.
//

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <Data/DataPersistenceManager.h>
#include <Data/SqlKVDatabase.h>
#include <list>
#include <map>

using namespace Iris;

class SqlKVDatabase_Test : public ::testing::Test {

protected:
    KVDataPersistenceLayer *db = nullptr;
    //This map stores all record that DB operates, used to verdict correct answer.
    typedef std::map<std::string, KVSpace::ValueType> SpaceSpec;
    typedef std::map<std::string, SpaceSpec> DBSpec;
    DBSpec cache; //This cache is used in stress test case

    SqlKVDatabase_Test() {
        db = new SqlKVDatabase("localhost", "iris", "iris", "iris_test");
        srand((unsigned int) time(nullptr));
        db->wipe(true);
    }

    void SetUp() override {

    }

    void TearDown() override {
        //std::cout << "TearDown()" << std::endl;
        //Don't delete because it is singleton
        //delete db;
    }

    std::string generateRandomString(int min = 1, int max = 10) {
        static const char *rule = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        size_t rule_length = std::strlen(rule);
        std::string ret;
        int length = rand() % (max - min + 1) + min;
        for (int i = 0; i < length; ++i) ret += rule[rand() % rule_length];
        return ret;
    }

    void generateStressTestData(KVDataPersistenceLayer* dpl, int space_count = 100, int space_size_min = 1, int space_size_max = 10) {

        cache.clear();

        std::map<std::string, std::shared_ptr<KVSpace> > spaceList;
        for(int i = 0; i < space_count; ++i) {
            std::string space_name = generateRandomString(1, 16);
            spaceList.insert(std::make_pair(space_name, dpl->get_space(space_name)));
            //cache[space_name] = SpaceSpec{};

            for(std::pair<std::string, std::shared_ptr<KVSpace> > pair : spaceList) {
                int space_size = rand() % (space_size_max - space_size_min) + space_size_min;
                for(int j = 0; j < space_size; ++j) {
                    std::string column_name = generateRandomString(1, 10);
                    KVSpace::ValueType value = (rand() % 2) ? (KVSpace::ValueType)rand() : (KVSpace::ValueType)generateRandomString(1, 16);
                    cache[pair.first].insert(std::make_pair(column_name, value));
                    pair.second->set_value(column_name, value);
                }

            }

        }
    }
};


TEST_F(SqlKVDatabase_Test, SpaceAccessTest) {
    std::string SPACE_NAME = generateRandomString(4, 6);
    std::string KEY1 = generateRandomString(1, 10);
    std::string KEY2 = generateRandomString(1, 10);
    std::string STR_VALUE = generateRandomString(5, 20);
    int INT_VALUE = rand();
    std::shared_ptr<KVSpace> space = db->get_space(SPACE_NAME);
    space->set_value(KEY1, STR_VALUE);
    EXPECT_EQ(boost::get<std::string>(space->get_value(KEY1)), STR_VALUE);
    space->set_value(KEY2, INT_VALUE);
    EXPECT_EQ(boost::get<int>(space->get_value(KEY2)), INT_VALUE);
}

TEST_F(SqlKVDatabase_Test, SpaceAccessReferenceTest) {
    std::string space_name = generateRandomString();
    std::shared_ptr<KVSpace> space = db->get_space(space_name);
    std::string key = generateRandomString();
    std::string str_value = generateRandomString();
    space->set_value(key, str_value);
    std::shared_ptr<KVSpace> space2 = db->get_space(space_name);
    EXPECT_EQ(boost::get<std::string>(space2->get_value(key)), str_value);
}

TEST_F(SqlKVDatabase_Test, SpaceGetNilValueTest) {
    std::string space_name = generateRandomString();
    std::string key = generateRandomString();
    std::shared_ptr<KVSpace> space = db->get_space(space_name);
    EXPECT_ANY_THROW(space->get_value(key));
}

TEST_F(SqlKVDatabase_Test, StressTest) {
    generateStressTestData(db);
}
<file_sep>/test/SimpleKVDevDBTest.cpp
//
// Created by Rayer on 14/08/2017.
//

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <Data/SimpleKVDevDB.h>
#include <Data/DataPersistenceManager.h>

using namespace Iris;

class SimpleDevDB_Test : public ::testing::Test {

protected:
    KVDataPersistenceLayer *db = nullptr;


    void SetUp() override {
        db = DataPersistenceManager::getInstance();
        srand((unsigned int) time(nullptr));
        db->wipe(true);
    }

    void TearDown() override {
        //std::cout << "TearDown()" << std::endl;
        //Don't delete because it is singleton
        //delete db;
    }

    std::string generateRandomString(int min = 1, int max = 10) {
        static const char *rule = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        size_t rule_length = std::strlen(rule);
        std::string ret;
        int length = rand() % (max - min + 1) + min;
        for (int i = 0; i < length; ++i) ret += rule[rand() % rule_length];
        return ret;
    }


    std::map<std::string, std::shared_ptr<KVSpace>>
    generateTestData(int space_count = 100, int space_size_min = 1, int space_size_max = 10) {
        std::map<std::string, std::shared_ptr<KVSpace>> ret;
        for (int i = 0; i < space_count; i++) {
            //Generate name
            std::string name;
            do {
                name = generateRandomString(1, 16);
                if (ret.find(name) != ret.end()) name = "";
            } while (name.empty());

            std::shared_ptr<KVSpace> space = std::make_shared<SimpleKVDevDBSpace>();
            int space_size = rand() % (space_size_max - space_size_min) + space_size_min;
            for (int i = 0; i < space_size; ++i) {
                if (rand() % 2)
                    space->set_value(generateRandomString(1, 10), rand());
                else
                    space->set_value(generateRandomString(1, 10), generateRandomString(1, 16));
            }

            ret.insert({name, space});
        }
        return ret;
    }
};


TEST_F(SimpleDevDB_Test, SpaceAccessTest) {
    std::string SPACE_NAME = generateRandomString(4, 6);
    std::string KEY1 = generateRandomString(1, 10);
    std::string KEY2 = generateRandomString(1, 10);
    std::string STR_VALUE = generateRandomString(5, 20);
    int INT_VALUE = rand();
    std::shared_ptr<KVSpace> space = db->get_space(SPACE_NAME);
    space->set_value(KEY1, STR_VALUE);
    EXPECT_EQ(boost::get<std::string>(space->get_value(KEY1)), STR_VALUE);
    space->set_value(KEY2, INT_VALUE);
    EXPECT_EQ(boost::get<int>(space->get_value(KEY2)), INT_VALUE);
}

TEST_F(SimpleDevDB_Test, SpaceAccessReferenceTest) {
    std::string space_name = generateRandomString();
    std::shared_ptr<KVSpace> space = db->get_space(space_name);
    std::string key = generateRandomString();
    std::string str_value = generateRandomString();
    space->set_value(key, str_value);
    std::shared_ptr<KVSpace> space2 = db->get_space(space_name);
    EXPECT_EQ(boost::get<std::string>(space2->get_value(key)), str_value);
}

TEST_F(SimpleDevDB_Test, SpaceGetNilValueTest) {
    std::string space_name = generateRandomString();
    std::string key = generateRandomString();
    std::shared_ptr<KVSpace> space = db->get_space(space_name);
    EXPECT_ANY_THROW(space->get_value(key));
}


TEST_F(SimpleDevDB_Test, SpaceSerializeTest) {
    std::string space_name = generateRandomString();
    std::shared_ptr<KVSpace> space1 = db->get_space(space_name);
    std::string key1 = generateRandomString();
    std::string value1 = generateRandomString();
    std::string key2 = generateRandomString();
    int value2 = rand();
    space1->set_value(key1, value1);
    space1->set_value(key2, value2);
    std::static_pointer_cast<SimpleKVDevDBSpace>(space1)->serialize("/tmp/" + space_name + ".test");

    std::shared_ptr<KVSpace> space2 = db->get_space(generateRandomString());
    std::static_pointer_cast<SimpleKVDevDBSpace>(space2)->deserialize("/tmp/" + space_name + ".test");
    EXPECT_EQ(boost::get<std::string>(space1->get_value(key1)), boost::get<std::string>(space2->get_value(key1)));
    EXPECT_EQ(boost::get<int>(space1->get_value(key2)), boost::get<int>(space2->get_value(key2)));
    EXPECT_EQ(boost::get<int>(space1->get_value(key2)), value2);

}

TEST_F(SimpleDevDB_Test, DBSerializeTest) {
    SimpleKVDevDB *devDb = (SimpleKVDevDB *) db;
    devDb->wipe(true);
    std::string space_name1 = generateRandomString();
    std::string key1 = generateRandomString();
    std::string key2 = generateRandomString();
    std::string value1 = generateRandomString();
    int value2 = rand();
    std::shared_ptr<KVSpace> space1 = db->get_space(space_name1);
    space1->set_value(key1, value1);
    space1->set_value(key2, value2);
    std::string space_name2 = generateRandomString();
    std::shared_ptr<KVSpace> space2 = db->get_space(space_name2);
    std::string key3 = generateRandomString();
    std::string key4 = generateRandomString();
    std::string value3 = generateRandomString();
    int value4 = rand();
    space2->set_value(key3, value3);
    space2->set_value(key4, value4);

    devDb->serialize("/tmp");
    devDb->wipe(true);
    ASSERT_ANY_THROW(space1->get_value("AAA"));

    devDb->deserialize("/tmp");
    space1 = db->get_space(space_name1);
    EXPECT_EQ(boost::get<std::string>(space1->get_value(key1)), value1);
    EXPECT_EQ(boost::get<int>(space1->get_value(key2)), value2);
    space2 = db->get_space(space_name2);
    EXPECT_EQ(boost::get<std::string>(space2->get_value(key3)), value3);
    EXPECT_EQ(boost::get<int>(space2->get_value(key4)), value4);
}

//TODO: This test case yet have any ASSERT!
TEST_F(SimpleDevDB_Test, SimpleDevDBPersistTest) {
    SimpleKVDevDB *simpleDevDB = new SimpleKVDevDB();
    std::map<std::string, std::shared_ptr<KVSpace>> test_data_raw = generateTestData();

    for (auto const &pair : test_data_raw) {
        auto space = std::static_pointer_cast<SimpleKVDevDBSpace>(simpleDevDB->get_space(pair.first));
        auto keys = std::static_pointer_cast<SimpleKVDevDBSpace>(pair.second)->get_keys();
        for (const std::string &name : keys) {
            space->set_value(name, pair.second->get_value(name));
        }
    }

    //std::string folder = "/tmp/" + generateRandomString(5, 6);
    std::string folder = boost::filesystem::complete(boost::filesystem::temp_directory_path()).generic_string() + "/" +
                         generateRandomString(5, 6);

    boost::filesystem::remove(folder);
    simpleDevDB->serialize(folder);

    delete simpleDevDB;

    simpleDevDB = new SimpleKVDevDB();
    simpleDevDB->deserialize(folder);


}
<file_sep>/src/Data/SimpleKVDevDBSpace.h
//
// Created by Rayer on 15/08/2017.
//

#ifndef IRIS_SIMPLEDEVDBSPACE_H
#define IRIS_SIMPLEDEVDBSPACE_H


#include "KVSpace.h"
#include <map>
#include <boost/serialization/access.hpp>
#include <boost/serialization/map.hpp>
#include <list>

namespace Iris {
/**
 * DPL Space for development
 */
    class SimpleKVDevDBSpace : public KVSpace {

    private:
        std::map<std::string, ValueType> map;

    public:
        void serialize(std::string fullPath);

        void deserialize(std::string fullPath);

        ValueType get_value(const std::string &key) override;

        std::list<std::string> get_keys();

        void set_value(const std::string &key, const ValueType &value) override;

        bool remove(const std::string &key) override;

        void wipe() override;
    };

}
#endif //IRIS_SIMPLEDEVDBSPACE_H
<file_sep>/src/Main/GameMain.cpp
/**
 * Project Iris
 */


#include "GameMain.h"
#include "Context.h"
#include "MoMLayer.h"
#include <chrono>
#include <iostream>

using namespace Iris;

/**
 * GameMain implementation
 */


/**
 * @param context
 * @return void
 */
using namespace Iris;

void GameMain::loop(Context *context) {
    typedef std::chrono::high_resolution_clock time;
    typedef std::chrono::milliseconds ms;

    //initialize
    auto timer = time::now();

    while (context->isRunning()) {

        double delta = std::chrono::duration_cast<std::chrono::duration<double, std::ratio<1>>>(
                time::now() - timer).count();
        mom_layer->update(delta, context);
        timer = time::now();
    }

}<file_sep>/src/Main/GameMain.h
/**
 * Project Iris
 */


#ifndef _GAMEMAIN_H
#define _GAMEMAIN_H





namespace Iris {
    class Context;
    class MoMLayer;
    class GameMain {

    private:
        MoMLayer *mom_layer;

    public:
        /**
         * @param context
         */
        void loop(Context *context);
    };

}

#endif //_GAMEMAIN_H<file_sep>/src/Data/SqlKVDatabaseSpace.h
//
// Created by <NAME> on 2018/6/5.
//

#ifndef IRIS_SQLDATABASESPACE_H
#define IRIS_SQLDATABASESPACE_H

#include "KVSpace.h"
#include <mariadb++/connection.hpp>

namespace Iris {
    class SqlKVDatabaseSpace : public KVSpace {
    private:
        std::map<std::string, ValueType> map;
        std::string space_name;
        mariadb::connection_ref sql_connect;
    public:
        SqlKVDatabaseSpace(const std::string &name, mariadb::connection_ref conn);

        ValueType get_value(const std::string &key) override;

        void set_value(const std::string &key, const ValueType &value) override;

        bool remove(const std::string &key) override;

        void wipe() override;

        void refresh();
    };
}




#endif //IRIS_SQLDATABASESPACE_H
<file_sep>/src/Main/MoManager.cpp
/**
 * Project Iris
 */


#include <string>
#include "Iris_State.h"
#include "Context.h"
#include "Loopable.h"
#include "MoManager.h"
#include <boost/uuid/uuid_generators.hpp>

/**
 * MoManager implementation
 */
Iris_State Iris::MoManager::update(double delta, Context *context) {
    std::for_each(this->module_list.begin(), this->module_list.end(), [&](Loopable* loopable){ loopable->update(delta, context); });
    return context->isRunning() ? Iris_State::RUNNING : Iris_State::STOP;
}

boost::uuids::uuid Iris::MoManager::register_module(Loopable *module) {
    boost::uuids::random_generator gen;
    boost::uuids::uuid tag = gen();
    module->set_tag(tag);
    module_list.push_back(module);
    return tag;
}

bool Iris::MoManager::remove_module(boost::uuids::uuid id) {
    for (Loopable *loopable : module_list) {
        if (loopable->tag() == id) {
            module_list.remove(loopable);
            return true;
        }
    }
    return false;
}
<file_sep>/src/Data/SimpleKVDevDB.cpp
//
// Created by Rayer on 15/08/2017.
//

#include <sstream>
#include <boost/filesystem.hpp>
#include "SimpleKVDevDB.h"

using namespace boost::filesystem;
using namespace Iris;

std::shared_ptr<KVSpace> SimpleKVDevDB::get_space(const std::string &name) {
    auto iter = space_map.find(name);
    if (iter != space_map.end())
        return iter->second;

    std::shared_ptr<SimpleKVDevDBSpace> space = std::make_shared<SimpleKVDevDBSpace>();
    space_map.insert({name, space});
    return space;
}


void SimpleKVDevDB::serialize(std::string folder) {
    //Create folder if not exist
    boost::filesystem::create_directory(folder);
    for (auto iter : space_map) {
        const std::string &space_name = iter.first;
        std::ostringstream ss;
        ss << folder << "/" << space_name << ".sel";
        std::static_pointer_cast<SimpleKVDevDBSpace>(iter.second)->serialize(ss.str());
    }
}

void SimpleKVDevDB::deserialize(std::string folder) {
    path p(folder);

    if (!is_directory(p)) throw ("Illegal deserialize folder name");
    std::list<path> list;
    std::copy(directory_iterator(p), directory_iterator(), std::back_inserter(list));

    for (auto &file : list) {
        if (file.extension().generic_string() != ".sel") continue;
        std::shared_ptr<SimpleKVDevDBSpace> space = std::make_shared<SimpleKVDevDBSpace>();
        space->deserialize(complete(file).generic_string());
        space_map.insert(std::make_pair(file.stem().generic_string(), space));
    }

}

void SimpleKVDevDB::wipe(bool force) {
    space_map.clear();
}





