From 05298b6111c89f9016fff111cb71851f086e2e1c Mon Sep 17 00:00:00 2001 From: King_DuckZ Date: Sat, 25 May 2024 01:47:51 +0200 Subject: [PATCH] Add support for object parameters to Wren calls --- examples/call_cpp/main.cpp | 175 ++++++++++++++++++++++ examples/call_cpp/meson.build | 5 + examples/greet/main.cpp | 4 +- examples/meson.build | 1 + include/wrenpp/detail/foreign_object.hpp | 63 ++++++++ include/wrenpp/detail/setters_getters.hpp | 13 +- include/wrenpp/handle.hpp | 4 +- include/wrenpp/vm_fun.hpp | 42 ++---- src/handle.cpp | 12 +- 9 files changed, 282 insertions(+), 37 deletions(-) create mode 100644 examples/call_cpp/main.cpp create mode 100644 examples/call_cpp/meson.build create mode 100644 include/wrenpp/detail/foreign_object.hpp diff --git a/examples/call_cpp/main.cpp b/examples/call_cpp/main.cpp new file mode 100644 index 0000000..5870a35 --- /dev/null +++ b/examples/call_cpp/main.cpp @@ -0,0 +1,175 @@ +/* Copyright 2020-2024, Michele Santullo + * This file is part of wrenpp. + * + * Wrenpp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Wrenpp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wrenpp. If not, see . + */ + +#include "wrenpp/vm_fun.hpp" +#include "wrenpp/def_configuration.hpp" +#include "wrenpp/callback_manager.hpp" +#include "wrenpp/class_manager.hpp" +#include +#include +#include + +namespace { +constexpr char g_module_a[] = +"foreign class ClassA {" R"script( + construct new(msg) { + System.print("Wren ClassA constructed") + } + + foreign say_hi() +} +)script"; + +constexpr char g_module_b[] = "" +R"script( +import "module_a" for ClassA + +foreign class ClassB { + construct new(nickname) { + System.print("Wren ClassB constructed") + } + + do_action(obj_a, times) { + System.print("Wren ClassB is doing its action %(times) times...") + for (z in 1..times) { + obj_a.say_hi() + } + greeting_message("John", "Doe", "\\(O.O)/") + } + + foreign greeting_message(first_name, family_name, emoji) +} +)script"; + +//This is three examples in one: +//1. instantiate ClassA in Wren and pass it to do_action() which is a +// non-foreign method in wren +//2. instantiate ClassA in C++, have C++ invoke the non-foreign method +// do_action() on the object obj_b from the previous execution and pass the +// C++ instance of ClassA to it +//3. instantiate ClassB in C++, have C++ invoke the non-foreign method +// do_action() on it and pass the same ClassA instance from the previous +// step to it +constexpr char g_script[] = "" +R"script( +import "module_a" for ClassA +import "module_b" for ClassB + +var obj_b = ClassB.new("TheWren") +obj_b.do_action(ClassA.new("instanciated from script"), 2) +obj_b.greeting_message("Jane", "Doe", "ʕ·͡ᴥ·ʔ") +)script"; + +class MyWrenConfig : public wren::DefConfiguration { +public: + char* load_module_fn (wren::VM* vm, std::string_view module_name) { + if (module_name == "module_a") + return copied(g_module_a, sizeof(g_module_a)); + else if (module_name == "module_b") + return copied(g_module_b, sizeof(g_module_b)); + return nullptr; + } + + void load_module_complete_fn (wren::VM* vm, std::string_view module_name, char* buff) { + static_cast(vm); + static_cast(module_name); + delete[] buff; + } + +private: + char* copied (const char* source, std::size_t len) { + char* const buff = new char[len]; + std::copy(source, source + len / sizeof(source[0]), buff); + return buff; + } +}; + +struct ClassA { + explicit ClassA (std::string msg) : m_message(msg) { + std::cout << "C++ ClassA constructed\n"; + } + ClassA (const ClassA&) = delete; + ClassA (ClassA&&) = delete; + + void say_hi() { + std::cout << "C++ ClassA says \"" << m_message << "\"!\n"; + } + + std::string m_message; +}; + +class ClassB { +public: + explicit ClassB (std::string nickname) : m_nickname(nickname) { + std::cout << "C++ ClassB constructed\n"; + } + + ClassB (const ClassB&) = delete; + ClassB (ClassB&&) = delete; + + void greeting_message ( + std::string_view first_name, + const char* family_name, + const std::string& emoji + ) { + std::cout << "C++ ClassB says \"hello " << first_name << " '" << + m_nickname << "' " << family_name << "\" " << emoji << '\n'; + } + +private: + std::string m_nickname; +}; +} //unnamed namespace + +int main() { + using wren::make_function_bindable; + using wren::make_foreign_class; + using wren::make_wren_object; + + //We need a custom one to deal with custom module loading for + //module_a and module_b + MyWrenConfig config; + wren::VM vm(&config, nullptr); + + + vm.callback_manager() + .add_callback(false, "module_a", "ClassA", "say_hi()", make_function_bindable<&ClassA::say_hi>) + .add_callback(false, "module_b", "ClassB", "greeting_message(_,_,_)", make_function_bindable<&ClassB::greeting_message>) + ; + vm.class_manager() + .add_class_maker("module_b", "ClassB", make_foreign_class) + .add_class_maker("module_a", "ClassA", make_foreign_class) + ; + + //Example 1: invoke obj_b.do_action() from Wren passing an obj_a that's + //also instantiated from within the Wren script + vm.interpret("main", g_script); + + //Example 2: invoke obj_b.do_action() from C++ passing a new obj_a which + //is instantiated from C++ + wren::ForeignObject obj_a = make_wren_object(vm, wren::MN<"module_a", "ClassA">, "instantiated from c++"); + wren::call(vm, wren::MN<"main", "obj_b">, "do_action", obj_a, 3); + + //Example 3: invoke obj_b.do_action() from C++, where obj_b is also + //instantiated from C++, and pass the C++ obj_a instance to it + auto obj_b = make_wren_object(vm, wren::MN<"module_b", "ClassB">, "TheCpp"); + wren::call(vm, obj_b, "do_action", obj_a, 4); + + std::cout << "Quitting in 1 sec" << std::endl; + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + return 0; +} diff --git a/examples/call_cpp/meson.build b/examples/call_cpp/meson.build new file mode 100644 index 0000000..fc7a39a --- /dev/null +++ b/examples/call_cpp/meson.build @@ -0,0 +1,5 @@ +executable('call_cpp', + 'main.cpp', + dependencies: wrenpp_dep, + install: false, +) diff --git a/examples/greet/main.cpp b/examples/greet/main.cpp index 7357397..6585dba 100644 --- a/examples/greet/main.cpp +++ b/examples/greet/main.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020-2022, Michele Santullo +/* Copyright 2020-2024, Michele Santullo * This file is part of wrenpp. * * Wrenpp is free software: you can redistribute it and/or modify @@ -63,8 +63,6 @@ void user_get_env (wren::VM& vm, wren::ModuleAndName) { } //unnamed namespace int main() { - //typedef wren::ModuleAndName MN; - wren::DefConfiguration config; wren::VM vm(&config, nullptr); vm.callback_manager().add_callback(true, "main", "User", "get_env(_)", [](){return &user_get_env;}); diff --git a/examples/meson.build b/examples/meson.build index 25b06ea..4d7752e 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -2,3 +2,4 @@ subdir('dieroll') subdir('greet') subdir('calendar') subdir('math_vector') +subdir('call_cpp') diff --git a/include/wrenpp/detail/foreign_object.hpp b/include/wrenpp/detail/foreign_object.hpp new file mode 100644 index 0000000..6c7e78a --- /dev/null +++ b/include/wrenpp/detail/foreign_object.hpp @@ -0,0 +1,63 @@ +/* Copyright 2020-2024, Michele Santullo + * This file is part of wrenpp. + * + * Wrenpp is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Wrenpp is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with wrenpp. If not, see . + */ + +#pragma once + +#include "wrenpp/handle.hpp" +#include + +namespace wren { + +template +class ForeignObject { +public: + ForeignObject() = default; + ForeignObject (std::nullptr_t) : ForeignObject() {} + ForeignObject (T* obj, Handle&& handle); + ~ForeignObject() noexcept = default; + + void set_to_slot (int num); + T& object() { return *m_object; } + const T& object() const { return *m_object; } + Handle& handle() { return m_handle; } + const Handle& handle() const { return m_handle; } + bool is_valid() const { return nullptr != m_object; } + + operator T&() { return object(); } + operator const T&() const { return object(); } + operator Handle&() { return handle(); } + operator const Handle&() const { return handle(); } + bool operator!= (std::nullptr_t) const { return nullptr != m_object; } + bool operator== (std::nullptr_t) const { return nullptr == m_object; } + +private: + Handle m_handle; + T* m_object{nullptr}; +}; + +template +inline ForeignObject::ForeignObject (T* obj, Handle&& handle) : + m_handle(std::move(handle)), + m_object(obj) +{ } + +template +inline void ForeignObject::set_to_slot (int num) { + m_handle.set_to_slot(num); +} + +} //namespace wren diff --git a/include/wrenpp/detail/setters_getters.hpp b/include/wrenpp/detail/setters_getters.hpp index 472359d..2e0c82c 100644 --- a/include/wrenpp/detail/setters_getters.hpp +++ b/include/wrenpp/detail/setters_getters.hpp @@ -1,4 +1,4 @@ -/* Copyright 2020-2022, Michele Santullo +/* Copyright 2020-2024, Michele Santullo * This file is part of wrenpp. * * Wrenpp is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ #pragma once #include "../vm.hpp" +#include "../handle.hpp" #include "module_and_name.hpp" #include #include @@ -87,6 +88,8 @@ namespace wren { void set (VM& vm, int slot_num, const char* beg, const char* end); void set (VM& vm, int slot_num, int value); void set (VM& vm, int slot_num, std::size_t value); + void set (VM& vm, int slot_num, const Handle& handle); + void set (VM& vm, int slot_num, const ModuleAndName& name); std::string_view slot_string_view (VM& vm, int slot_num); template detail::GetTypeToRetType_t get (VM& vm, int slot_num); template T* foreign (VM& vm, int slot_num); @@ -138,6 +141,14 @@ namespace wren { vm.set_slot_double(slot_num, static_cast(value)); } + inline void set (VM& vm, int slot_num, const Handle& handle) { + vm.set_slot_handle(handle, slot_num); + } + + inline void set (VM& vm, int slot_num, const ModuleAndName& name) { + vm.variable_or_throw(name.module_name_char(), name.class_name_char(), slot_num); + } + template inline T* foreign (VM& vm, int slot_num) { T* obj = static_cast(vm.slot_foreign(slot_num)); diff --git a/include/wrenpp/handle.hpp b/include/wrenpp/handle.hpp index 35db0c6..91deae5 100644 --- a/include/wrenpp/handle.hpp +++ b/include/wrenpp/handle.hpp @@ -1,4 +1,4 @@ -/* Copyright 2020-2022, Michele Santullo +/* Copyright 2020-2024, Michele Santullo * This file is part of wrenpp. * * Wrenpp is free software: you can redistribute it and/or modify @@ -42,6 +42,8 @@ namespace wren { operator WrenHandle*() const { return m_handle; } operator bool() const { return nullptr != m_handle; } + void set_to_slot (int num); + private: WrenHandle* m_handle; VM* m_vm; diff --git a/include/wrenpp/vm_fun.hpp b/include/wrenpp/vm_fun.hpp index b5030f9..c28bf38 100644 --- a/include/wrenpp/vm_fun.hpp +++ b/include/wrenpp/vm_fun.hpp @@ -1,4 +1,4 @@ -/* Copyright 2020-2022, Michele Santullo +/* Copyright 2020-2024, Michele Santullo * This file is part of wrenpp. * * Wrenpp is free software: you can redistribute it and/or modify @@ -26,6 +26,7 @@ #include "detail/construct_foreign_class.hpp" #include "detail/setters_getters.hpp" #include "detail/wren_class_name_from_type.hpp" +#include "detail/foreign_object.hpp" #include #include #include @@ -46,11 +47,11 @@ namespace wren { R call (VM& vm, ModuleAndName object, const char (&method)[N], const Args&... args); template - T* make_wren_object(VM& vm, ModuleAndName mn, Args&&... args); + ForeignObject make_wren_object(VM& vm, ModuleAndName mn, Args&&... args); #if defined(WRENPP_WITH_NAME_GUESSING) template - T* make_wren_object(VM& vm, Args&&... args); + ForeignObject make_wren_object(VM& vm, Args&&... args); #endif void interpret (VM& vm, const std::string& module_name, const std::string& script); @@ -61,28 +62,13 @@ namespace wren { void abort_fiber_with_error (VM& vm, int slot, std::string_view message); namespace detail { - template - inline void set_single_for_call (VM& vm, int slot, const T& value) { - set(vm, slot, value); - } - - template <> - inline void set_single_for_call (VM& vm, int slot_num, const Handle& handle) { - vm.set_slot_handle(handle, slot_num); - } - - template <> - inline void set_single_for_call (VM& vm, int slot_num, const ModuleAndName& name) { - variable(vm, name, slot_num); - } - //Sets arguments so wren is ready to perform a function call. It doesn't //touch at slot 0, so eventual objects for method calls must be set //manually template inline void set_for_call (std::integer_sequence, VM& vm, const Args&... args) { vm.ensure_slots(sizeof...(Args)); - (set_single_for_call(vm, Indices + 1, args), ...); + (set(vm, Indices + 1, args), ...); } template @@ -145,7 +131,7 @@ namespace wren { } else { ModuleAndName wren_name = wren_class_name_from_type(vm); - R* const new_object = make_wren_object(vm, wren_name, std::move(ret)); + ForeignObject new_object = make_wren_object(vm, wren_name, std::move(ret)); static_cast(new_object); } } @@ -197,15 +183,13 @@ namespace wren { inline R call (VM& vm, const Handle& object, const char (&method)[N], const Args&... args) { using dhandy::bt::string; - const constexpr char dummy_name_buff[N] = {0}; - const constexpr auto params = string(dummy_name_buff) + - string("(") + + const constexpr auto params = string("(") + ((static_cast(args), string(",_")) + ... + string("")).template substr<1>() + string(")"); ; - char cat_buff[params.size() + 1]; + char cat_buff[params.size() + 1 + N - 1]; std::copy(method, method + N - 1, cat_buff); - std::copy(params.data() + N - 1, params.data() + params.size() + 1, cat_buff + N - 1); + std::copy(params.data(), params.data() + params.size() + 1, cat_buff + N - 1); return call(vm, object, vm.make_call_handle(cat_buff), args...); } @@ -218,12 +202,12 @@ namespace wren { } template - inline T* make_wren_object(VM& vm, ModuleAndName mn, Args&&... args) { - variable(vm, mn, 0); + inline ForeignObject make_wren_object(VM& vm, ModuleAndName mn, Args&&... args) { + variable_ensure_slot(vm, mn, 0); void* const mem = vm.set_slot_new_foreign(0, 0, sizeof(T)); try { T* const obj = new(mem) T{std::forward(args)...}; - return obj; + return {obj, vm.slot_handle(0)}; } catch (const std::runtime_error& err) { abort_fiber_with_error(vm, 1, err.what()); @@ -233,7 +217,7 @@ namespace wren { #if defined(WRENPP_WITH_NAME_GUESSING) template - inline T* make_wren_object(VM& vm, Args&&... args) { + inline ForeignObject make_wren_object(VM& vm, Args&&... args) { return make_wren_object( vm, MN>, diff --git a/src/handle.cpp b/src/handle.cpp index 401e58d..210a6bc 100644 --- a/src/handle.cpp +++ b/src/handle.cpp @@ -1,4 +1,4 @@ -/* Copyright 2020-2022, Michele Santullo +/* Copyright 2020-2024, Michele Santullo * This file is part of wrenpp. * * Wrenpp is free software: you can redistribute it and/or modify @@ -18,6 +18,7 @@ #include "wrenpp/handle.hpp" #include "wrenpp/vm.hpp" #include +#include namespace wren { Handle::~Handle() noexcept { @@ -25,7 +26,12 @@ namespace wren { } void Handle::release() noexcept { - assert(m_vm); - m_vm->release_handle(*this); + assert(m_vm or not m_handle); + if (m_vm) + m_vm->release_handle(*this); + } + + void Handle::set_to_slot (int slot) { + m_vm->set_slot_handle(*this, slot); } } //namespace wren