Skip to content

File environment.hpp

File List > backends > cxx > include > zmbt > model > environment.hpp

Go to the documentation of this file

#ifndef ZMBT_MODEL_ENVIRONMENT_HPP_
#define ZMBT_MODEL_ENVIRONMENT_HPP_


#include <boost/json.hpp>
#include <boost/mp11.hpp>
#include <zmbt/core/aliases.hpp>
#include <zmbt/core/format_string.hpp>
#include <zmbt/core/interface_id.hpp>
#include <zmbt/core/interface_traits.hpp>
#include <zmbt/core/json_node.hpp>
#include <zmbt/core/object_id.hpp>
#include <zmbt/model/test_failure.hpp>
#include <zmbt/reflect/signal_traits.hpp>
#include <zmbt/reflect/invocation.hpp>
#include <zmbt/reflect/prototypes.hpp>
#include <zmbt/reflect/serialization.hpp>
#include <zmbt/expr.hpp>
#include <exception>
#include <functional>
#include <iosfwd>
#include <map>
#include <memory>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <typeindex>
#include <utility>

#include "environment_data.hpp"
#include "exceptions.hpp"
#include "trigger.hpp"

namespace zmbt {


class Environment {

  protected:

    using lock_t = typename EnvironmentData::lock_t;

    template <class T>
    using return_t = reflect::invocation_ret_t<ifc_pointer_t<T>>;

    template <class T>
    using unqf_args_t = reflect::invocation_args_unqf_t<ifc_pointer_t<T>>;


    template <class T>
    using argsref_t = reflect::invocation_args_t<ifc_pointer_t<T>>;

    template <class T>
    using rvref_to_val = mp_if<std::is_rvalue_reference<T>, std::remove_reference_t<T>, T>;

    template <class I>
    using hookout_args_t = mp_transform<rvref_to_val, argsref_t<I>>;

    std::shared_ptr<EnvironmentData> data_;

    // TODO: move to persistent config
    using FailureHandler = std::function<void(boost::json::value const&)>;
    struct PersistentConfig
    {
        FailureHandler failure_handler {&zmbt::default_test_failure};
        bool pretty_print {false};
    };

    std::shared_ptr<PersistentConfig> config_;


  public:
    class InterfaceHandle;

    template <class Interface>
    class TypedInterfaceHandle;


    JsonNode& json_data()
    {
        return data_->json_data;
    }

    JsonNode const& json_data() const
    {
        return data_->json_data;
    }

    lock_t Lock() const;

    lock_t TryLock() const;

    lock_t DeferLock() const;

    Environment();

    Environment(Environment &&) = default;

    Environment(Environment const&) = default;

    Environment& operator=(Environment &&) = default;

    Environment& operator=(Environment const&) = default;


    virtual ~Environment()
    {
    }

    void DumpJsonData(std::ostream &os);


    void SetVar(lang::Expression const& key_expr, boost::json::value var);

    template <class T>
    void SetVar(lang::Expression const& key_expr, T var)
    {
        return SetVar(key_expr, json_from(var));
    }


    boost::json::value GetVarOrUpdate(lang::Expression const& key_expr, boost::json::value update_value);

    template <class T>
    T GetVarOrUpdate(lang::Expression const& key_expr, T update_value)
    {
        return dejsonize<T>(GetVarOrUpdate(key_expr, json_from(update_value)));
    }


    boost::json::value GetVarOrDefault(lang::Expression const& key_expr, boost::json::value default_value = {});

    template <class T>
    T GetVarOrDefault(lang::Expression const& key_expr, T default_value = reflect::signal_traits<T>::init())
    {
        return dejsonize<T>(GetVarOrDefault(key_expr, json_from(default_value)));
    }


    boost::json::value GetVar(lang::Expression const& key_expr);

    template <class T>
    T GetVar(lang::Expression const& key_expr)
    {
        return dejsonize<T>(GetVar(key_expr));
    }


    template <class T>
    void SetShared(lang::Expression const& key_expr, std::shared_ptr<T> data)
    {
        boost::json::string const key = key_expr.eval().as_string();
        EnvironmentData::shared_data_record const record { typeid(T), data };

        auto lock = Lock();

        if (data_->shared.count(key)){
            data_->shared.at(key) = record;
        }
        else {
            data_->shared.emplace(key, record);
        }
    }


    template <class T>
    std::shared_ptr<T> GetShared(lang::Expression const& key_expr) const
    {
        boost::json::string const key = key_expr.eval().as_string();
        auto found = data_->shared.cend();
        {
            auto lock = Lock();
            found = data_->shared.find(key);
        }
        if (found == data_->shared.cend())
        {
            return nullptr;
        }

        auto const record = found->second;
        if (std::type_index(typeid(T)) != record.first)
        {
            throw environment_error("GetShared invoked with incompatible type for `%s`", key);
        }

        return std::static_pointer_cast<T>(record.second);
    }

    template <class T, class... A>
    T& GetSharedRef(lang::Expression const& key_expr, A&&... args)
    {
        boost::json::string const key = key_expr.eval().as_string();
        auto lock = Lock();
        auto found = data_->shared.find(key);
        if (found == data_->shared.cend())
        {
            auto const shared = std::make_shared<T>(std::forward<A>(args)...);
            EnvironmentData::shared_data_record const record { typeid(T), shared };

            if (not data_->shared.emplace(key, record).second)
            {
                throw environment_error("GetSharedRef failed to create shared object at `%s`", key);
            }
            return *shared;
        }

        auto const record = found->second;
        if (std::type_index(typeid(T)) != record.first)
        {
            throw environment_error("GetSharedRef invoked with incompatible type for `%s`", key);
        }

        return *std::static_pointer_cast<T>(record.second);
    }

