matusnovak / wrenbind17

A header only library for binding C++17 classes and functions to Wren, an embeddable programming language
https://matusnovak.github.io/wrenbind17
MIT License
65 stars 10 forks source link

How do I put functions from a library into wren class without defining wrapper class on C++ side? #18

Closed mastercuber55 closed 1 year ago

mastercuber55 commented 1 year ago

I'm using a graphics library called raylib It defines a bunch of functions.

on wren side I want to put all of those raylib functions inside a class named "rl" without defining any wrapper classes to call those functions on c++ side

but I was unable to find anything about this in Tutorials section.

mastercuber55 commented 1 year ago

btw Being able to call those functions directly without class will also work for me.

matusnovak commented 1 year ago

Hi @mastercuber55

Unfortunately having purely global functions is not supported. However, you could do the following:

Create a dummy class on the C++ side. It does not have to define anything. You can leave it blank.

class RayLib {
};

Then register any ray lib functions. Something like this should work:

// Assuming this global function is from raylib
extern void DrawRectangle(int x, int y, int w, int h, int color);

wren::VM vm;
auto& m = vm.module("mymodule");
auto& cls = m.klass<RayLib>("RayLib"); // Dummy
cls.funcStaticExt<&DrawRectangle>("DrawRectangle");  // Here, use "funcStaticExt"

But that would force you to create an instance of RayLib class in the Wren user code. There is one trick for that! Create an instance of the RayLib in Wren and make it a global variable (has to start with an upper case letter):

m.append("var RL = RayLib.new()\n");

The m.append appends any kind of Wren code into the module you have created with vm.module("mymodule").

Then, in your Wren user code, you can do this:

import "mymodule" for RL

RL.DrawRectangle(10, 20, 100, 200, 255)

A similar concept is here: https://matusnovak.github.io/wrenbind17/tutorial/custom_types/#656-class-static-variables (section "6.5.6 Class static variables").

Would this little hack work for you?

mastercuber55 commented 1 year ago

Hi matsunovak, thank you for writing a example for me and yes it will work for me so thank you once again.

mastercuber55 commented 1 year ago

I'm unforunately unable to get it working, I'm getting this wrenbind17::CompileError on running my wren code

terminate called after throwing an instance of 'wrenbind17::CompileError'
  what():  Runtime error: raylib does not implement 'InitWindow(_,_,_)'.
  at: wrenbind17.wren:3

Here's the wren code

 import "raylib" for rl

rl.InitWindow(640, 480, "Hello Wren")

and here is the code on c++ side

auto& Raylibm = VM.module("raylib");

    auto& Raylibc = Raylibm.klass<raylib>("raylib");
    Raylibc.ctor<>();
    Raylibc.funcStaticExt<&InitWindow>("InitWindow");
    Raylibm.append("var rl = raylib.new()\n");

and this is the raylib function RLAPI void InitWindow(int width, int height, const char *title);

matusnovak commented 1 year ago

Oh! My bad! I told you to create an instance but to use a static function :D I probably should not write code at midnight.

So, problem number one, the raylib does not implement is because Wren is looking for a function with 4 parameters where the first one is "this" an instance of the class and the 3 other parameters x, y, and the title. I gave you a wrong code.

Problem number two is that you can't use const char* in the function arguments. Wren is the owner of the strings, so taking a pointer out of them and using it in a C++ class is dangerous and can cause segmentation fault. Therefore I have not implemented that conversion into this library on purpose. You have to create a wrapper function that accepts std::string and calls the real RayLib function with .c_str().

Therefore, you need something like this:

// This should be the real RayLib function, for the purpose of this example
// I am just defining it here like this to print out the arguments.
static void InitWindow(int x, int y, const char* title) {
    std::cout << "InitWindow called with x: " << x << " y: " << y << " title: " << title << std::endl;
}

// A wrapper class
class Raylib {
public:
    // A function that accepts std::string instead of const char
    static void wrenInitWindow(int x, int y, const std::string& title) {
        // Call the real RayLib function with the const char* from the std::string
        // It assumes that RayLib's function InitWindow() makes a copy of the const char*
        // Otherwise there will be a segmentation fault at some point later.
        InitWindow(x, y, title.c_str());
    }
};

int main() {
    wren::VM vm;

    auto& m = vm.module("raylib");
    auto& cls = m.klass<Raylib>("RL");
    // Bind the custom function, not the real InitWindow.
    cls.funcStaticExt<&Raylib::wrenInitWindow>("InitWindow");

    // Sample code to run
    static const std::string code = R"(
        import "raylib" for RL

        RL.InitWindow(640, 480, "Hello Wren")
    )";

    vm.runFromSource("main", code);

    return 0;
}

Should print:

InitWindow called with x: 640 y: 480 title: Hello Wren

An alternative version, not using static functions, but creating an instance of the wrapper class.

static void InitWindow(int x, int y, const char* title) {
    std::cout << "InitWindow called with x: " << x << " y: " << y << " title: " << title << std::endl;
}

class Raylib {
public:
    void wrenInitWindow(int x, int y, const std::string& title) { // No longer static
        InitWindow(x, y, title.c_str());
    }
};

int main() {
    wren::VM vm;

    auto& m = vm.module("raylib");
    auto& cls = m.klass<Raylib>("Raylib");
    cls.ctor<>(); // Added empty constructor
    cls.func<&Raylib::wrenInitWindow>("InitWindow"); // Changed from "funcStaticExt" to "func"

    m.append("var RL = Raylib.new()\n"); // Added

    // Remains the same
    const std::string code = R"(
        import "raylib" for RL

        RL.InitWindow(640, 480, "Hello Wren")
    )";

    vm.runFromSource("main", code);

    return 0;
}

Tested on my PC, should work out of the box.

mastercuber55 commented 1 year ago

Yea, that worked so thanks again a bunch and I hope I don't have to reopen this issue and bother you anymore.

btw I think you make a discord server for this amazing library.

matusnovak commented 1 year ago

Yea, that worked so thanks again a bunch and I hope I don't have to reopen this issue and bother you anymore.

No problem. Feel free to ask for more help.

btw I think you make a discord server for this amazing library.

Good idea but I do not want to manage a Discord server :D Too much work. GitHub issues seem easier to me.