Couchbase C++ SDK 1.0.2 (rev. 51f4775)
Loading...
Searching...
No Matches
game_server.cxx
/* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
* Copyright 2021-Present Couchbase, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <spdlog/spdlog.h>
#include <functional>
#include <iostream>
#include <random>
#include <string>
#include <thread>
using namespace couchbase::transactions;
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:
explicit GameServer(couchbase::cluster& cluster)
: transactions_(cluster.transactions())
{
}
[[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)
{
auto [err, result] =
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()) {
// rollback - propagate the error
return e2;
}
if (monster_new_hitpoints <= 0) {
// Monster is killed. The remove is just for demoing, and a more realistic examples would
// set a "dead" flag or similar.
ctx->remove(monster);
const Player& player_body = player.content_as<Player>();
// the player earns experience for killing the monster
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; // pseudorandom number generator
auto rand = std::bind(hit_distribution, random_number_engine);
auto options = couchbase::cluster_options("Administrator", "password");
options.transactions().cleanup_config().cleanup_window(std::chrono::seconds(5));
options.transactions().durability_level(couchbase::durability_level::majority);
options.transactions().cleanup_config().cleanup_lost_attempts(true);
options.transactions().cleanup_config().cleanup_client_attempts(true);
options.transactions().timeout(std::chrono::milliseconds(100));
auto [connect_err, cluster] = couchbase::cluster::connect("couchbase://localhost", options).get();
if (connect_err) {
std::cout << "Error opening cluster: " << fmt::format("{}", connect_err) << std::endl;
return -1;
}
auto collection = cluster.bucket("default").default_collection();
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() };
// upsert a player document
{
auto [err, resp] = collection.upsert(player_id, player_data, {}).get();
if (!err) {
std::cout << "Upserted sample player document: " << player_id
<< "with cas:" << resp.cas().value() << std::endl;
}
}
// upsert a monster document
{
auto [err, resp] = collection.upsert(monster_id, monster_data, {}).get();
if (!err) {
std::cout << "Upserted sample monster document: " << monster_id
<< "with cas:" << resp.cas().value() << std::endl;
}
}
GameServer game_server(cluster);
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);
}
});
}
// wait for all threads to finish...
for (auto& t : threads) {
if (t.joinable()) {
t.join();
}
}
// close the cluster...
cluster.close().get();
}
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.
Definition error.hxx:30
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 ...