Wrecked Games

Please login or register.

Login with username, password and session length
Advanced search  

News:

We're just that awesome.

Pages: 1 2 [3] 4

Author Topic: Squirrel troubles  (Read 19438 times)

OvermindDL1

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 288
    • View Profile
    • http://www.overminddl1.com/forum/
Squirrel troubles
« Reply #30 on: August 03, 2006, 06:40:49 AM »

Instead of calling directly as you are trying to do, you need an intermediary, which is what boost::python::object is, or you could make your own.  For example, in my scripting class you cannot ever actually call anything, you ask for something back that you 'can' call.  In psuedo-code, this is how I do it:
Code: [Select]
// just the pseudo code signiture
py::object PythonScriptEngine::getObject(std::string objectName, std::string moduleName);

// in some function somewhere...
py::object classPtr = PythonScriptEngine::getSingleton.getObject("someClass", "currentLevelModule");
if(!classPtr) throw SomeException("Main Level class not found");

py::object levelInstance = classPtr(); // if this was a function object I got, I could pass in any amount of any type of parameters that I wanted, although by default the count is restricted to 20 to ease compile time, it can be increased by setting a define before including it.
if(!levelInstance) throw SomeException("Main Level class failed to be instanced");

LevelPtr theLevel = py::extract<LevelPtr>(levelInstance);
theLevel->isSinglePlayer = true;
// other setup...

HostLevel(theLevel, 1); // host the instanced level with networking restricted to 1 player, aka, singleplayer

return true;


Not my real code by a longshot, but it is similar to what I did earlier before I stuck more things in Python, and it worked perfect.

Also, note about templates, the functions have to be fully defined where they are used, they cannot cross dll boundries, they can't even cross static library boundries, they can call other code that crosses boundries, but the templates themselves need to be defined completely in the header and included where they are used.
Logged

pjcast

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 2652
    • View Profile
    • http://www.wreckedgames.com
Squirrel troubles
« Reply #31 on: August 03, 2006, 02:37:06 PM »

heh, don't give up :)

The calling of scripts from the InputManager can just be the calling of a bound function. As I mentioned before, the specified script implementations might be able to bind directly, or they might need to have an intermediate step. Which, is still not all that different from the current state of affairs. I still would rather use something like fast delegates to have member or static function pointers, and do not really see an anvantage of boost for that. And, as for virtual methods, that is not really an issue here, as we can make that limitation - a specific script binding dll would likely not even be using virtual class/methods.
Logged

mysterycoder

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 447
    • View Profile
Squirrel troubles
« Reply #32 on: August 03, 2006, 03:04:18 PM »

I'm not sure I follow, are you saying that I should have functions in the PythonScriptingEngine Dll, that call the python functions, and then have the InputManager call the functions in the PythonScriptingEngine Dll?

or am I missing your point completely?  :wink:

Perhaps another method would be to use a messanging system. The InputManager would wait for input, and if a key was pressed, it would send a message to the messaging system. In python, you would setup a listener that listened for input messages, and dependening on what function registered itself to what key, it would run that function.

But, that would require a new messaging system, and other changes, so I should probably get this working first.  :)
Logged

OvermindDL1

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 288
    • View Profile
    • http://www.overminddl1.com/forum/
Squirrel troubles
« Reply #33 on: August 03, 2006, 04:33:03 PM »

You want to see my class?  It is rather simple, but I have not yet found anything I want/need/etc.. to add to it...

Code: [Select]

