I'm using Luabind to bind my Lua scripts to my C++ engine. (it uses lua 5.1.4)
I added a new lua script called "controller.lua" which my entities script, called "cat.lua", will reference and use. One the c++ calls the method "Update" it's all in the hands of Lua.
But once I try to pass my binded c++ methods to the new script file, it feels like all the bindings from that c++ object disapear. I get the following error:
Expression: scripts/controller.lua:5(method MoveUp)
scripts/controller.lua:5: attempt to call method 'GetComponent' (a nil value)
Here is some C++ snippets
// Definitions
module(luaState)
[
class_<Entity>("Entity")
.def("GetComponent", &Entity::GetComponent)
class_<Component>("Component")
.enum_("eComponentTypes")
[
value("Steering", kComponentType_Steering)
],
class_<SteeringComponent>("SteeringComponent")
];
// The script components update
void ScriptComponent::Update() {
const Entity* owner = this.GetOwner();
mLuaDataTable["Update"](owner); // Executes the Update function on the script Cat.lua
}
The entities code being called by c++ (When it executes it returns the Cat table to c++.)
-- Cat.lua
local controller = loadfile("scripts/controller.lua")
local Cat = {}
function Cat.Update(entity)
steeringComponent = entity:GetComponent(Component.Steering) -- Works fine
controller:MoveUp(entity)
end
return Cat
and the Controller
--controller.lua
local up = vec2(0.0, 1.0)
local Controller = {}
function Controller.MoveUp(entity)
steeringComponent = entity:GetComponent(Component.Steering) -- Fails
end
return Controller
Bonus points:
When I make a change to the controller that doesn't work (like if I just threw an s character anywhere), the controller loads up nil, no warnings. Is there some way to make it throw warnings?
Is there a better way I should be doing to "link" to other lua files, like the way im working with Controller?
Thanks to ToxicFrog on Freenode chat for helping me figure this one out.
Basically: I was calling controller MoveUp like so:
controller:MoveUp(entity)
which of course translates into
controller.MoveUp(controller, entity)
and the function was defined as
function Controller.MoveUp(entity)
this "entity" was accepted as the first parameter, controller, while the actual entity is discarded per spec.
http://lua-users.org/wiki/ObjectOrientationTutorial
Related
In a C++ NodeJS project I'm currently working on, I want to have an object which contains a nested object, like
console.log(object.myObject); // { [keys: string]: any }
Without binding objects to properties my addon is working very well. I create the Instance with
auto ctor = Nan::New<v8::FunctionTemplate>(New);
auto ctorInst = ctor->InstanceTemplate();
And bind the object to the class instance like
v8::Local<v8::Object> obj = Nan::New<v8::Object>();
ctorInst->Set(Nan::New("myObject").ToLocalChecked(), obj);
which prints the following when executing the JS code (create an instance of the C++ extension).
# Fatal error in , line 0
# Check failed: !value_obj->IsJSReceiver() || value_obj->IsTemplateInfo().
On the other hand binding primitives (number, string, etc.) works flawlessly.
ctorInst->Set(Nan::New("myObject").ToLocalChecked(), Nan::New<v8::Number>(42));
Do you have got any suggestions? Thanks and cheers!
All templates (Including InstanceTemplate's) can only have other templates, or primitives, in them. As you see, IsTemplateInfo was false, when it should have been true. A similar question was asked and answered here. Simply make an ObjectTemplate and then populate it like you were doing with the FunctionTemplate.
Only when the Template is instantiated (with GetFunction), can you populate it with real objects.
In addition to Nicholas' answer I'd like to add the following.
Instance
Simply replace the v8::Object with v8::ObjectTemplate. Set properties to the object template and bind the template to the addon instance.
v8::Local<v8::ObjectTemplate> objTemplate = Nan::New<v8::ObjectTemplate>();
objTemplate->Set(Nan::New("prop").ToLocalChecked(), Nan::New<v8::Number>(42));
ctorInst->Set(Nan::New("myObject").ToLocalChecked(), objTemplate);
Now you are able to use
console.log(addonInstance.myObject); // { prop: 42 }
Prototype
If you want to bind functions to the object, create an object template like above. Then create a function template from an existing cpp function, and bind it to an object property of the object template. Then create a template on the addon instances prototype.
v8::Local<v8::ObjectTemplate> objTemplate = Nan::New<v8::ObjectTemplate>();
objTemplate->Set(Nan::New("prop").ToLocalChecked(), Nan::New<v8::Number>(42));
auto fn = Nan::New<v8::FunctionTemplate>(setup);
objTemplate->Set(Nan::New("setup").ToLocalChecked(), fn);
Nan::SetPrototypeTemplate(ctor, "myObj", objTemplate);
and the setup function is simply
NAN_METHOD(YourClass::setup) {
info.GetReturnValue().Set(Nan::New<v8::String>("Hello world").ToLocalChecked());
}
The console output would be
console.log(addonInstance.myObject) // { prop: 42, setup: [Function: setup] }
Is it possible to get an existing object reference using CEF API?
For example I run a script using ExecuteJavaScript()
function foo()
{
var self = this;
self.value="some value";
}
var fooObj = new foo;
This script creates a new variable fooObj. It is possible to get a reference to this variable later in the C++ code and to modify its value?
You should be able to get it by doing something like the following (untested):
auto context = AppGetBrowser()->GetMainFrame()->GetV8Context();
CefRefPtr<CefV8Value> p = context->GetGlobal()->GetValue(CefString("fooObj"));
You may need to Enter/Exit the context depending on where you're calling it from in C++. Furthermore you may need to actually reference your object explicitly as 'window.fooObj' in which case you'll have to get the value for 'window' and then get 'fooObj' off that.
(edit - accidentally posted too early)
(edit 2 - more)
So I'm working on a little project in which I'm using Python as an embedded scripting engine. So far I've not had much trouble with it using boost.python, but there's something I'd like to do with it if it's possible.
Basically, Python can be used to extend my C++ classes by adding functions and even data values to the class. I'd like to be able to have these persist in the C++ side, so one python function can add data members to a class, and then later the same instance passed to a different function will still have them. The goal here being to write a generic core engine in C++, and let users extend it in Python in any way they need without ever having to touch the C++.
So what I thought would work was that I would store a boost::python::object in the C++ class as a value self, and when calling the python from the C++, I'd send that python object through boost::python::ptr(), so that modifications on the python side would persist back to the C++ class. Unfortunately when I try this, I get the following error:
TypeError: No to_python (by-value) converter found for C++ type: boost::python::api::object
Is there any way of passing an object directly to a python function like that, or any other way I can go about this to achieve my desired result?
Thanks in advance for any help. :)
Got this fantastic solution from the c++sig mailing list.
Implement a std::map<std::string, boost::python::object> in the C++ class, then overload __getattr__() and __setattr__() to read from and write to that std::map. Then just send it to the python with boost::python::ptr() as usual, no need to keep an object around on the C++ side or send one to the python. It works perfectly.
Edit: I also found I had to override the __setattr__() function in a special way as it was breaking things I added with add_property(). Those things worked fine when getting them, since python checks a class's attributes before calling __getattr__(), but there's no such check with __setattr__(). It just calls it directly. So I had to make some changes to turn this into a full solution. Here's the full implementation of the solution:
First create a global variable:
boost::python::object PyMyModule_global;
Create a class as follows (with whatever other information you want to add to it):
class MyClass
{
public:
//Python checks the class attributes before it calls __getattr__ so we don't have to do anything special here.
boost::python::object Py_GetAttr(std::string str)
{
if(dict.find(str) == dict.end())
{
PyErr_SetString(PyExc_AttributeError, JFormat::format("MyClass instance has no attribute '{0}'", str).c_str());
throw boost::python::error_already_set();
}
return dict[str];
}
//However, with __setattr__, python doesn't do anything with the class attributes first, it just calls __setattr__.
//Which means anything that's been defined as a class attribute won't be modified here - including things set with
//add_property(), def_readwrite(), etc.
void Py_SetAttr(std::string str, boost::python::object val)
{
try
{
//First we check to see if the class has an attribute by this name.
boost::python::object obj = PyMyModule_global["MyClass"].attr(str.c_str());
//If so, we call the old cached __setattr__ function.
PyMyModule_global["MyClass"].attr("__setattr_old__")(ptr(this), str, val);
}
catch(boost::python::error_already_set &e)
{
//If it threw an exception, that means that there is no such attribute.
//Put it on the persistent dict.
PyErr_Clear();
dict[str] = val;
}
}
private:
std::map<std::string, boost::python::object> dict;
};
Then define the python module as follows, adding whatever other defs and properties you want:
BOOST_PYTHON_MODULE(MyModule)
{
boost::python::class_<MyClass>("MyClass", boost::python::no_init)
.def("__getattr__", &MyClass::Py_GetAttr)
.def("__setattr_new__", &MyClass::Py_SetAttr);
}
Then initialize python:
void PyInit()
{
//Initialize module
PyImport_AppendInittab( "MyModule", &initMyModule );
//Initialize Python
Py_Initialize();
//Grab __main__ and its globals
boost::python::object main = boost::python::import("__main__");
boost::python::object global = main.attr("__dict__");
//Import the module and grab its globals
boost::python::object PyMyModule = boost::python::import("MyModule");
global["MyModule"] = PyMyModule;
PyMyModule_global = PyMyModule.attr("__dict__");
//Overload MyClass's setattr, so that it will work with already defined attributes while persisting new ones
PyMyModule_global["MyClass"].attr("__setattr_old__") = PyMyModule_global["MyClass"].attr("__setattr__");
PyMyModule_global["MyClass"].attr("__setattr__") = PyMyModule_global["MyClass"].attr("__setattr_new__");
}
Once you've done all of this, you'll be able to persist changes to the instance made in python over to the C++. Anything that's defined in C++ as an attribute will be handled properly, and anything that's not will be appended to dict instead of the class's __dict__.
Now this would seem to be something very straight forward, but seemingly not so in ColdFusion. I need to create an instance of a CFC from within itself as in var a = new this() but this obviously does not work. The CFC name can't be used as it is a base that will be extended so I am attempting a hack around the issue with the following:
component {
public function subQuery (required string table) {
var classPath = getMetaData(this).fullname;
return createObject("component", classPath).init(table, this.dsn);
}
}
This would be acceptable but the class path returned from getMetaData(this).fullname is incorrect. The CFC is within a folder named with a hypen as in my-folder and the returned path looks like my.-folder.myCFC with a period inserted before the hyphen. Obviously I could manipulate this string with a Regex but that is just not a road I want to go down.
Hoping someone has a cleaner approach, thanks.
You should be able to do it without any context on the object name in theory, as it will be being executed from within itself and it should check its current directory.
The following should therefore do the job you need
var classPath = ListLast(getMetaData(this).fullname,'.');
return createObject("component", classPath).init(table, this.dsn);
This way it doesn't matter what the directory names are, and it will work on any objects that extend that one regardless of directory structure, or for a complete example
public function cloneMe() {
return CreateObject('component', ListLast(getMetaData(this).fullname,'.')).init(argumentCollection=arguments);
}
This way any arguments passed in will be passed through into the init. I.e. an extending CFC may redefine the method as the following (if you want errors when the init arguments aren't supplied)
public function cloneMe(required string table) {
return super.cloneMe(table=arguments.table,dsn=this.dsn);
}
I have embedded Ruby inside my C++ application. I have generated the bindings using SWIG.
Basically, I run the ruby file and then Ruby takes over and calls my C++ class.
Based on my previous question, I would like to get the current instance of the class that is defined in the ruby file back to the C++ class so that I may execute instance methods.
I execute the ruby file as follows:
rb_eval_string_protect(<ruby script string>, &status );
rb_funcall(Qnil, rb_intern("main"), 0);
The global main method in the script creates an instance of the defined class in the file. That's the instance I am after.
If I have to, I will add a parameter or another function to pass the instance back, however, I'm not sure how to define that in C++ so that when SWIG generates the binding, it all works ok.
Any help would be appreciated.
Previous Question: Calling Ruby class methods from C++
The C api for ruby does its best to preserve ruby's functional nature, so rb_eval_string_protect() returns the VALUE of the last line of the script given, and rb_funcall() returns the VALUE of the last line of the method invoked.
So the trick is really to think of it as how would you get that instance value in pure ruby? If it's just the return value of main, like
# I'm a ruby script!
main_retval = main()
Then capturing the return value in C is similar:
// I'm some C (or C++) code
VALUE main_retval;
// ...
rb_eval_string_protect("...", &status);
main_retval = rb_funcall(Qnil, rb_intern("main"), 0);
And would give you a reference to the ruby object returned by main.
You can use this object as normal, calling methods and the like
VALUE main_retval_as_string = rb_funcall(main_retval, rb_intern("to_s"), 0);