Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support std::function as function parameter #142

Open
ivalylo opened this issue Oct 16, 2020 · 4 comments
Open

Support std::function as function parameter #142

ivalylo opened this issue Oct 16, 2020 · 4 comments

Comments

@ivalylo
Copy link

ivalylo commented Oct 16, 2020

Hello,

Seems this is not supported currently, but it's quite handy to have (for example callbacks). I tried implementing it, but I'm very new to both v8 and v8pp. Also not that good with template programming either. But here is something I got working, and maybe can be used to build somewhat better implementation.

template<typename T, typename ...Args>
struct V8CallFunc
{
   V8CallFunc(v8::Isolate* isolate, v8::Local<v8::Function> func)
      : isolate(isolate), func(isolate, func)
   {
   }

   T operator()(Args&&... args) const
   {
      v8::EscapableHandleScope scope(isolate);

      int const arg_count = sizeof...(Args);
      // +1 to allocate array for arg_count == 0
      v8::Local<v8::Value> v8_args[arg_count + 1] =
      {
         to_v8(isolate, std::forward<Args>(args))...
      };

      v8::TryCatch try_catch(isolate);
      v8::Local<v8::Value> result;
      v8::Local<v8::Function> funcLocal = func.Get(isolate);
      bool const not_empty = funcLocal->Call(isolate->GetCurrentContext(), funcLocal, arg_count, v8_args).ToLocal(&result);

      if (try_catch.HasCaught())
      {
         std::string const msg = v8pp::from_v8<std::string>(isolate,
            try_catch.Exception()->ToString(isolate->GetCurrentContext()).ToLocalChecked());
		 THROW(std::runtime_error, msg);
      }

      assert(not_empty);
      return from_v8<T>(isolate, scope.Escape(result));
   }

   v8::Isolate* isolate;
   v8::Eternal<v8::Function> func;
};

template<typename ...Args>
struct V8CallFunc<void, Args...>
{
   V8CallFunc(v8::Isolate* isolate, v8::Local<v8::Function> func)
      : isolate(isolate), func(isolate, func)
   {
   }

   void operator()(Args&&... args) const
   {
      v8::EscapableHandleScope scope(isolate);

      int const arg_count = sizeof...(Args);
      // +1 to allocate array for arg_count == 0
      v8::Local<v8::Value> v8_args[arg_count + 1] =
      {
         to_v8(isolate, std::forward<Args>(args))...
      };

      v8::TryCatch try_catch(isolate);
      v8::Local<v8::Function> funcLocal = func.Get(isolate);
      funcLocal->Call(isolate->GetCurrentContext(), funcLocal, arg_count, v8_args);

      if (try_catch.HasCaught())
      {
         std::string const msg = v8pp::from_v8<std::string>(isolate,
            try_catch.Exception()->ToString(isolate->GetCurrentContext()).ToLocalChecked());
         THROW(std::runtime_error, msg);
      }
   }

   v8::Isolate* isolate;
   v8::Eternal<v8::Function> func;
};

template <typename Ret, typename... Args> __forceinline
std::function<Ret(Args...)> createV8CallFunc(std::function<Ret(Args...)> f, v8::Isolate* isolate, v8::Local<v8::Function> func)
{
    return V8CallFunc<Ret, Args...>(isolate, func);
}

template<typename T>
struct convert<std::function<T>>
{
	using from_type = std::function<T>;
	using to_type = v8::Local<v8::Function>;

	static bool is_valid(v8::Isolate* isolate, v8::Local<v8::Value> value)
	{
		return !value.IsEmpty() && value->IsFunction();
	}

	static from_type from_v8(v8::Isolate* isolate, v8::Local<v8::Value> value)
	{
		if (!is_valid(isolate, value))
		{
			THROW(invalid_argument, isolate, value, "Function");
		}
		auto func = value.As<v8::Function>();
		return createV8CallFunc(from_type(), isolate, func);
	}

	static to_type to_v8(v8::Isolate* isolate, const from_type& func)
	{
		using T_type = typename std::decay<from_type>::type;
		T_type func_copy = func;

		v8::Local<v8::Function> fn = v8::Function::New(isolate->GetCurrentContext(),
			&detail::forward_function<raw_ptr_traits, T_type>,
			detail::set_external_data(isolate, std::forward<T_type>(func_copy))).ToLocalChecked();
		return fn;
	}
};
@pmed
Copy link
Owner

pmed commented Oct 17, 2020

Hi @ivalylo

there is a wrap_function() helper in v8pp/function.hpp that allows to wrap any callable C++ object (pointer to free function, lambda, maybe also std::function) to a V8 Function value. So you could try to use the wrapped function as a callback argument.

Btw, there is also a call_v8() helper in v8pp/call_v8.hpp that seems to be very similar to your V8CallFunc::operator().

@YarikTH
Copy link
Contributor

YarikTH commented Aug 20, 2021

It seems that proper support for std::function overload for v8pp::convert require including full content of call_from_v8.hpp and call_v8.hpp. It can be easily done from the technical point of view, but I have no idea about the architectural point of view.

I made my ugly version using forward declaration, but it doesn't work if used doesn't include call_v8.hpp or call_from_v8.hpp or both while using std::function bindings. And there are not so clear messages "undefined reference to v8::Local<v8::Value> v8pp::call_v8<double>()"

What do you think, @pmed?

@pmed
Copy link
Owner

pmed commented Jan 21, 2023

Hi @YarikTH

do you mean PR #169? Thanks, it looks viable, maybe with a test case added.

I hope call_v8() can be used somehow inside convert<std::function<>> specialization. Anyways call_v8() i just a helper for v8::Function::Call(), that we can use in the specialization.

@YarikTH
Copy link
Contributor

YarikTH commented Jan 21, 2023

Yes. But I gave up using V8 a long time ago so I can't collaborate more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants