Skip to content

File environment_interface_record.hpp

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

Go to the documentation of this file

#ifndef ZMBT_MODEL_ENVIRONMENT_INTERFACE_RECORD_HPP_
#define ZMBT_MODEL_ENVIRONMENT_INTERFACE_RECORD_HPP_


#include <chrono>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <thread>
#include <tuple>
#include <typeindex>
#include <type_traits>

#include "environment.hpp"
#include "expression.hpp"




namespace zmbt {



class Environment::InterfaceHandle
{

    mutable zmbt::Environment env;
    object_id refobj_;
    interface_id interface_;

protected:

    JsonNode captures;
    JsonNode injects;

public:

    interface_id interface() const
    {
        return interface_;
    }

    object_id refobj() const
    {
        return refobj_;
    }

    InterfaceHandle(Environment const& e, interface_id const& interface, object_id refobj);
    InterfaceHandle(Environment const& e, boost::json::string_view ref);

  public:

    InterfaceHandle(interface_id const& interface, object_id refobj);

    InterfaceHandle(interface_id const& interface, nullptr_t);

    InterfaceHandle(boost::json::string_view ref);


    template<class H, class E = mp_if<mp_not<is_pointer<H>>, void>>
    InterfaceHandle(interface_id const& interface, H const& obj)
        : InterfaceHandle(interface, std::addressof(obj))
    {
    }

    InterfaceHandle(InterfaceHandle const&) = default;
    InterfaceHandle(InterfaceHandle && o) = default;
    InterfaceHandle& operator=(InterfaceHandle const&) = default;
    InterfaceHandle& operator=(InterfaceHandle &&) = default;
    ~InterfaceHandle() = default;

    Environment& Env() const
    {
        return env;
    }

    boost::json::value const& PrototypeReturn() const;

    boost::json::value const& PrototypeArgs() const;

    boost::json::value GetInjection(boost::json::string_view group, boost::json::string_view jp, int const nofcall);

    boost::json::value GetInjectionReturn(boost::json::string_view jp, std::size_t const nofcall = 0)
    {
        return GetInjection("return", jp, nofcall);
    }

    boost::json::value GetInjectionReturn(std::size_t const nofcall = 0)
    {
        return GetInjectionReturn("", nofcall);
    }

    boost::json::value GetInjectionArgs(boost::json::string_view jp, std::size_t const nofcall = 0)
    {
        return GetInjection("args", jp, nofcall);
    }


    boost::json::value GetInjectionArgs(std::size_t const nofcall = 0)
    {
        return GetInjectionArgs("", nofcall);
    }


    void Inject(Expression const& e, boost::json::string_view op, boost::json::string_view group, boost::json::string_view jp = "");

    void InjectReturn(Expression const& e, SignalOperatorHandler const& op, boost::json::string_view jp = "")
    {
        return Inject(e, op.annotation(), "return", jp);
    }

    void InjectReturn(Expression const& e, boost::json::string_view jp = "")
    {
        return InjectReturn(e, {}, jp);
    }


    void InjectArgs(Expression const& e, SignalOperatorHandler const& op, boost::json::string_view jp = "")
    {
        return Inject(e, op.annotation(), "args", jp);
    }

    void InjectArgs(Expression const& e, boost::json::string_view jp = "")
    {
        return InjectArgs(e, {}, jp);
    }


    std::size_t ObservedCalls() const;


    boost::json::array ObservedArgs(int const nofcall = -1);


    boost::json::array CaptureSlice(boost::json::string_view signal_path, int start = 0, int stop = -1, int const step = 1);

    boost::json::array const& Captures();

    boost::json::value ObservedReturn(int const nofcall = -1);

    boost::json::string const& key() const;

    InterfaceHandle& RunAsAction();

    InterfaceHandle& RunAsTrigger(std::size_t const nofcall = 0);
};

template <class Interface>
class Environment::TypedInterfaceHandle : public Environment::InterfaceHandle
{

    using reflection  =  reflect::invocation<Interface const&>;
    using return_t    = typename reflection::return_t;
    using args_t      = typename reflection::args_t;
    using unqf_args_t = tuple_unqf_t<args_t>;

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

    using hookout_args_t = mp_transform<rvalue_reference_to_value, args_t>;

    std::size_t HookImpl(hookout_args_t & args)
    {
        auto const ts = get_ts();
        std::string const tid = get_tid();
        auto lock = Env().Lock();

        captures("/+") = {
            {"ts", ts},
            {"tid", tid },
            {"args", json_from(convert_tuple_to<unqf_args_t>(args))}
        };

        auto nofcall = captures().as_array().size() - 1;
        auto const injection = GetInjectionArgs(nofcall).as_array();
        if (injection.size() != std::tuple_size<unqf_args_t>())
        {
            throw model_error("invalid inject arguments arity");
        }

        auto args_out = dejsonize<unqf_args_t>(injection);
        tuple_exchange(args, args_out);

        // Produce
        return nofcall;
    }

    public:


    template <class H>
    TypedInterfaceHandle(interface_id const& interface, H const& refobj)
        : Environment::InterfaceHandle(interface, refobj)
    {
    }

    TypedInterfaceHandle(TypedInterfaceHandle const&) = default;
    TypedInterfaceHandle(TypedInterfaceHandle &&) = default;
    ~TypedInterfaceHandle() = default;

    return_t Hook(hookout_args_t args)
    {
        std::size_t nofcall;
        boost::json::value result;

        try
        {
            nofcall = HookImpl(args);
        }
        catch(const std::exception& e)
        {
            throw model_error("Hook #%d %s capture error: `%s`, args: %s"
                , nofcall, interface()
                , e.what(), json_from(args));
            return dejsonize<return_t>(nullptr);
        }

        try
        {
            result = GetInjectionReturn(nofcall);
        }
        catch(const std::exception& e)
        {
            throw model_error("Hook #%d %s return evaluation error: `%s`", nofcall, interface(), e.what());
            return dejsonize<return_t>(nullptr);
        }

        try
        {
            return dejsonize<return_t>(result);
        }
        catch(const std::exception& e)
        {
            throw model_error("Hook #%d %s return evaluation error, can't deserialize %s as %s",
                nofcall, interface(), boost::json::serialize(result), type_name<return_t>());
            return dejsonize<return_t>(nullptr);
        }
    }

    template <class... A>
    return_t Hook(A&&... arg)
    {
        hookout_args_t args {arg...};
        return Hook(args);
    }
};


template <class I>
Environment::TypedInterfaceHandle<I> InterfaceRecord(I const& interface, object_id const& obj = {ifc_host_nullptr<I>})
{
    Environment env {};
    env.RegisterPrototypes(interface);
    return {interface, obj};
}


}  // namespace zmbt

#endif  // ZMBT_MAPPING_ENVIRONMENT_INTERFACE_RECORD_HPP_