Clemapfel / jluna

Julia Wrapper for C++ with Focus on Safety, Elegance, and Ease of Use
https://clemens-cords.com/jluna
MIT License
239 stars 12 forks source link

Usertype best practices? #35

Closed paulerikf closed 1 year ago

paulerikf commented 1 year ago

Hey clem, haven't been working with Jluna for a few weeks, but back at it.

Got a couple of quick question about usertypes best practices.

  1. How should I be setting up a jluna usertype for something I already have a Julia side type for? Should I be making an equivalent type through the jluna usertype system and doing conversions Julia side (or using multiple dispatch when possible)? Or could I somehow link my jluna usertype to the existing Julia side struct?

  2. Can I use a usertype in another usertype? Is there anything I need to do to make that possible? Currently getting the following error when trying to build the example below: In template: no member named 'type_name' in 'jluna::detail::as_julia_type_aux<JlunaStamp>'

#ifndef JLUNA_WRAPPER_JLUNA_STAMP_H
#define JLUNA_WRAPPER_JLUNA_STAMP_H
#include <jluna.hpp>
using namespace jluna;

struct JlunaStamp {
    Int32 sec;
    UInt32 nanosec;

    JlunaStamp() {}
    JlunaStamp(Int32 sec, UInt32 nanosec)
    : sec(sec), nanosec(nanosec) {}

    static void addProperties(jluna::Module m = jl_main_module);
};

set_usertype_enabled(JlunaStamp);

#endif //JLUNA_WRAPPER_JLUNA_STAMP_H

-------------------

#include <jluna_wrapper/types/jluna_stamp.h>

void JlunaStamp::addProperties(jluna::Module m) {
    jluna::Usertype<JlunaStamp>::add_property<Int32>(
            "sec",
            [](JlunaStamp& in) -> Int32 {return in.sec;},
            [](JlunaStamp& out, Int32 in) -> void {out.sec = in;}
    );
    jluna::Usertype<JlunaStamp>::add_property<UInt32>(
            "nanosec",
            [](JlunaStamp& in) -> UInt32 {return in.nanosec;},
            [](JlunaStamp& out, UInt32 in) -> void {out.nanosec = in;}
    );

    Usertype<JlunaStamp>::implement(m);
}
#ifndef JLUNA_WRAPPER_JLUNA_HEADER_H
#define JLUNA_WRAPPER_JLUNA_HEADER_H
#include <jluna.hpp>
#include <jluna_wrapper/types/jluna_stamp.h>
using namespace jluna;

struct JlunaHeader {
    JlunaStamp stamp;
    std::string frame_id;

    JlunaHeader() {}
    JlunaHeader(JlunaStamp stamp, std::string frame_id)
    : stamp(stamp), frame_id(frame_id) {}

    static void addProperties(jluna::Module m = jl_main_module);
};

set_usertype_enabled(JlunaHeader);

#endif //JLUNA_WRAPPER_JLUNA_HEADER_H

---------------------------

#include <jluna_wrapper/types/jluna_header.h>

void JlunaHeader::addProperties(jluna::Module m) {
    jluna::Usertype<JlunaHeader>::add_property<JlunaStamp>(
            "stamp",
            [](JlunaHeader& in) -> JlunaStamp {return in.stamp;},
            [](JlunaHeader& out, JlunaStamp in) -> void {out.stamp = in;}
    );
    jluna::Usertype<JlunaHeader>::add_property<std::string>(
            "frame_id",
            [](JlunaHeader& in) -> std::string {return in.frame_id;},
            [](JlunaHeader& out, std::string in) -> void {out.frame_id = in;}
    );

    Usertype<JlunaHeader>::implement(m);
}
    jluna::initialize();
    jluna::Module m = jluna::Main.safe_eval("return jluna_wrapper");
    JlunaStamp::addProperties(m);
    JlunaHeader::addProperties(m);
Clemapfel commented 1 year ago

How should I be setting up a jluna usertype for something I already have a Julia side type for? Should I be making an equivalent type through the jluna usertype system and doing conversions Julia side (or using multiple dispatch when possible)? Or could I somehow link my jluna usertype to the existing Julia side struct?

Don't, if you already have a Julia-side type there is no point to using jluna::usertype. I think you misunderstood its purpose, it is specifically meant to create a not-yet-existing Julia-side type and make easy conversion possible in the C++ -> Julia direction. Everything else you should custom define.

If you already have a Julia-side type, simply write a wrapper like so:

# in julia
struct JuliaSideType
    member::Any
    JuliaSideType() = new(nothing)
end

julia_side_type_method(x::JuliaSideType) = println("do something");
// in cpp
class JuliaSideTypeWrapper
{
    public:
        // attach existing Julia-side value
        JuliaSideTypeWrapper(jluna::Proxy& in)
            : _value(in)
        {
             static auto* julia_side_type_type = Main["JuliaSideType"];
             assert(in.isa(julia_side_type_type));
        }

        // newly allocate Julia-side instance
        JuliaSideTypeWrapper() 
        {
             static auto new_julia_side_type = Main["JuliaSideType"];
             in = new_julia_side_type();
        }

        // allow for use with other proxy functions by allowing implicit conversion
        operator jluna::unsafe::Value*()
        {
            return in.operator unsafe::Value*();
        }

        // wrap method
        void julia_side_type_method()
        {
             jluna::Main["julia_side_type_method"](_value);
        }

    private: 
        jluna::Proxy _value;
}

(example untested, it's just to illustrate the design pattern)

Clemapfel commented 1 year ago

Can I use a usertype in another usertype? Is there anything I need to do to make that possible? Currently getting the following error when trying to build the example below: In template: no member named 'type_name' in 'jluna::detail::as_julia_type_aux'

You can, add the following before the declaration of JlunaHeader:

namespace jluna::detail
{
    template<>
    struct as_julia_type_aux<JlunaStamp>
    {
        static inline const std::string type_name = "JlunaStamp";
    };
}

It adds a case to the template meta function jluna::detail::as_julia_type_aux so it works, It's mentioned in the error but I also realize that template meta functions aren't a very accessible thing, so I might make a macro for this since the error isn't very descriptive.

Clemapfel commented 1 year ago

Rather than pasting the above mentioned code, you can now call make_usertype_implicitly_convertible(JlunaStamp) once #37 is merged.