#include <spdlog/spdlog.h>
#include <functional>
#include <iostream>
#include <random>
#include <string>
#include <thread>
std::string
make_uuid()
{
static std::random_device dev;
static std::mt19937 rng(dev());
std::uniform_int_distribution<int> dist(0, 15);
const char* v = "0123456789abcdef";
const std::array dash_pattern{
false, false, false, false, true, false, true, false,
true, false, true, false, false, false, false, false,
};
std::string res;
for (bool dash : dash_pattern) {
if (dash) {
res += "-";
}
res += v[dist(rng)];
res += v[dist(rng)];
}
return res;
}
struct Player {
int experience;
int hitpoints;
std::string json_type;
int level;
bool logged_in;
std::string name;
std::string uuid;
};
template<>
struct tao::json::traits<Player> {
template<template<typename...> class Traits>
static void assign(tao::json::basic_value<Traits>& v, const Player& p)
{
v = { { "experience", p.experience },
{ "hitpoints", p.hitpoints },
{ "jsonType", p.json_type },
{ "level", p.level },
{ "loggedIn", p.logged_in },
{ "name", p.name },
{ "uuid", p.uuid } };
}
template<template<typename...> class Traits>
static void to(const tao::json::basic_value<Traits>& v, Player& p)
{
v.at("experience").to(p.experience);
v.at("hitpoints").to(p.hitpoints);
v.at("jsonType").to(p.json_type);
v.at("level").to(p.level);
v.at("loggedIn").to(p.logged_in);
v.at("name").to(p.name);
v.at("uuid").to(p.uuid);
}
};
struct Monster {
int experience_when_killed;
int hitpoints;
double item_probability;
std::string json_type;
std::string name;
std::string uuid;
};
template<>
struct tao::json::traits<Monster> {
template<template<typename...> class Traits>
static void assign(tao::json::basic_value<Traits>& v, const Monster& m)
{
v = { { "experienceWhenKilled", m.experience_when_killed },
{ "hitpoints", m.hitpoints },
{ "itemProbability", m.item_probability },
{ "jsonType", m.json_type },
{ "name", m.name },
{ "uuid", m.uuid } };
}
template<template<typename...> class Traits>
static void to(const tao::json::basic_value<Traits>& v, Monster& m)
{
v.at("experienceWhenKilled").to(m.experience_when_killed);
v.at("hitpoints").to(m.hitpoints);
v.at("itemProbability").to(m.item_probability);
v.at("jsonType").to(m.json_type);
v.at("name").to(m.name);
v.at("uuid").to(m.uuid);
}
};
class GameServer
{
private:
std::shared_ptr<transactions> transactions_;
public:
{
}
[[nodiscard]] int calculate_level_for_experience(int experience) const
{
return experience / 100;
}
void player_hits_monster(int damage_,
const std::string& player_id,
const std::string& monster_id,
std::atomic<bool>& exists)
{
transactions_->run([&](std::shared_ptr<attempt_context> ctx) ->
couchbase::error {
auto [e, monster] = ctx->get(
collection, monster_id);
std::cout << "monster no longer exists" << std::endl;
exists = false;
return {};
}
const Monster& monster_body = monster.content_as<Monster>();
int monster_hitpoints = monster_body.hitpoints;
int monster_new_hitpoints = monster_hitpoints - damage_;
std::cout << "Monster " << monster_id << " had " << monster_hitpoints << " hitpoints, took "
<< damage_ << " damage, now has " << monster_new_hitpoints << " hitpoints"
<< std::endl;
auto [e2, player] = ctx->get(
collection, player_id);
if (e2.ec()) {
return e2;
}
if (monster_new_hitpoints <= 0) {
ctx->remove(monster);
const Player& player_body = player.content_as<Player>();
int experience_for_killing_monster = monster_body.experience_when_killed;
int player_experience = player_body.experience;
int player_new_experience = player_experience + experience_for_killing_monster;
int player_new_level = calculate_level_for_experience(player_new_experience);
std::cout << "Monster " << monster_id << " was killed. Player " << player_id << " gains "
<< experience_for_killing_monster << " experience, now has level "
<< player_new_level << std::endl;
Player player_new_body = player_body;
player_new_body.experience = player_new_experience;
player_new_body.level = player_new_level;
ctx->replace(player, player_new_body);
} else {
std::cout << "Monster " << monster_id << " is damaged but alive" << std::endl;
Monster monster_new_body = monster_body;
monster_new_body.hitpoints = monster_new_hitpoints;
ctx->replace(monster, monster_new_body);
}
return {};
});
if (err.ec()) {
std::cout << "txn error during player_hits_monster: " << err.ec().message() << ", "
<< (err.cause().has_value() ? err.cause().value().ec().message() : "") << std::endl;
}
}
};
int
main()
{
constexpr int NUM_THREADS = 4;
std::atomic<bool> monster_exists = true;
std::string bucket_name = "default";
std::uniform_int_distribution<int> hit_distribution(1, 6);
std::mt19937 random_number_engine;
auto rand = std::bind(hit_distribution, random_number_engine);
options.transactions().cleanup_config().cleanup_window(std::chrono::seconds(5));
options.transactions().cleanup_config().cleanup_lost_attempts(true);
options.transactions().cleanup_config().cleanup_client_attempts(true);
options.transactions().timeout(std::chrono::milliseconds(100));
if (connect_err) {
std::cout << "Error opening cluster: " << fmt::format("{}", connect_err) << std::endl;
return -1;
}
std::string player_id{ "player_data" };
Player player_data{ 14248, 23832, "player", 141, true, "Jane", make_uuid() };
std::string monster_id{ "a_grue" };
Monster monster_data{ 91, 4000, 0.19239324085462631, "monster", "Grue", make_uuid() };
{
if (!err) {
std::cout << "Upserted sample player document: " << player_id
<< "with cas:" << resp.cas().value() << std::endl;
}
}
{
if (!err) {
std::cout << "Upserted sample monster document: " << monster_id
<< "with cas:" << resp.cas().value() << std::endl;
}
}
std::vector<std::thread> threads;
threads.reserve(NUM_THREADS);
for (int i = 0; i < NUM_THREADS; i++) {
threads.emplace_back(
[&rand, player_id,
collection, monster_id, &monster_exists, &game_server]() {
while (monster_exists.load()) {
std::cout << "[thread " << std::this_thread::get_id() << "]Monster exists -- lets hit it!"
<< std::endl;
game_server.player_hits_monster(
rand() % 80,
collection, player_id, monster_id, monster_exists);
}
});
}
for (auto& t : threads) {
if (t.joinable()) {
t.join();
}
}
}
Definition cluster_options.hxx:44
The cluster is the main entry point when connecting to a Couchbase cluster.
Definition cluster.hxx:60
static void connect(const std::string &connection_string, const cluster_options &options, cluster_connect_handler &&handler)
Connect to a Couchbase cluster.
void close(std::function< void()> &&handler)
auto bucket(std::string_view bucket_name) const -> bucket
Opens a bucket with the given name.
The collection provides access to all collection APIs.
Definition collection.hxx:70
void upsert(std::string document_id, codec::encoded_value document, const upsert_options &options, upsert_handler &&handler) const
Upserts an encoded body of the document which might or might not exist yet, with custom options.
Base class for operations of data service.
Definition result.hxx:32
The transactions object is used to initiate a transaction.
Definition transactions.hxx:39
void initialize_console_logger()
void set_level(log_level level)
Definition transactions.hxx:28
@ majority
The mutation must be replicated to a majority of the Data Service nodes (that is, held in the memory ...