Skip to content

File trigger.hpp

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

Go to the documentation of this file

#ifndef ZMBT_MODEL_TRIGGER_HPP_
#define ZMBT_MODEL_TRIGGER_HPP_

#include <boost/type_index.hpp>

#include "zmbt/core.hpp"
#include "zmbt/reflect.hpp"
#include "output_recorder.hpp"

namespace zmbt {

namespace detail {

template <class T>
inline auto static_ptr_cast(std::shared_ptr<void> obj)
{
    return std::static_pointer_cast<T>(obj);
};

template <>
inline auto static_ptr_cast<nullptr_t>(std::shared_ptr<void> obj)
{
    return nullptr;
};

}



class TriggerObj
{
    object_id id_;
    bool is_unsafe_ptr_;
    std::shared_ptr<void> ptr_;

    static void stub_delete(void*) {}

public:

    template <class T>
    TriggerObj(std::shared_ptr<T> obj)
        : id_{(remove_cvref_t<T>*)(obj.get())}
        , is_unsafe_ptr_{false}
        , ptr_{obj}
    {
    }

    template <class T>
    TriggerObj(T* obj)
        : id_{obj}
        , is_unsafe_ptr_{true}
        , ptr_{std::shared_ptr<void>(obj, stub_delete)}
    {
    }

    template <class T>
    TriggerObj(T const* obj) : TriggerObj(const_cast<T*>(obj))
    {
    }

    template <class T>
    TriggerObj(T& obj) : TriggerObj(&obj)
    {
    }

    template <class T>
    TriggerObj(T const& obj) : TriggerObj(&obj)
    {
    }


    TriggerObj(nullptr_t)
        : id_{nullptr}
        , is_unsafe_ptr_{false}
        , ptr_{nullptr}
    {
    }

    virtual ~TriggerObj() = default;

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

    object_id id() const
    {
        return id_;
    }

    bool unsafe() const
    {
        return is_unsafe_ptr_;
    }

    std::shared_ptr<void> ptr() const
    {
        return ptr_;
    }
};



class TriggerIfc
{
    interface_id id_;
    std::function<void(std::shared_ptr<void>, boost::json::value const&, OutputRecorder&)> fn_;

    template <class R>
    static auto apply_fn(std::function<R()> fn) -> mp_if<is_void<R>, nullptr_t>
    {
        fn();
        return nullptr;
    }

    template <class R>
    static auto apply_fn(std::function<R()> fn) -> mp_if<mp_not<is_void<R>>, R>
    {
        return fn();
    }

public:

    template <class Interface>
    TriggerIfc(Interface&& interface)
        : id_{std::forward<Interface>(interface)}
        , fn_{[ifc_ptr = get_ifc_pointer(std::forward<Interface>(interface))]
            (std::shared_ptr<void> obj, boost::json::value const& args_in, OutputRecorder& recorder) {
            using reflection = reflect::invocation<Interface const&>;
            using return_t = typename reflection::return_t;
            using args_t = typename reflection::args_t;
            using args_unqf_t = tuple_unqf_t<args_t>;
            using ifc_host_unref_t = remove_reference_t<typename reflection::host_t>;

            using result_t = mp_if<is_void<return_t>, nullptr_t, remove_cvref_t<return_t>>;

            boost::json::value args_json_in_out;
            std::function<return_t()> fn;
            std::function<boost::json::value()> get_in_out_args = []{ return nullptr; };

            args_unqf_t args_to_capture = reflect::signal_traits<args_unqf_t>::init();
            ErrorOr<result_t> return_or_error {};

            try
            {
                args_unqf_t args_typed_unqf_in = args_in.is_array()
                    ? dejsonize<args_unqf_t>(args_in)
                    : dejsonize<args_unqf_t>(boost::json::array{args_in});

                args_t args_typed_in_out = convert_tuple_to<args_t>(args_typed_unqf_in);

                fn = [obj, ifc_ptr, args_typed_in_out]() -> return_t {
                    if (is_member_function_pointer<Interface>::value && !obj) {
                        throw_exception(environment_error("invoking mfp trigger with null object"));
                    }
                    // WARN: is_unsafe_ptr cast
                    // TODO: check type_index by comp option
                    return reflection::apply(detail::static_ptr_cast<ifc_host_unref_t>(obj), ifc_ptr, args_typed_in_out);
                };

                try
                {
                    return_or_error = ErrorOr<result_t>::MakeValue(apply_fn<return_t>(fn));
                }
                catch(const std::exception& e)
                {
                    auto const dynamic_exception_type = boost::typeindex::type_id_runtime(e).pretty_name();
                    ErrorInfo err;
                    err.type = dynamic_exception_type.c_str();
                    err.what = e.what();
                    err.context = "trigger execution";
                    return_or_error = ErrorOr<result_t>::MakeError(err);

                }
                catch(...)
                {
                    // TODO: try to get type info from std::exception_ptr

                    ErrorInfo err;
                    err.type = "unknown";
                    err.what = "unknown";
                    err.context = "trigger execution";
                    return_or_error = ErrorOr<result_t>::MakeError(err);
                }

                args_to_capture = convert_tuple_to<args_unqf_t>(args_typed_in_out);
            }
            catch(const std::exception& e)
            {
                auto const dynamic_exception_type = boost::typeindex::type_id_runtime(e).pretty_name();
                    ErrorInfo err;
                    err.type = dynamic_exception_type.c_str();
                    err.what = e.what();
                    err.context = "trigger args evaluation";
                    return_or_error = ErrorOr<result_t>::MakeError(err);
            }

            recorder.push(args_to_capture, return_or_error);
        }}
    {
    }

    virtual ~TriggerIfc() = default;

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

    interface_id id() const
    {
        return id_;
    }


    void execute(std::shared_ptr<void> obj, boost::json::value const& args_in, OutputRecorder& recorder) const
    {
        return fn_(obj, args_in, recorder);
    }
};

class Trigger final {
    struct internal_ctor {};
    TriggerObj obj_;
    TriggerIfc ifc_;
    std::shared_ptr<OutputRecorder> output_recorder_;

    Trigger(internal_ctor, TriggerObj const& obj, TriggerIfc const& ifc, std::shared_ptr<OutputRecorder> recorder)
        : obj_{obj}
        , ifc_{ifc}
        , output_recorder_{std::move(recorder)}
    {
    }


  public:

    ~Trigger() = default;
    Trigger(Trigger const&) = default;
    Trigger(Trigger &&) = default;
    Trigger& operator=(Trigger const&) = default;
    Trigger& operator=(Trigger &&) = default;

    template <class T, class Interface>
    Trigger(T&& obj, Interface&& interface, std::shared_ptr<OutputRecorder> recorder)
        : Trigger(internal_ctor{}, TriggerObj(std::forward<T>(obj)), TriggerIfc(std::forward<Interface>(interface)), recorder)
    {
    }


    void operator()(boost::json::value args_in) const
    {
        ifc_.execute(obj_.ptr(), std::move(args_in), *output_recorder_);
    }

    bool operator==(Trigger const& o) const
    {
        return (obj_.id() == o.obj_.id()) && (ifc_.id() == o.ifc_.id());
    }

    bool operator!=(Trigger const& o) const
    {
        return !this->operator==(o);
    }

    object_id obj_id() const
    {
        return obj_.id();
    }

    interface_id ifc_id() const
    {
        return ifc_.id();
    }

};


}  // namespace zmbt

#endif