// This class is a singleton because there can only be one Python instance per
//  process anyways.  Put things in different python namespaces to get different
//  worlds.  Well, technically there can be one interpreter per thread, but
//  since there is one global python lock, that does not allow parallel
//  processing anyway.  Python itself can create and manage threads from script,
//  if need-be.
class PythonScriptManager
{
protected: //vars and such
friend void initODL1_Embedded_Builtin(void);
std::list<boost::function<void(void)> > m_MainModuleFunctionLoaders;
py::object m_MainModule;
py::object m_MainNamespace;
py::object m_PickleModule;

bool isSetup;

public: // Singleton stuff...
static PythonScriptManager &getSingleton(void);

private: // private constructor/destructor
PythonScriptManager(void);
~PythonScriptManager(void);

public:
static std::string getTraceback(void);

public:
bool registerIntoInternalModule(boost::function<void(void)> callback);
bool initPython(void);
void shutdownPython(void);
bool addModuleDirectory(std::string newDirectory);
bool addModuleFromString(const char *uncompiledCode, const char *filenameField, const char *moduleName);
bool addModuleFromCompiledCode(py::object compiled_code, const char *moduleName);
py::object compileString(const char *uncompiledCode, const char*filenameField);
py::object instanceClassObject(std::string moduleName, std::string className);

__forceinline py::object &getMainModule(void)
{
return m_MainModule;
}
__forceinline py::object &getMainNamespace(void)
{
return m_MainNamespace;
}
__forceinline py::object &getPickleModule(void)
{
return m_PickleModule;
}
};