    bool ContainsShared(lang::Expression const& key_expr) const;


    void ResetInterfaceData();


    void ResetInterfaceDataFor(object_id obj);


    void ResetAll();


    void ResetAllFor(object_id obj);


    Environment& RegisterAction(lang::Expression const& key_expr, std::function<void()> action);


    Environment& RunAction(lang::Expression const& key_expr);


    template <class I>
    interface_id RegisterParametricTriggerIfc(I&& interface)
    {
        RegisterPrototypes(interface);
        TriggerIfc trigger_ifc{std::forward<I>(interface)};
        interface_id ifc_id{trigger_ifc.id()};
        auto key = format("/trigger_ifcs/%s", ifc_id);
        auto lock = Lock();
        data_->trigger_ifcs.emplace(ifc_id, std::move(trigger_ifc));
        data_->json_data(key) = 0; // TODO: timestamp
        return ifc_id;
    }

    template <class T>
    object_id RegisterParametricTriggerObj(T&& obj)
    {
        TriggerObj trigger_obj{std::forward<T>(obj)};
        object_id obj_id{trigger_obj.id()};
        auto key = format("/trigger_objs/%s", obj_id);

        auto lock = Lock();
        data_->trigger_objs.emplace(obj_id, std::move(trigger_obj));
        data_->json_data(key) = 0; // TODO: timestamp
        return obj_id;
    }

    static boost::json::string autokey(object_id const& obj_id, interface_id const& ifc_id)
    {
        return {format("%s:%s", obj_id.key(), ifc_id.key())};
    }

    boost::json::string GetOrRegisterParametricTrigger(object_id const& obj_id, interface_id const& ifc_id);

    template <class I, class H>
    Environment& RegisterTrigger(boost::json::string_view key, I&& interface, H&& host)
    {
        Trigger trigger{std::forward<H>(host), interface};
        auto const json_data_key = format("/triggers/%s", key);
        auto const err_msg = format("Trigger registering failed: key \"%s\" is taken", key);
        auto lock = Lock();
        auto const trigger_found =  data_->triggers.find(key);
        if (trigger_found != data_->triggers.cend())
        {
            if (trigger_found->second != trigger)
            {
                throw environment_error(err_msg);
            }
            return *this;
        }
        data_->triggers.emplace(key, trigger);
        data_->json_data(json_data_key) = 0; // TODO: timestamp
        RegisterInterface(key, interface, trigger.obj_id());
        return *this;
    }


    template <class I>
    Environment& RegisterTrigger(boost::json::string_view key, I&& interface)
    {
        return RegisterTrigger(key, std::forward<I>(interface), ifc_host_nullptr<I>);
    }

    template <class H, class I>
    boost::json::string RegisterAnonymousTrigger(I&& interface, H&& host)
    {
        auto key = autokey(host, interface);
        RegisterTrigger(key, interface, host);
        return key;
    }


    bool HasTrigger(boost::json::string_view key) const;

    bool HasAction(boost::json::string_view key) const;


    template <class I>
    Environment& RegisterPrototypes(I&& interface)
    {
        static_assert(is_ifc_handle<I>::value, "");
        auto const ifc_id = interface_id(interface);
        auto const obj_id = object_id{ifc_host_nullptr<I>};
        auto const proto_key = format("/prototypes/%s", ifc_id);
        auto const defobj_key = format("/refs/defobj/%s", ifc_id);
        auto const prototypes = reflect::prototypes<I>();

        auto lock = Lock();
        if (!data_->json_data.contains(proto_key))
        {
            data_->json_data(proto_key) = prototypes;
            data_->json_data(defobj_key) = obj_id;
        }
        return *this;
    }

    Environment& RegisterInterface(boost::json::string_view key, interface_id const& ifc_id, object_id const& obj_id);

    Environment& RegisterAnonymousInterface(interface_id const& ifc_id, object_id const& obj_id)
    {
        return RegisterInterface(autokey(obj_id, ifc_id), ifc_id, obj_id);
    }


    template <class I>
    enable_if_t<is_ifc_handle<I>::value, Environment&>
    RegisterInterface(boost::json::string_view key, I&& interface, object_id const& obj_id = object_id{ifc_host_nullptr<I>})
    {
        RegisterPrototypes(std::forward<I>(interface));
        return RegisterInterface(key, interface_id{std::forward<I>(interface)}, obj_id);
    }

    template <class I>
    enable_if_t<is_ifc_handle<I>::value, Environment&>
    RegisterAnonymousInterface(I&& interface, object_id const& obj_id = object_id{ifc_host_nullptr<I>})
    {
        RegisterPrototypes(std::forward<I>(interface));
        return RegisterInterface(autokey(obj_id, interface_id{interface}), interface_id(interface), obj_id);
    }


    PersistentConfig Config() const;


    Environment& SetPrettyPrint(bool const pretty_print = true);

    Environment& SetFailureHandler(std::function<void(boost::json::value const&)> const& fn);

    Environment& ResetFailureHandler();

    Environment& HandleTestFailure(boost::json::value const& diagnostics);

    object_id ObjectId(boost::json::string_view interface_key) const;

    interface_id InterfaceId(boost::json::string_view interface_key) const;

    boost::json::string GetOrRegisterInterface(object_id const& obj_id, interface_id const& ifc_id);

    object_id DefaultObjectId(interface_id const& ifc_id) const;

};

}  // namespace zmbt

#endif  // ZMBT_FIXTURE_ENVIRONMENT_STORE_HPP_