Can't get chaiscript to pass me a shared_ptr of an object


#1

I have this function.
void attach(std::shared_ptr<basic> in);

Don’t worry about its context or body - the problem is calling it at all.

The class block derives basic. Here, I want to create one and attach() it so that I have chaiscript’s shared_ptr to the object, causing it to “live on” after the end of the function.

def doBlockTest() { var b = block(game, 0.0f, 0.0f, blockType_CONTROL) attach(b); }

If I did this in C++, I would have constructed the shared_ptr myself and passed that to the function. However, I can’t do that in Chaiscript because the language doesn’t (as far as I know) explicitly allow pointers. My expectation, therefore, is that Chaiscript will pass the object’s pointer, like it would if I had defined attach as attach(basic* in).

Chaiscript doesn’t appear to be able to do this, so instead I get a function dispatch error telling me that the function will not take the type “block”, only “basic”. If the function took basic* rather than std::shared_ptr<basic>, this wouldn’t be a problem as chaiscript would downcast automatically. Can it not do that here for some reason?

If I use a factory function (i.e. b.clone()) from chaiscript to make sure the variable b is holding a pointer (and the original block created by chaiscript is therefore deleted instantly) I get an even more bewildering error - now the function will not take the type “basic”, only “basic”. I will clarify that I am talking about my own clone() function here that returns a pointer to a copy of the object - I’m not sure if chaiscript defines one too.

When I tried having chaiscript pass me Boxed_Values, it started simply copying the object and passing me the copy. This is the opposite of what I want.

How do I get Chaiscript to pass me its shared_ptr instance for an object so that I can extend the object’s lifetime by keeping a reference to it? As detailed here: https://github.com/ChaiScript/ChaiScript/issues/214#issuecomment-146198716

Thanks in advance!


#2

Actually having the full context of the issue is quite important, in almost 100% of cases.

Since I don’t have the full context, I went ahead and created this complete example that does what you are asking. I don’t know where your code is going astray, but this should get you going.

Also regarding your “bewildering error” I believe in that case you are passing a const object to one that expects a non-const value.

#include <chaiscript/chaiscript.hpp>
#include <chaiscript/chaiscript_stdlib.hpp>

struct Base
{
  virtual ~Base() = default;
};

struct Derived : Base
{
};

void attach(std::shared_ptr<Base>)
{
  std::cout << "attach called!\n";
}

int main()
{
  chaiscript::ChaiScript chai(chaiscript::Std_Lib::library());
  chai.add(chaiscript::fun(&attach), "attach");
  chai.add(chaiscript::constructor<Derived()>(), "Derived");
  chai.add(chaiscript::base_class<Base, Derived>());

  chai.eval(R"(
    var d = Derived();
    attach(d);
  )");


}

#3

Oops. In that case, here’s the full context:

I have a class called basic that serves as a definite base for game objects and a class called container that can contain these objects. Currently, however, the only thing that derives container is the core game class.

objBasic and uiBasic both derive basic, and then block derives uiBasic. Everything is registered with the scripting engine.

Attach looks like this on the Game object (it has no base declaration, container is just a template):

void Game::attach(std::shared_ptr<basic> in)
{
	if (in->getOwner()) {
		p::warn("Cannot attach to game - object is already attached to something.");
		return;
	}

	auto object = std::dynamic_pointer_cast<objBasic>(in);
	auto ui = std::dynamic_pointer_cast<uiBasic>(in);
	auto map = std::dynamic_pointer_cast<tilemap>(in);

	if (map) {
		listTileLayer(map);
	}
	else if (object) {
		listObject(object);
	}
	else if (ui) {
		listUi(ui);
	}
	else {
		p::warn("Can't attach " + std::string(typeid(*in.get()).name()) + " to game. Make sure what you're trying to attach is derived from objBasic or uiBasic!");
	}
}

listObject/listUi/etc will add the shared_ptr to a vector list holding all objects derived from that base. As I said before, the function can’t be called for whatever reason.

As game is the root object, I decided a while ago to make it invisible to chaiscript - it is instead as if you are acting AS the game object when you use the chai engine. Attach is registered as a lambda:

// game functions (lambdas)
chai->add(fun([&](std::shared_ptr<basic> in) { game->attach(in); }), "attachToGame");
chai->add(fun([&](basic* in) { game->detach(in); }), "detachFromGame");
chai->add(fun([&]() { game->allCleanup(); }), "allCleanup");
// ...etc

The chai script looks like this:

// load stuff
use("./scripts/resources.chai");
use("./scripts/prefabs.chai");

onGameInitDone.registerObserver(fun() {
	backToMenu()
})

def doStressTest() {
	var p = findPlayer().getPosition();
	
	for(var i = 0; i < 1000; i += 1) {
		var b = createFromPrefab("Butterfly");
		
		b.setPosition(p.x + randomInt(-1000, 1000), p.y + randomInt(-1000, 1000));
		attachToGame(b);
	}
}

def doBlockTest() {
	allCleanup()

	var b = block(game, 0.0f, 0.0f, blockType_CONTROL)
	attachToGame(b);
}

And finally, I trigger doBlockTest() by typing it in the console. Funnily enough, doStressTest() works perfectly, though that’s probably because createFromPrefab will always return a std::shared_ptr<objBasic>.

Since there are constantly multiple layers of inheritance and chaiscript doesn’t normally support that, I have used a macro to bind the final layers of inheritance and allow a class like block that is two levels down to call basic’s functions without chaiscript complaining.

#define BIND_CONVERSIONS(obj) chai->add(type_conversion<obj*, basic*>());\\
chai->add(type_conversion<obj*, sf::Transformable*>());

I’m not sure if this is exactly the right way to do this. Basic derives sf::Transformable, that’s why it’s up there.

Block binding is therefore:

chai->add(user_type<block>(), "block");
chai->add(base_class<uiBasic, block>());
BIND_CONVERSIONS(block);
chai->add(constructor<block(Game*, float, float, block::Type)>(), "block");
// functions below here removed

#4

Full error after calling doBlockTest via the console:

Eval exception was thrown;
        Error: "Error calling function 'attachToGame'" With parameters: (block) during evaluation at (D:\Repos\Alchemist\build\windows\bin\RelWithDebInfo\resources\main.chai 24, 2)
          Expected: (basic)
          D:\Repos\Alchemist\build\windows\bin\RelWithDebInfo\resources\main.chai (24, 2) 'attachToGame(b)'
          from __EVAL__ (1, 1) 'doBlockTest()'

I know that’s a lot, if you need me to I can make a runnable, dressed down example without all the extra functionality of things ripped from a work in progress game engine.


#5

My conclusion has to be that somehow the inheritance relationship / conversion relationship is not being registered properly by your code.

The type_conversion that you are using invokes only standard static_cast type conversions that can be applied by the compiler. Also, there’s no way in C++ to convert from shared_ptr to shared_ptr via * to *, as your type_conversion invocation would like.

To get the level of inheritance support you would like, you’ll need to use the base_class<> for converting between the shared_ptr instances.

base_class<> works equally well for raw pointers, shared_ptr, and references as they are passed to function calls.


#6
#define BIND_CONVERSIONS(obj) chai->add(base_class<basic, obj>());\
chai->add(base_class<container, obj>());\
chai->add(base_class<sf::Transformable, obj>());

Like this?

Edit: YES! You were right.

Thanks for your help. I wasn’t aware base_class could go beyond the original parent.