And the important parts you would care about for now (note, I didn't clean out unnecessary comments and notes to myself and so on):
Code: [Select]
PythonScriptManager &PythonScriptManager::getSingleton(void)
{
static PythonScriptManager s_Instance;
return s_Instance;
}


PythonScriptManager::PythonScriptManager(void) :
isSetup(false)
{
}

PythonScriptManager::~PythonScriptManager(void)
{
shutdownPython();
}

std::string PythonScriptManager::getTraceback(void)
{
// Best to drop into basic python commands for this function since it is called only in the middle of an exception being thrown from inside python.

// Python equivalent:
// import traceback, sys
// return "".join(traceback.format_exception(sys.exc_type,
//    sys.exc_value, sys.exc_traceback))

PyObject *type, *value, *traceback;
PyObject *tracebackModule;
string toReturn;

PyErr_Fetch(&type, &value, &traceback);

tracebackModule = PyImport_ImportModule("traceback");
if (tracebackModule != NULL)
{
PyObject *tbList, *emptyString, *strRetval;

tbList = PyObject_CallMethod(
tracebackModule,
"format_exception",
"OOO",
type,
value == NULL ? Py_None : value,
traceback == NULL ? Py_None : traceback);

emptyString = PyString_FromString("");
strRetval = PyObject_CallMethod(emptyString, "join",
"O", tbList);

toReturn = PyString_AsString(strRetval);

Py_DECREF(tbList);
Py_DECREF(emptyString);
Py_DECREF(strRetval);
Py_DECREF(tracebackModule);
}
else
{
toReturn = "Unable to import traceback module.";
}

Py_DECREF(type);
Py_XDECREF(value);
Py_XDECREF(traceback);

return toReturn;
}

bool PythonScriptManager::registerIntoInternalModule(boost::function<void(void)> callback)
{
if(isSetup)
return false;

m_MainModuleFunctionLoaders.push_back(callback);
return true;
}

bool PythonScriptManager::initPython(void)
{
if(isSetup) return true;

std::string strEr;

Py_SetProgramName("MyProgram"); // TODO:  Probably pass a string into init to set this name
//PySys_SetArgv(int, char **); // Set sys.argv based on argc and argv. These parameters are similar to those passed to the program's main() function with the difference that the first entry should refer to the script file to be executed rather than the executable hosting the Python interpreter. If there isn't a script (as would be the case when embedding) that will be run, the first entry in argv can be an empty string. If this function fails to initialize sys.argv, a fatal condition is signaled using Py_FatalError()
// PySys_SetPath(char *); // TODO:  Look into this later for interesting use...

if(PyImport_AppendInittab("ODL1_Embedded_Builtin", initODL1_Embedded_Builtin) == -1)
{
// shouldn't happen, if anything seems fishy, maybe throw an exception from here or something.
return false;
}

Py_InitializeEx(0); // 0 means do not install signal handlers, like Ctrl+C to break the app (would be bad...)

try
{
m_MainModule = py::object((py::handle<>(py::borrowed(PyImport_AddModule("__main__")))));
m_MainNamespace = py::object((m_MainModule.attr("__dict__")));
}
catch(py::error_already_set)
{
ODL1PyTempAssert(0&&"Could not get global namespace");
strEr = "SCRIPT-ERROR: Could not get global namespace, canceling\n"+getTraceback()+"\n";
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
//PrintConsoleMessage((char*)(strEr.c_str()));
return false;
}

try
{
m_PickleModule = py::object(py::handle<>(PyImport_ImportModule("cPickle")));
}
catch(py::error_already_set)
{
//ODL1PyTempAssert(0&&"SCRIPT-ERROR: Could not load module 'cPickle' attempting to load 'pickle'");
strEr = "SCRIPT-ERROR: Could not load module 'cPickle' attempting to load 'pickle'\n"+PythonScriptManager::getTraceback()+"\n";
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
//PrintConsoleMessage((char*)(strEr.c_str()));

try
{
m_PickleModule = py::object(py::handle<>(PyImport_ImportModule("pickle")));
}
catch(py::error_already_set)
{
//ODL1PyTempAssert(0&&"SCRIPT-ERROR: Could not load module 'cPickle' attempting to load 'pickle'");
strEr = "SCRIPT-ERROR: Could not load module 'pickle' canceling\n"+PythonScriptManager::getTraceback()+"\n";
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
//PrintConsoleMessage((char*)(strEr.c_str()));
return false;
}
}

try
{
py::handle<> result(
PyRun_String(
"import ODL1_Embedded_Builtin\n"
"import sys\n"
"sys.stdout = ODL1_Embedded_Builtin.stdOutConsoleWrapper(sys.stdout, True, False)\n"
"sys.stderr = ODL1_Embedded_Builtin.stdOutConsoleWrapper(sys.stdout, False, True)\n"
,Py_file_input,
m_MainNamespace.ptr(),
m_MainNamespace.ptr())
);
// Result is not needed
result.reset();
}
catch(py::error_already_set)
{
ODL1PyTempAssert(0&&"Could not load starting modules and/or hook stdout/stderr");
strEr = "SCRIPT-ERROR: Could not load starting modules and/or hook stdOut, canceling\n"+getTraceback();
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
//PrintConsoleMessage((char*)(strEr.c_str()));

isSetup=true;
shutdownPython();
return false;
}

isSetup=true;
return true;
}

void PythonScriptManager::shutdownPython(void)
{
if(isSetup)
{
Py_Finalize();
isSetup = false;
}
}

bool PythonScriptManager::addModuleDirectory(std::string newDirectory)
{
if(!isSetup) return false;
std::string strEr;
try
{
py::handle<> result(
PyRun_String(
("import sys\n"
"sys.path.append(\""+newDirectory+"\")\n").c_str()
,Py_file_input,
m_MainNamespace.ptr(),
m_MainNamespace.ptr())
);
// Result is not needed
result.reset();
}
catch(py::error_already_set)
{
ODL1PyTempAssert(0&&"Could not load modules");
strEr = "SCRIPT-ERROR: Could not load modules, canceling\n"+getTraceback();
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
//PrintConsoleMessage((char*)(strEr.c_str()));
return false;
}
return true;
}

bool PythonScriptManager::addModuleFromString(const char *uncompiledCode, const char *filenameField, const char *moduleName)
{
py::object compiled_code = compileString(uncompiledCode, filenameField);
if(compiled_code)
return addModuleFromCompiledCode(compiled_code, moduleName);
else
return false;
}

py::object PythonScriptManager::compileString(const char *uncompiledCode, const char *filenameField)
{
std::string strEr;
try
{
//PyCompilerFlags compFlags;
////compFlags.cf_flags = Py_file_input;
return py::object(py::handle<>(Py_CompileString/*Flags*/(uncompiledCode, filenameField, Py_file_input/*, &compFlags*/)));
}
catch(py::error_already_set)
{
ODL1PyTempAssert(0&&"Could not compile code");
strEr = "SCRIPT-ERROR: Could not compile code, canceling\n"+getTraceback();
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
//PrintConsoleMessage((char*)(strEr.c_str()));
return py::object();
}
}

bool PythonScriptManager::addModuleFromCompiledCode(py::object compiled_code, const char *moduleName)
{
std::string strEr;
try
{
return py::object(py::handle<>(PyImport_ExecCodeModule((char*)moduleName, compiled_code.ptr())));
}
catch(py::error_already_set)
{
ODL1PyTempAssert(0&&"Could not inject module");
strEr = "SCRIPT-ERROR: Could not inject module, canceling\n"+getTraceback();
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
//PrintConsoleMessage((char*)(strEr.c_str()));
return false;
}
}

py::object PythonScriptManager::instanceClassObject(std::string moduleName, std::string className)
{
string strEr;
py::object loadedModule;
py::object loadedClass;
py::object loadedObject;

if(className.empty())
{
ODL1PyTempAssert(0&&"classname is empty, wierdo... :P"); // wierdo...
return py::object();
}

if(!moduleName.empty())
{
try
{
loadedModule = py::object(py::handle<>(PyImport_ImportModule((char*)(moduleName.c_str()))));
}
catch(py::error_already_set)
{
ODL1PyTempAssert(0&&"Could not import module");
strEr = "SCRIPT-ERROR: Could not import module '"+moduleName+"', so class '"+className+"' cannot be loaded\n"+getTraceback();
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
return py::object();
}
}
else
{
loadedModule = PythonScriptManager::getMainModule();
}

try
{
loadedClass = loadedModule.attr(className.c_str());
}
catch(py::error_already_set)
{
ODL1PyTempAssert(0&&"Could not find class");
strEr = "SCRIPT-ERROR: Could not find class '"+className+"' in module '"+moduleName+"'.\n"+getTraceback();
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
return py::object();
}

try
{
loadedObject = loadedClass();
}
catch(py::error_already_set)
{
ODL1PyTempAssert(0&&"Could not instance class");
strEr = "SCRIPT-ERROR: Could not instance class '"+className+"' from module '"+moduleName+"'.\n"+getTraceback();
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
return py::object();
}

return loadedObject;
}


Okay, so maybe I went a bit long, but that is the finality of about a years worth of embedding research that no one bothered to document well beyond me.  

I even did my final report in my technical writing class over writing this type of class embedding handler for embedding Python if you want a copy of it, that above is more advanced though.
Logged

pjcast

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 2652
    • View Profile
    • http://www.wreckedgames.com
Squirrel troubles
« Reply #34 on: August 03, 2006, 05:35:17 PM »

I don't have anything complicated in mind.

Basically, the InputManager currently sets up callable functions with a string name, which is used to call a squirrel function. Basically, replace that string with a functor, and call it. I suggest, only one possible functor per event trigger (ie. the 'f' key, the left mouse, etc). The script binding dll (be it squirrel, python, etc), when invoked to register itself, would just add the functor to the InputManager.

Bascially, just like the OIS Ogre Action mapping demo. Only, a more complete function binding templates (eg fast delegates), as the one in the OIS demo only supports class methods at the moment.
Logged

OvermindDL1

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 288
    • View Profile
    • http://www.overminddl1.com/forum/
Squirrel troubles
« Reply #35 on: August 03, 2006, 06:06:59 PM »

Bah, that is too complicated (although that is what I did before >.>), take a look at my new mapper class, combined with the console class...

Header:
Code: [Select]

class Console
{
protected:
PythonScriptManager &m_ScriptManager;
HashMap<std::string,std::string> m_Mapping;
std::list<ConsoleView*> m_Views;

py::dict m_ConsoleDictionary;

public: // Singleton stuff
static Console &getSingleton(void);

protected:
Console(void);
~Console(void);

public: // View handling
bool addView(ConsoleView *newView);
bool removeView(ConsoleView *toRemove);

public: // Registering
bool callAction(std::string actionName);
bool bindToAction(std::string actionName, std::string scriptCall);
std::string getAction(std::string actionName);

public:
std::string doConsoleLine(std::string line);
void showText(std::string text);
void clearText(void);
void show(void);
void hide(void);

SETUP_ROOT_INTERFACES_SIGNITURES;
};


And the relevent code from the implementation file, cutting out pieces that don't have to do with input:
Code: [Select]
Console::Console(void) :
m_ScriptManager(PythonScriptManager::getSingleton())
{
m_ScriptManager.initPython();

m_ConsoleDictionary["__builtins__"] = py::handle<>(PyEval_GetBuiltins());
}

Console::~Console(void)
{
}

Console &Console::getSingleton(void)
{
static Console s_Instance;
return s_Instance;
}

bool Console::callAction(std::string actionName)
{
HashMap<std::string,std::string>::const_iterator found = m_Mapping.find(actionName);
if(found!=m_Mapping.end())
{
std::string command = found->second;
std::string response = doConsoleLine(command);
if(!response.empty())
{
showText(response);
}
return true;
}
return false;
}

bool Console::bindToAction(std::string actionName, std::string scriptCall)
{
m_Mapping[actionName] = scriptCall;
return true;
}

std::string Console::getAction(std::string actionName)
{
HashMap<std::string,std::string>::const_iterator found = m_Mapping.find(actionName);
if(found!=m_Mapping.end())
{
return found->second;
}
return "";
}

std::string Console::doConsoleLine(std::string line)
{
if(line.empty()) return "";//"Empty Command";
std::string strEr;

try
{
py::handle<> result(
PyRun_String(
(line+"\n").c_str()
,Py_single_input,
PythonScriptManager::getSingleton().getMainNamespace().ptr(),
PythonScriptManager::getSingleton().getMainNamespace().ptr())
);
// Result is not needed
result.reset();
}
catch(py::error_already_set)
{
ODL1PyTempAssert(0&&"Invalid Command");
strEr = "SCRIPT-ERROR: Command is invalid:\n"+PythonScriptManager::getTraceback();
if(Ogre::Root::getSingletonPtr()!=0)
Ogre::LogManager::getSingleton().getDefaultLog()->logMessage(strEr,Ogre::LML_CRITICAL);
//writeToScriptLog(strEr);
//PrintConsoleMessage((char*)(strEr.c_str()));
return strEr;
}
return "";
}

void bind1(std::string event, std::string action)
{
Console::getSingleton().bindToAction(event, action);
}
std::string bind2(std::string event)
{
return Console::getSingleton().getAction(event);
}
void callAlias(std::string event)
{
Console::getSingleton().callAction(event);
}

SETUP_ROOT_INTERFACE_BINDINGS_MAIN(Console)
{
using namespace boost::python;

def("bind", &bind1);
def("bind", &bind2);
def("callAlias", &callAlias);
}


Which allows me to do things like... well, let me quote another post I did in another forum:
Quote
Code: [Select]
20:54:02: hello
20:54:02: a = 6


Know what that is, it is from my log, the first one is from hitting 'w', the second is from hitting 's'.  Set it up by typing this into my console:
Code: [Select]
bind("KC_W","print 'hello'")
bind("KC_S","a=4;a=a+2;print 'a =', a")


------------------------------------------Another Post------------------------------------------

Got alias's and all such working too, finalized the keypress names too...
Code: [Select]
// Some default commands, mostly for testing...
// NOTE:  Something fancy for testing, should just expose a toggleConsole() function...
Console::getSingleton().bindToAction("consoleOn", "showConsole();bind(\"Tilde_Press\",\"callAlias('consoleOff')\")");
Console::getSingleton().bindToAction("consoleOff", "hideConsole();bind(\"Tilde_Press\",\"callAlias('consoleOn')\")");
Console::getSingleton().bindToAction("Tilde_Press", "callAlias('consoleOn')");


Also got a console, togglable using the key it is set io (tilde by default as you see).  The console is operational so you could open it and type:
Code: [Select]
bind("consoleOn", "showConsole();bind('Tilde_Press','callAlias(\'consoleOff\')'))
bind("consoleOff", "showConsole();bind('Tilde_Press','callAlias(\'consoleOn\')'))
bind("Tilde_Press", "callAlias('consoleOn')")

to do the same thing...

Yes, I will make a toggleConsole function, I was just making it overly complex for a test.

Will probably change callAlias to callAction though, as that is what it really is...  maybe callEvent, any ideas?


As you can see, you could easily make a "Walk" event and have it make the character walk with, for example, "W_Press" to "CallAlias('Walk')".
Logged

mysterycoder

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 447
    • View Profile
Squirrel troubles
« Reply #36 on: August 03, 2006, 09:30:16 PM »

@pjcast: I tried it with functors, but boost::python can't wrap them. Of course after a half-hour of learning about functors, and trying them out, I find this http://www.boost.org/libs/python/doc/v2/faq.html#funcptr  :wink:


@OvermindDL1: That's an interesting solution, I'll give that a shot.

@pjcast(again): What do you think of the messaging system approach? I think that it would be pretty cool to have a messaging system in general for things like events in games, or tying together collision and game logic(Sound, appropriate reactions, etc, btw, do you think a messaging system is fast enough to deliver colision messages?).
Logged

OvermindDL1

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 288
    • View Profile
    • http://www.overminddl1.com/forum/
Squirrel troubles
« Reply #37 on: August 03, 2006, 11:58:42 PM »

Actually you can wrap a function pointer (I have some code around here somewhere that does), you just have to fully instance it instead of having any templating in it at all.  
Logged

pjcast

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 2652
    • View Profile
    • http://www.wreckedgames.com
Squirrel troubles
« Reply #38 on: September 15, 2006, 04:57:06 PM »

Well, I'm having a bit of trouble with the method Collide of CollsionActor class. I could not get that method to bind quite right, not sure what the deal is  - it has a problem with using the SQUserPointer object in the definition. So, I just commented that one param out, and it compiles now. Why use a userpointer? and not just an actual bound object there? Or, perhaps an int relating to the Actor's ID? I don't much care for using things as userPointers, because then we are dealing with odd problems that can creep up because of  void*s.

One thing I noticed, which may or may not be related to this thread, you had:
CollisionActor *act = (CollisionActor)actor;

Which, is of course very bad, and should not really compile, not sure if it did for you and caused this thread's problem. (In case you missed it, the cast is missing the * part).

I'm very keen on getting squirrel stuff working again atm, as I am working on that part of the editor, and it is a key feature to have remote script debugging working :)
Logged

OvermindDL1

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 288
    • View Profile
    • http://www.overminddl1.com/forum/
Squirrel troubles
« Reply #39 on: September 21, 2006, 11:32:36 AM »

Do note.  *NEVER* use void* to pass things in a C++ program as it destroys type information, very bad when using scripting languages...

You should just do what I did in my engine.  I have a base class from which all game objects are inherited from, "Actor".  I do things like this in my engine:

Code: [Select]

// Assuming base class is Actor, Doodad is subclassed from Actor, Box is subclassed from Doodad
//  Actor and Doodad exist both in C++ and the script, Box is script only
ActorPtr aBox = Actor::Spawn("Box");
if(!aBox) return false;
aBox->setPosition(0.0,1.0,2.0);
ObjectPtr aBoxSelf = aBox.getSelf()
aBoxSelf.attr("someScriptMethod")(3, "hi");
Doodad aDoodadBox = Doodad::cast(aDoodadBox);
if(!aDoodadBox) throw SomeBigError();
aDoodadBox->DoodadFly(1.4);
assert(aBox==aDoodadBox && aBoxSelf==aDoodadBox.getSelf());
aBox.Destroy();
aDoodadBox.reset();
aBoxSelf.reset();
aBox.reset();

First I spawn an Actor of type "Box" (could exist in C++ or Python or wherever).  I then check to make sure it actually spawned (could be a few cases why, abstract, can't fit, not allowed in the level, etc...).
I then set its 3d position like a pointer.
I then get the direct script interface object and put it in aBoxSelf.  I then call a script only method that does not have a C++ signature (a function in the class that does not exist on Actor base since the pointer is an ActorPtr).  I can pass whatever I want to it as well, could even return values (as an Object, which I can then extract whatever out of).
Since I spawned a box, and box inherits from Doodad (which inherits from Actor), and since Doodad is a C++ class, I then cast it using a convenient static method in Doodad (I have a single macro call which you put in any C++ subclass of Actor to auto make many helper static functions like that cast one), and make sure it is valid.  I then call a method that exists only on the Doodad class and its subclasses, the method could even be overridden by the box subclass, even though it exists in script, through the DoodadPtr.  I then call the Destroy method on the aBox ActorPtr which tells the in-game object to remove itself from the game, clean-up, calls the Destroyed method that they can subclass to do proper clean-up, resets all other ActorPtr's in the class (to prevent cyclic link so they will actually die correctly), and so on.  The class itself, although basically turned off, still exists.  Method's can be called on it and so on, although most will do nothing (no script functions are called when an instance is turned off unless then have a decorator on the function that specify they can be called after the instance is dead).  So pointers are still valid, information can be gathered, etc...  When the actual ActorPtr's (or derivatives) are .reset(), and all links in Python are deleted, then the class is free'd.  To do the same functionality (call the same things and so on) inside the script, you would do something like this:
Code: [Select]

aBox = Spawn("Box")
# I could also pass the class itself if I have imported it like: aBox = Spawn(Box)
if not aBox: return false
aBox.setPosition(0.0,1.0,2.0)
aBox.someScriptMethod(3, "hi")
aBox.DoodadFly(1.4)
aBox.Destroy()
del aBox

And yes, if an Actor loses all links, it will be Destroy'd, then free'd, although the active level keeps a pointer to all game instances inside the level, if the level changes or dies or what-not, a LevelChange event is called on the instances that, by default, just call Destroy (but they could do other things, like stick around for the next level, as long as they remember to add themselves to it).

And yea, I'm not sure how "CollisionActor *act = (CollisionActor)actor;" would get past without generating a nice Warning at the least.

Does Squirrel have a JIT out of curiosity?  I've been using psyco to JIT most python code (tremendous speed boosts I must say), but been looking quite a bit into PyPy recently, which fully compiles Python code to machine code.  It is not 1.0 status yet, but I cannot wait...
Logged

pjcast

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 2652
    • View Profile
    • http://www.wreckedgames.com
Squirrel troubles
« Reply #40 on: September 21, 2006, 11:49:50 AM »

Squirrel's script is compiled before you execute it (you can, alternatively, pre-compile your scripts instead of using text files for later loading). ALthough, there is no advanced JIT type of operations I am aware of.
Logged

OvermindDL1

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 288
    • View Profile
    • http://www.overminddl1.com/forum/
Squirrel troubles
« Reply #41 on: September 21, 2006, 10:30:32 PM »

Squirrel operates like CPython it sounds, compiles text into object files (which are stored on-disk as the filename.pyc if you so wish, it does by default, and loads those next time if they are newer then the .py file itself).  The object file in memory is then executed as normal.  There is an extension called psyco (python specialized something compiler something) that compiles the code objects from the interpreted byte code and compiles them to machine code, putting most thing at C compiled speed.  It supports most constructs, and only compiles what it knows how to compile.  If, for example, a function has something in it, like dynamically generating a class, something that cannot be done at normal machine level while remaining in context, it will not compile it and it will still run interpreted.  PyPy is a python compiler written in Python, currently capable of compiling itself.  It can compile to a few different formats including the .NET runtime, the Java runtime, and of course, machine code.  It compiles the entire thing to machine code for example, since it makes the context it has no issues.  It can also compile to a few other things.  As stated, it is not version 1.0-stable yet (else I would have it embedded in my app instead of CPython+psyco).  CPython by itself is rather slow, about 20 times slower then C on math, and about 5 times slower on function calls, although string and map operations are oddly faster in Python then C.  With psyco, math operations are compiled to identical C speed, function calls are identical C speed,  string operations become slightly slower then in normal python, roughly about C speed.  Anything it cannot statically infer at compile time will be slower, but considering that near all Python code in a program is written like a static app anyway (you only assign things like an integer to a local variable in a function for example, so it can treat it as such, but if you assign it an integer in the first part, and a string in the next, then psyco has to treat it differently, slowing it down to between 2-7 times C speed, still faster then Python), it gets quite fast.

Sorry if much of this doesn't make a whole ton of grammatical sense, should have been asleep hours ago and I am feeling it...

You should still use smart pointers to reference every single game object though, makes binding with scripting languages vastly easier.  Smart pointers can take a deleter function when it is set to a pointer, so that if the class is instanced in the C++ side it can delete as normal (by not specifying one for example, although I do to override some things), or from the scripting side it can properly dereference the scripting handle.  Things like boost::python make that completely transparent.  The Spawn function is roughly written like this (typed from a sleepy memory, irrelevent stuff left out):
Code: [Select]
// ActorPtr is typedef'd from boost::shared_ptr<Actor>
ActorPtr Actor::Spawn(std::string className, /*some more param's like init position and other useful things that are optional that I am leaving out for now*/)
{
std::string strErr;
ObjectPtr theClass = getClassObject(className);
if(!theClass) return ActorPtr();
try
{
ObjectPtr theInstance = theClass();
if(!theInstance) return ActorPtr();
ActorPtr cInstance = py::extract<ActorPtr>(theInstance);
Level::getCurrentActiveLevel().addActor(cInstance);
return cInstance;
}
catch(py::error_already_set)
{
strErr = PythonScriptEngine::getCurrentError();
Console::Log(strErr, Console::LogImportant);
}
return ActorPtr()
}


Although it is actually quite a bit more complicated due to checking of some things like valid in current level and such, also quite a bit more white-space...
Logged

mysterycoder

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 447
    • View Profile
Squirrel troubles
« Reply #42 on: October 22, 2006, 10:42:48 PM »

@OvermindDL1, or anyone else:

Do you happen to have a snippet of code which uses Boost::Python to call a function in python from C++, or do you have a way to retrieve a PyObject* function from a module, or maybe both if I'm lucky.  :)

I have made many attempts at calling python functions, with both using the C Python API, and boost. I need to pass the functon an arguement also. I've tried to research this a lot, but I have found no clear cut explaination on the best way to do this.
Logged

OvermindDL1

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 288
    • View Profile
    • http://www.overminddl1.com/forum/
Squirrel troubles
« Reply #43 on: October 23, 2006, 01:00:44 PM »

Yep, I have both and use them plenty.  I am at work right now, but if you have GTalk then get on and I'll show you there (my name on it is OvermindDL1), else wait about 6 hours or so and I'll be home so I can post some of my code.

EDIT1:  I have a few moments so I term-served in.  Here is a chunk of code I use for loading the pickle module, for example, if you need to get a module handle.
Code: [Select]
try
{
m_PickleModule = py::object(py::handle<>(PyImport_ImportModule("cPickle")));
}
catch(py::error_already_set)
{
//ODL1PyTempAssert(0&&"SCRIPT-ERROR: Could not load module 'cPickle' attempting to load 'pickle'");
strEr = "SCRIPT-ERROR: Could not load module 'cPickle' attempting to load 'pickle'\n"+PythonScriptManager::getTraceback()+"\n";
writeToScriptLog(strEr);

try
{
m_PickleModule = py::object(py::handle<>(PyImport_ImportModule("pickle")));
}
catch(py::error_already_set)
{
strEr = "SCRIPT-ERROR: Could not load module 'pickle' canceling\n"+PythonScriptManager::getTraceback()+"\n";
writeToScriptLog(strEr);
return false;
}
}

Where m_PickleModule is a boost::python::object.

As for getting a function from the afore-mentioned pickle module, do something like this:
Code: [Select]
m_PickleModule.attr("dumps")

As for calling it, just call it like any normal boost::python::object:
Code: [Select]
// dumps expects a picklable object as the first param, and a serialization type as the second, with 0 being binary, aka, smallest/fastest, but unreadable

namespace py = boost::python;

// Just calling it directly...
m_PickleModule.attr("dumps")(py::make_tuple(functionName, posArgs, keyArgs), 0);

// Getting the function object, maybe to store a direct link to it for a later time...
py::object pickleDumps(m_PickleModule.attr("dumps"));
// Then to call it...
pickleDumps(objectToPickle, someInteger);


Thus, to 'get' something from a class, whether a variable, property, function, etc..., just call attr("theName") on the py::object.  To call a function py::object, then just call it directly as if it was a function pointer, passing in any and all variables.  The boost templating will properly write out the proper C code to convert everything and so forth when the compile takes place, so you can put as many params of any type that you want, it will just throw the usual py::error_already_set if there are any problems.  And of course you can call the C API's Py_IsFunction (I think is the name), passing in the object's ptr() (was it get()?) to test if it really is a function before hand.  There are attributes you can pull from a function too, to get the name of the function, code object, variables it will use, parameters it can take, etc...
Logged

mysterycoder

  • Administrator
  • Veteran
  • *****
  • Karma: +0/-0
  • Posts: 447
    • View Profile
Squirrel troubles
« Reply #44 on: October 23, 2006, 02:07:29 PM »

Absolutely wonderful.  :D
Thank you very much, worked like a charm.
  :lol: You should have seen some of the crazy stuff I was trying before.  :wink:
Logged
Pages: 1 2 [3] 4