changeset 181:fbfc2555bda5

Common: cleanup
author David Demelier <markand@malikania.fr>
date Fri, 12 Oct 2018 20:24:40 +0200
parents 23ee2b6091e8
children 3107ce017c3a
files libcommon/CMakeLists.txt libcommon/malikania/game.hpp libcommon/malikania/loader.cpp libcommon/malikania/loader.hpp libcommon/malikania/locator.cpp libcommon/malikania/locator.hpp libcommon/malikania/network_stream.cpp libcommon/malikania/network_stream.hpp libcommon/malikania/socket.cpp libcommon/malikania/socket.hpp libcommon/malikania/tileset.hpp tests/tools/tileset/main.cpp
diffstat 12 files changed, 504 insertions(+), 561 deletions(-) [+]
line wrap: on
line diff
--- a/libcommon/CMakeLists.txt	Tue Aug 21 10:55:13 2018 +0200
+++ b/libcommon/CMakeLists.txt	Fri Oct 12 20:24:40 2018 +0200
@@ -23,12 +23,13 @@
 
 set(
     HEADERS
+    ${libmlk-common_SOURCE_DIR}/malikania/error/auth_error.hpp
     ${libmlk-common_SOURCE_DIR}/malikania/error/error.hpp
-    ${libmlk-common_SOURCE_DIR}/malikania/error/auth_error.hpp
     ${libmlk-common_SOURCE_DIR}/malikania/game.hpp
     ${libmlk-common_SOURCE_DIR}/malikania/loader.hpp
     ${libmlk-common_SOURCE_DIR}/malikania/locator.hpp
     ${libmlk-common_SOURCE_DIR}/malikania/size.hpp
+    ${libmlk-common_SOURCE_DIR}/malikania/socket.hpp
     ${libmlk-common_SOURCE_DIR}/malikania/tileset.hpp
     ${libmlk-common_SOURCE_DIR}/malikania/unicode.hpp
     ${libmlk-common_SOURCE_DIR}/malikania/util.hpp
@@ -39,6 +40,7 @@
     ${libmlk-common_SOURCE_DIR}/malikania/error/auth_error.cpp
     ${libmlk-common_SOURCE_DIR}/malikania/loader.cpp
     ${libmlk-common_SOURCE_DIR}/malikania/locator.cpp
+    ${libmlk-common_SOURCE_DIR}/malikania/socket.cpp
     ${libmlk-common_SOURCE_DIR}/malikania/unicode.cpp
     ${libmlk-common_SOURCE_DIR}/malikania/util.cpp
 )
--- a/libcommon/malikania/game.hpp	Tue Aug 21 10:55:13 2018 +0200
+++ b/libcommon/malikania/game.hpp	Fri Oct 12 20:24:40 2018 +0200
@@ -24,22 +24,19 @@
  * \brief Game description.
  */
 
-#include <cassert>
 #include <string>
-#include <vector>
 
 namespace mlk {
 
 /**
  * \brief Basic game description.
  */
-class game {
-public:
-    std::string name;
-    std::string version;
-    std::string requires;
-    std::string license;
-    std::string author;
+struct game {
+	std::string name;
+	std::string version;
+	std::string requires;
+	std::string license;
+	std::string author;
 };
 
 } // !mlk
--- a/libcommon/malikania/loader.cpp	Tue Aug 21 10:55:13 2018 +0200
+++ b/libcommon/malikania/loader.cpp	Fri Oct 12 20:24:40 2018 +0200
@@ -24,76 +24,85 @@
 
 namespace mlk {
 
+namespace {
+
+auto load_tileset_tile_properties(const nlohmann::json& json) -> tileset::properties_map
+{
+	auto it = json.find("properties");
+
+	if (it == json.end())
+		return {};
+
+	tileset::properties_map props;
+
+	for (auto pair = it->begin(); pair != it->end(); ++pair) {
+		// TODO: maxi convert
+		if (!pair->is_string())
+			continue;
+
+		props.emplace(pair.key(), pair->get<std::string>());
+	}
+
+	return props;
+}
+
+auto load_tileset_tiles_properties(const nlohmann::json& json) -> tileset::tile_properties_map
+{
+	auto tiles = json.find("tiles");
+
+	if (tiles == json.end())
+		return {};
+
+	tileset::tile_properties_map props;
+
+	for (auto it = tiles->begin(); it != tiles->end(); ++it)
+		props.emplace(std::stoi(it.key()), load_tileset_tile_properties(*it));
+
+	return props;
+}
+
+} // !namespace
+
 loader::loader(mlk::locator& locator)
-    : m_locator(locator)
+	: locator_(locator)
 {
 }
 
-game loader::load_game() const
+auto loader::get_locator() noexcept -> locator&
 {
-    auto value = nlohmann::json::parse(m_locator.read("game.json"));
-
-    if (!value.is_object()) {
-        throw std::runtime_error("game.json: not a JSON object");
-    }
-
-    return game{util::json::require_string(value, "/name"_json_pointer),
-                util::json::require_string(value, "/version"_json_pointer),
-                util::json::require_string(value, "/requires"_json_pointer),
-                value.count("license") > 0 ? value["license"] : "",
-                value.count("author") > 0 ? value["author"] : ""};
+	return locator_;
 }
 
-tileset::properties_map load_tileset_tile_properties(const nlohmann::json& json)
+auto loader::load_game() const -> game
 {
-    auto it = json.find("properties");
+	auto value = nlohmann::json::parse(locator_.read("game.json"));
 
-    if (it == json.end())
-        return {};
-
-    tileset::properties_map props;
+	if (!value.is_object())
+		throw std::runtime_error("game.json: not a JSON object");
 
-    for (auto pair = it->begin(); pair != it->end(); ++pair) {
-        // TODO: maxi convert
-        if (!pair->is_string())
-            continue;
-
-        props.emplace(pair.key(), pair->get<std::string>());
-    }
-
-    return props;
+	return game{
+		util::json::require_string(value, "/name"_json_pointer),
+		util::json::require_string(value, "/version"_json_pointer),
+		util::json::require_string(value, "/requires"_json_pointer),
+		value.count("license") > 0 ? value["license"] : "",
+		value.count("author") > 0 ? value["author"] : ""
+	};
 }
 
-tileset::tile_properties_map load_tileset_tiles_properties(const nlohmann::json& json)
+auto loader::load_tileset(std::string_view id) const -> tileset
 {
-    auto tiles = json.find("tiles");
-
-    if (tiles == json.end())
-        return {};
+	auto value = nlohmann::json::parse(locator_.read(id));
 
-    tileset::tile_properties_map props;
-
-    for (auto it = tiles->begin(); it != tiles->end(); ++it)
-        props.emplace(std::stoi(it.key()), load_tileset_tile_properties(*it));
-
-    return props;
-}
+	if (!value.is_object())
+		throw std::runtime_error("not a valid tileset");
 
-tileset loader::load_tileset(const std::string& id) const
-{
-    auto value = nlohmann::json::parse(m_locator.read(id));
-
-    if (!value.is_object())
-        throw std::runtime_error(id + ": not a valid tileset");
+	tileset tileset;
 
-    tileset tileset(
-        util::json::require_string(value, "/image"_json_pointer),
-        util::json::require_size(value, "/cell"_json_pointer)
-    );
+	tileset.image = util::json::require_string(value, "/image"_json_pointer);
+	tileset.cell = util::json::require_size(value, "/cell"_json_pointer);
+	tileset.tile_properties = load_tileset_tiles_properties(value);
 
-    tileset.set_tile_properties(load_tileset_tiles_properties(value));
-
-    return tileset;
+	return tileset;
 }
 
 } // !mlk
--- a/libcommon/malikania/loader.hpp	Tue Aug 21 10:55:13 2018 +0200
+++ b/libcommon/malikania/loader.hpp	Fri Oct 12 20:24:40 2018 +0200
@@ -23,9 +23,10 @@
 
 namespace mlk {
 
-class game;
+struct game;
+struct tileset;
+
 class locator;
-class tileset;
 
 /**
  * \brief Open resources files using a locator.
@@ -37,47 +38,44 @@
  */
 class loader {
 private:
-    mlk::locator& m_locator;
+	locator& locator_;
 
 public:
-    /**
-     * Construct the resources loader.
-     *
-     * \param locator the locator
-     */
-    loader(mlk::locator& locator);
+	/**
+	 * Construct the resources loader.
+	 *
+	 * \param locator the locator
+	 */
+	loader(locator& locator);
 
-    /**
-     * Virtual destructor defaulted.
-     */
-    virtual ~loader() noexcept = default;
+	/**
+	 * Virtual destructor defaulted.
+	 */
+	virtual ~loader() noexcept = default;
 
-    /**
-     * Get the underlying locator.
-     *
-     * \return the locator
-     */
-    inline mlk::locator& locator() noexcept
-    {
-        return m_locator;
-    }
+	/**
+	 * Get the underlying locator.
+	 *
+	 * \return the locator
+	 */
+	auto get_locator() noexcept -> locator&;
 
-    /**
-     * Load a game.
-     *
-     * \return the game
-     * \throw std::runtime_error on errors
-     */
-    virtual game load_game() const;
+	/**
+	 * Load a game.
+	 *
+	 * \return the game
+	 * \throw std::runtime_error on errors
+	 */
+	virtual auto load_game() const -> game;
 
-    /**
-     * Load a tileset.
-     *
-     * \param id the tileset id
-     * \return a tileset ready to use
-     * \throw std::runtime_error on errors
-     */
-    virtual tileset load_tileset(const std::string& id) const;
+	/**
+	 * Load a tileset.
+	 *
+	 * \param id the tileset id
+	 * \return a tileset ready to use
+	 * \throw std::runtime_error on errors
+	 */
+	virtual auto load_tileset(std::string_view id) const -> tileset;
 };
 
 } // !mlk
--- a/libcommon/malikania/locator.cpp	Tue Aug 21 10:55:13 2018 +0200
+++ b/libcommon/malikania/locator.cpp	Fri Oct 12 20:24:40 2018 +0200
@@ -20,37 +20,50 @@
 #include <cstring>
 #include <fstream>
 #include <iterator>
+#include <sstream>
 #include <stdexcept>
 
 #include "locator.hpp"
 
 namespace mlk {
 
+auto directory_locator::make_path(std::string_view id) -> std::string
+{
+	std::ostringstream oss;
+
+	oss << path_;
+	oss << "/";
+	oss << id;
+
+	return oss.str();
+}
+
 directory_locator::directory_locator(std::string path) noexcept
-    : m_path(std::move(path))
+	: path_(std::move(path))
 {
 }
 
-std::string directory_locator::read(const std::string &id)
+auto directory_locator::read(std::string_view id) -> std::string 
 {
-    std::ifstream in(m_path + "/" + id, std::ifstream::in | std::ifstream::binary);
+	std::ifstream in(make_path(id), std::ifstream::in | std::ifstream::binary);
 
-    if (!in) {
-        throw std::runtime_error(std::strerror(errno));
-    }
+	if (!in)
+		throw std::runtime_error(std::strerror(errno));
 
-    return std::string(std::istreambuf_iterator<char>(in.rdbuf()), std::istreambuf_iterator<char>());
+	return std::string(
+		std::istreambuf_iterator<char>(in.rdbuf()),
+		std::istreambuf_iterator<char>()
+	);
 }
 
-std::unique_ptr<std::istream> directory_locator::open(const std::string &id)
+auto directory_locator::open(std::string_view id) -> std::unique_ptr<std::istream> 
 {
-    auto ptr = std::make_unique<std::ifstream>(m_path + "/" + id);
+	auto ptr = std::make_unique<std::ifstream>(make_path(id));
 
-    if (!(*ptr)) {
-        throw std::runtime_error(std::strerror(errno));
-    }
+	if (!(*ptr))
+		throw std::runtime_error(std::strerror(errno));
 
-    return std::move(ptr);
+	return std::move(ptr);
 }
 
 } // !mlk
--- a/libcommon/malikania/locator.hpp	Tue Aug 21 10:55:13 2018 +0200
+++ b/libcommon/malikania/locator.hpp	Fri Oct 12 20:24:40 2018 +0200
@@ -19,9 +19,10 @@
 #ifndef MALIKANIA_LOCATOR_HPP
 #define MALIKANIA_LOCATOR_HPP
 
-#include <string>
+#include <istream>
 #include <memory>
-#include <istream>
+#include <string>
+#include <string_view>
 
 namespace mlk {
 
@@ -30,28 +31,28 @@
  */
 class locator {
 public:
-    /**
-     * Default destructor.
-     */
-    virtual ~locator() = default;
+	/**
+	 * Default destructor.
+	 */
+	virtual ~locator() = default;
 
-    /**
-     * Read a whole resource as a string.
-     *
-     * \param id the resource id
-     * \return the string
-     * \throw std::runtime_error on any errors
-     */
-    virtual std::string read(const std::string& id) = 0;
+	/**
+	 * Read a whole resource as a string.
+	 *
+	 * \param id the resource id
+	 * \return the string
+	 * \throw std::runtime_error on any errors
+	 */
+	virtual auto read(std::string_view id) -> std::string = 0;
 
-    /**
-     * Open a resource as a stream.
-     *
-     * \param id the resource id
-     * \return the stream
-     * \throw std::runtime_error on any errors
-     */
-    virtual std::unique_ptr<std::istream> open(const std::string& id) = 0;
+	/**
+	 * Open a resource as a stream.
+	 *
+	 * \param id the resource id
+	 * \return the stream
+	 * \throw std::runtime_error on any errors
+	 */
+	virtual auto open(std::string_view id) -> std::unique_ptr<std::istream> = 0;
 };
 
 /**
@@ -59,25 +60,27 @@
  */
 class directory_locator : public locator {
 private:
-    std::string m_path;
+	std::string path_;
+
+	auto make_path(std::string_view) -> std::string;
 
 public:
-    /**
-     * Load the game from the directory.
-     *
-     * \param path the base directory
-     */
-    directory_locator(std::string path) noexcept;
+	/**
+	 * Load the game from the directory.
+	 *
+	 * \param path the base directory
+	 */
+	directory_locator(std::string path) noexcept;
 
-    /**
-     * \copydoc locator::read
-     */
-    std::string read(const std::string& id) override;
+	/**
+	 * \copydoc locator::read
+	 */
+	auto read(std::string_view id) -> std::string override;
 
-    /**
-     * \copydoc locator::open
-     */
-    std::unique_ptr<std::istream> open(const std::string& id) override;
+	/**
+	 * \copydoc locator::open
+	 */
+	auto open(std::string_view id) -> std::unique_ptr<std::istream> override;
 };
 
 } // !mlk
--- a/libcommon/malikania/network_stream.cpp	Tue Aug 21 10:55:13 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,123 +0,0 @@
-/*
- * network_stream.cpp -- network socket
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#include <cassert>
-
-#include "network_stream.hpp"
-
-namespace mlk {
-
-void network_stream::do_recv(network_recv_handler handler)
-{
-    boost::asio::async_read_until(socket_, rbuffer_, "\r\n\r\n", [this, handler] (auto code, auto xfer) {
-        if (code)
-            handler(std::move(code), nullptr);
-        else if (xfer == 0U)
-            handler(make_error_code(boost::system::errc::network_down), nullptr);
-        else {
-            std::string str(
-                boost::asio::buffers_begin(rbuffer_.data()),
-                boost::asio::buffers_begin(rbuffer_.data()) + xfer - 4
-            );
-
-            // Remove early in case of errors.
-            rbuffer_.consume(xfer);
-
-            // TODO: catch nlohmann::json::parse_error when 3.0.0 is released.
-            nlohmann::json message;
-
-            try {
-                message = nlohmann::json::parse(str);
-            } catch (...) {}
-
-            if (!message.is_object())
-                handler(make_error_code(boost::system::errc::invalid_argument), nullptr);
-            else
-                handler(code, std::move(message));
-        }
-    });
-}
-
-void network_stream::do_send(const std::string& str, network_send_handler handler)
-{
-    boost::asio::async_write(socket_, boost::asio::buffer(str), [handler] (auto code, auto xfer) {
-        if (xfer == 0U)
-            handler(make_error_code(boost::system::errc::network_down));
-        else
-            handler(code);
-    });
-}
-
-void network_stream::rflush()
-{
-    if (rqueue_.empty())
-        return;
-
-    do_recv([this] (auto code, auto json) {
-        auto handler = rqueue_.front();
-
-        rqueue_.pop_front();
-        handler(code, std::move(json));
-
-        if (!code)
-            rflush();
-    });
-}
-
-void network_stream::sflush()
-{
-    if (squeue_.empty())
-        return;
-
-    do_send(squeue_.front().first, [this] (auto code) {
-        auto handler = squeue_.front().second;
-
-        squeue_.pop_front();
-
-        if (handler)
-            handler(code);
-        if (!code)
-            sflush();
-    });
-}
-
-void network_stream::recv(network_recv_handler handler)
-{
-    assert(handler);
-
-    auto in_progress = !rqueue_.empty();
-
-    rqueue_.push_back(std::move(handler));
-
-    if (!in_progress)
-        rflush();
-}
-
-void network_stream::send(nlohmann::json json, network_send_handler handler)
-{
-    assert(json.is_object());
-
-    auto in_progress = !squeue_.empty();
-
-    squeue_.emplace_back(json.dump(0) + "\r\n\r\n", std::move(handler));
-
-    if (!in_progress)
-        sflush();
-}
-
-} // !mlk
--- a/libcommon/malikania/network_stream.hpp	Tue Aug 21 10:55:13 2018 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,164 +0,0 @@
-/*
- * network_stream.hpp -- network socket
- *
- * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
- *
- * Permission to use, copy, modify, and/or distribute this software for any
- * purpose with or without fee is hereby granted, provided that the above
- * copyright notice and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
- * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
- * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
- * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
- * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
- * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
- */
-
-#ifndef MALIKANIA_COMMON_NETWORK_STREAM_HPP
-#define MALIKANIA_COMMON_NETWORK_STREAM_HPP
-
-/**
- * \file network_stream.cpp
- * \brief Network socket.
- */
-
-#include "sysconfig.hpp"
-
-#include <deque>
-#include <functional>
-#include <string>
-#include <utility>
-
-#include <boost/asio.hpp>
-#include <boost/asio/ssl.hpp>
-
-#include <json.hpp>
-
-namespace mlk {
-
-/**
- * Read handler.
- *
- * Call this function when a receive operation has finished on success or
- * failure.
- */
-using network_recv_handler = std::function<void (boost::system::error_code, nlohmann::json)>;
-
-/**
- * Send handler.
- *
- * Call this function when a send operation has finished on success or failure.
- */
-using network_send_handler = std::function<void (boost::system::error_code)>;
-
-/**
- * \brief Base shared network stream.
- *
- * This class can be used to perform I/O over a networking socket, it is
- * implemented as asynchronous operations over Boost.Asio.
- *
- * All recv/send operations are placed in a queue and performed when possible.
- */
-class network_stream {
-private:
-    using socket_t = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>;
-    using rbuffer_t = boost::asio::streambuf;
-    using rqueue_t = std::deque<network_recv_handler>;
-    using squeue_t = std::deque<std::pair<std::string, network_send_handler>>;
-
-    socket_t socket_;
-    rbuffer_t rbuffer_;
-    rqueue_t rqueue_;
-    squeue_t squeue_;
-
-    void rflush();
-    void sflush();
-    void do_recv(network_recv_handler);
-    void do_send(const std::string&, network_send_handler);
-
-public:
-    /**
-     * Construct the stream.
-     *
-     * \param service the IO service
-     * \param ctx the SSL context
-     */
-    inline network_stream(boost::asio::io_service& service,
-                          boost::asio::ssl::context& ctx)
-        : socket_(service, ctx)
-    {
-    }
-
-    /**
-     * Get the underlying socket.
-     *
-     * \return the socket
-     */
-    inline const socket_t& socket() const noexcept
-    {
-        return socket_;
-    }
-
-    /**
-     * Overloaded function.
-     *
-     * \return the socket
-     */
-    inline socket_t& socket() noexcept
-    {
-        return socket_;
-    }
-
-    /**
-     * Tells if receive operations are pending.
-     *
-     * \return true if receiving is in progress
-     */
-    inline bool is_receiving() const noexcept
-    {
-        return !rqueue_.empty();
-    }
-
-    /**
-     * Tells if send operations are pending.
-     *
-     * \return true if sending is in progress
-     */
-    inline bool is_sending() const noexcept
-    {
-        return !squeue_.empty();
-    }
-
-    /**
-     * Tells if there are any I/O pending.
-     *
-     * \return true if sending is in progress
-     */
-    inline bool is_active() const noexcept
-    {
-        return is_receiving() || is_sending();
-    }
-
-    /**
-     * Request a receive operation.
-     *
-     * \pre handler != nullptr
-     * \param handler the handler
-     */
-    void recv(network_recv_handler);
-
-    /**
-     * Request a send operation.
-     *
-     * \pre json.is_object()
-     * \param json the json message
-     * \param handler the optional handler
-     */
-    void send(nlohmann::json json, network_send_handler = nullptr);
-};
-
-} // !mlk
-
-#endif // MALIKANIA_COMMON_NETWORK_STREAM_HPP
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/malikania/socket.cpp	Fri Oct 12 20:24:40 2018 +0200
@@ -0,0 +1,154 @@
+/*
+ * socket.cpp -- SSL socket using JSON messages
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <cassert>
+
+#include "socket.hpp"
+
+namespace mlk {
+
+void socket::do_recv(recv_handler handler)
+{
+    boost::asio::async_read_until(socket_, rbuffer_, "\r\n\r\n", [this, handler] (auto code, auto xfer) {
+        if (code)
+            handler(std::move(code), nullptr);
+        else if (xfer == 0U)
+            handler(make_error_code(boost::system::errc::network_down), nullptr);
+        else {
+            std::string str(
+                boost::asio::buffers_begin(rbuffer_.data()),
+                boost::asio::buffers_begin(rbuffer_.data()) + xfer - 4
+            );
+
+            // Remove early in case of errors.
+            rbuffer_.consume(xfer);
+
+            // TODO: catch nlohmann::json::parse_error when 3.0.0 is released.
+            nlohmann::json message;
+
+            try {
+                message = nlohmann::json::parse(str);
+            } catch (...) {}
+
+            if (!message.is_object())
+                handler(make_error_code(boost::system::errc::invalid_argument), nullptr);
+            else
+                handler(code, std::move(message));
+        }
+    });
+}
+
+void socket::do_send(const std::string& str, send_handler handler)
+{
+    boost::asio::async_write(socket_, boost::asio::buffer(str), [handler] (auto code, auto xfer) {
+        if (xfer == 0U)
+            handler(make_error_code(boost::system::errc::network_down));
+        else
+            handler(code);
+    });
+}
+
+socket::socket(boost::asio::io_service& service,
+               boost::asio::ssl::context& ctx)
+	: socket_(service, ctx)
+{
+}
+
+auto socket::get_socket() const noexcept -> const socket_t&
+{
+	return socket_;
+}
+
+auto socket::get_socket() noexcept -> socket_t&
+{
+	return socket_;
+}
+
+auto socket::is_receiving() const noexcept -> bool
+{
+	return !rqueue_.empty();
+}
+
+auto socket::is_sending() const noexcept -> bool
+{
+	return !squeue_.empty();
+}
+
+auto socket::is_active() const noexcept -> bool
+{
+	return is_receiving() || is_sending();
+}
+
+void socket::rflush()
+{
+    if (rqueue_.empty())
+        return;
+
+    do_recv([this] (auto code, auto json) {
+        auto handler = rqueue_.front();
+
+        rqueue_.pop_front();
+        handler(code, std::move(json));
+
+        if (!code)
+            rflush();
+    });
+}
+
+void socket::sflush()
+{
+    if (squeue_.empty())
+        return;
+
+    do_send(squeue_.front().first, [this] (auto code) {
+        auto handler = squeue_.front().second;
+
+        squeue_.pop_front();
+
+        if (handler)
+            handler(code);
+        if (!code)
+            sflush();
+    });
+}
+
+void socket::recv(recv_handler handler)
+{
+    assert(handler);
+
+    auto in_progress = !rqueue_.empty();
+
+    rqueue_.push_back(std::move(handler));
+
+    if (!in_progress)
+        rflush();
+}
+
+void socket::send(nlohmann::json json, send_handler handler)
+{
+    assert(json.is_object());
+
+    auto in_progress = !squeue_.empty();
+
+    squeue_.emplace_back(json.dump(0) + "\r\n\r\n", std::move(handler));
+
+    if (!in_progress)
+        sflush();
+}
+
+} // !mlk
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libcommon/malikania/socket.hpp	Fri Oct 12 20:24:40 2018 +0200
@@ -0,0 +1,148 @@
+/*
+ * socket.hpp -- SSL socket using JSON messages
+ *
+ * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef MALIKANIA_COMMON_SOCKET_HPP
+#define MALIKANIA_COMMON_SOCKET_HPP
+
+/**
+ * \file socket.hpp
+ * \brief SSL socket using JSON messages
+ */
+
+#include "sysconfig.hpp"
+
+#include <deque>
+#include <functional>
+#include <string>
+#include <system_error>
+#include <utility>
+
+#include <boost/asio.hpp>
+#include <boost/asio/ssl.hpp>
+
+#include <json.hpp>
+
+namespace mlk {
+
+/**
+ * \brief SSL socket using JSON messages
+ *
+ * This class can be used to perform I/O over a networking socket, it is
+ * implemented as asynchronous operations over Boost.Asio.
+ *
+ * All recv/send operations are placed in a queue and performed when possible.
+ */
+class socket {
+public:
+	/**
+	 * Read handler.
+	 *
+	 * Call this function when a receive operation has finished on success or
+	 * failure.
+	 */
+	using recv_handler = std::function<void (std::error_code, nlohmann::json)>;
+
+	/**
+	 * Send handler.
+	 *
+	 * Call this function when a send operation has finished on success or failure.
+	 */
+	using send_handler = std::function<void (std::error_code)>;
+
+private:
+	using socket_t = boost::asio::ssl::stream<boost::asio::ip::tcp::socket>;
+	using rbuffer_t = boost::asio::streambuf;
+	using rqueue_t = std::deque<recv_handler>;
+	using squeue_t = std::deque<std::pair<std::string, send_handler>>;
+
+	socket_t socket_;
+	rbuffer_t rbuffer_;
+	rqueue_t rqueue_;
+	squeue_t squeue_;
+
+	void rflush();
+	void sflush();
+	void do_recv(recv_handler);
+	void do_send(const std::string&, send_handler);
+
+public:
+	/**
+	 * Construct the socket.
+	 *
+	 * \param service the IO service
+	 * \param ctx the SSL context
+	 */
+	socket(boost::asio::io_service& service,
+               boost::asio::ssl::context& ctx);
+
+	/**
+	 * Get the underlying socket.
+	 *
+	 * \return the socket
+	 */
+	auto get_socket() const noexcept -> const socket_t&;
+
+	/**
+	 * Overloaded function.
+	 *
+	 * \return the socket
+	 */
+	auto get_socket() noexcept -> socket_t&;
+
+	/**
+	 * Tells if receive operations are pending.
+	 *
+	 * \return true if receiving is in progress
+	 */
+	auto is_receiving() const noexcept -> bool;
+
+	/**
+	 * Tells if send operations are pending.
+	 *
+	 * \return true if sending is in progress
+	 */
+	auto is_sending() const noexcept -> bool;
+
+	/**
+	 * Tells if there are any I/O pending.
+	 *
+	 * \return true if sending is in progress
+	 */
+	auto is_active() const noexcept -> bool;
+
+	/**
+	 * Request a receive operation.
+	 *
+	 * \pre handler != nullptr
+	 * \param handler the handler
+	 */
+	void recv(recv_handler);
+
+	/**
+	 * Request a send operation.
+	 *
+	 * \pre json.is_object()
+	 * \param json the json message
+	 * \param handler the optional handler
+	 */
+	void send(nlohmann::json json, send_handler = nullptr);
+};
+
+} // !mlk
+
+#endif // MALIKANIA_COMMON_SOCKET_HPP
--- a/libcommon/malikania/tileset.hpp	Tue Aug 21 10:55:13 2018 +0200
+++ b/libcommon/malikania/tileset.hpp	Fri Oct 12 20:24:40 2018 +0200
@@ -36,113 +36,21 @@
 /**
  * \brief Map tileset definition.
  */
-class tileset {
-public:
-    /**
-     * Map of general properties.
-     */
-    using properties_map = std::unordered_map<std::string, std::string>;
-
-    /**
-     * Per tile properties.
-     */
-    using tile_properties_map = std::unordered_map<int, properties_map>;
-
-private:
-    std::string image_;
-    size cell_;
-    properties_map properties_;
-    tile_properties_map tile_properties_;
-
-public:
-    /**
-     * Constructor.
-     */
-    inline tileset(std::string image, size cell) noexcept
-        : image_(std::move(image))
-        , cell_(std::move(cell))
-    {
-    }
-
-    /**
-     * Get the image identifier.
-     *
-     * \return the image identifier
-     */
-    inline const std::string& get_image() const noexcept
-    {
-        return image_;
-    }
-
-    /**
-     * Get the tile cell size.
-     *
-     * \return the cell size
-     */
-    inline const size& get_cell() const noexcept
-    {
-        return cell_;
-    }
+struct tileset {
+	/**
+	 * Map of general properties.
+	 */
+	using properties_map = std::unordered_map<std::string, std::string>;
 
-    /**
-     * Get the general properties.
-     *
-     * \return the properties
-     */
-    inline properties_map& get_properties() noexcept
-    {
-        return properties_;
-    }
-
-    /**
-     * Get the general properties.
-     *
-     * \return the properties
-     */
-    inline const properties_map& get_properties() const noexcept
-    {
-        return properties_;
-    }
-
-    /**
-     * Set the general properties.
-     *
-     * \param properties the properties
-     */
-    inline void set_properties(properties_map properties) noexcept
-    {
-        properties_ = std::move(properties);
-    }
+	/**
+	 * Per tile properties.
+	 */
+	using tile_properties_map = std::unordered_map<int, properties_map>;
 
-    /**
-     * Get the per-tile properties.
-     *
-     * \return the properties for each tile
-     */
-    inline tile_properties_map& get_tile_properties() noexcept
-    {
-        return tile_properties_;
-    }
-
-    /**
-     * Get the per-tile properties.
-     *
-     * \return the properties for each tile
-     */
-    inline const tile_properties_map& get_tile_properties() const noexcept
-    {
-        return tile_properties_;
-    }
-
-    /**
-     * Set the per-tile properties.
-     *
-     * \param properties the properties for each tile
-     */
-    inline void set_tile_properties(tile_properties_map properties) noexcept
-    {
-        tile_properties_ = std::move(properties);
-    }
+	std::string image;
+	size cell;
+	properties_map properties;
+	tile_properties_map tile_properties;
 };
 
 } // !mlk
--- a/tests/tools/tileset/main.cpp	Tue Aug 21 10:55:13 2018 +0200
+++ b/tests/tools/tileset/main.cpp	Fri Oct 12 20:24:40 2018 +0200
@@ -28,35 +28,33 @@
 
 class test_tileset {
 protected:
-    mlk::directory_locator locator_{CMAKE_CURRENT_BINARY_DIR};
-    mlk::loader loader_{locator_};
+	mlk::directory_locator locator_{CMAKE_CURRENT_BINARY_DIR};
+	mlk::loader loader_{locator_};
 };
 
 BOOST_FIXTURE_TEST_SUITE(test_tileset_suite, test_tileset)
 
 BOOST_AUTO_TEST_CASE(tileset)
 {
-    const std::vector<std::string> list{
-        "tileset.json",
-        "tileset_embedded.json",
-        "tileset_external.json"
-    };
+	const std::vector<std::string> list{
+		"tileset.json",
+		"tileset_embedded.json",
+		"tileset_external.json"
+	};
 
-    for (const auto& t : list) {
-        auto tileset = loader_.load_tileset(t);
+	for (const auto& t : list) {
+		auto tileset = loader_.load_tileset(t);
 
-        BOOST_REQUIRE_EQUAL("worldmapchip5fo.png", tileset.get_image());
-        BOOST_REQUIRE_EQUAL(32U, tileset.get_cell().width);
-        BOOST_REQUIRE_EQUAL(32U, tileset.get_cell().height);
-        BOOST_REQUIRE_EQUAL(3U, tileset.get_tile_properties().size());
+		BOOST_REQUIRE_EQUAL("worldmapchip5fo.png", tileset.image);
+		BOOST_REQUIRE_EQUAL(32U, tileset.cell.width);
+		BOOST_REQUIRE_EQUAL(32U, tileset.cell.height);
+		BOOST_REQUIRE_EQUAL(3U, tileset.tile_properties.size());
 
-        auto tp = tileset.get_tile_properties();
+		// Index 8.
+		BOOST_REQUIRE_EQUAL("value", tileset.tile_properties[8]["test"]);
 
-        // Index 8.
-        BOOST_REQUIRE_EQUAL("value", tp[8]["test"]);
-
-        // TODO: postpone collisions once made.
-    }
+		// TODO: postpone collisions once made.
+	}
 }
 
 BOOST_AUTO_TEST_SUITE_END()