Game Development Community

dev|Pro Game Development Curriculum

scriptT3D extension for Python

by Demolishun · 05/20/2012 (6:59 pm) · 54 comments

Requirements:
Windows OS (Windows XP and above I would assume)
Python 2.7 (32 bit)
T3D 1.2 (It may work for previous versions, but is untested by myself. Entr0py got it working with 1.1.)
T3D MIT 1.2 for MIT Release (Most likely would work fine with 1.2/1.1)
SWIG 2.0.4 (if you are rebuilding source from the .i files)
SWIG 2.0.8 for MIT Release (2.0.4 may work, but i upgraded to 2.0.8)

Code:
MIT ScriptT3D 1.2
Source files are included for engine source files. So you won't need to manually change engine files unless you have a highly modified version of T3D.

older scriptT3D 1.1
older scriptT3D 1.0
The older versions contain a diff against the engine source, the source files after generation through

SWIG, the SWIG files in case you want to modify the interface, the generated Python code, and sample Python scripts.

MIT Version of ScriptT3D

MIT ScriptT3D 1.2 is the version of ScriptT3D that works with the MIT 1.2 version of T3D. The ScriptT3D source code is officially released under the MIT license as well. This is in keeping with the desire to create a mind share of code development, but not limit commercial use of the code. Most of the documentation below still applies. However, the API is radically different. There is limited example code as well as unit tests. In addition there are some text files that help explain some parts of the interface. My intention over the next few months will be to convert the old examples, add new examples, and add new features to this interface. I am ramping up my development efforts on a particular project and this codebase will be the foundation for that work. So when I develop particularly useful features, fix bugs, and otherwise abuse this code those changes will end up here.

Description:
There is lots of documentation in the Python scripts on how to use the interface. The difficult part is how to structure your source code. I put everything including the modified engine source under the source directory in the project directory. This allows me to keep the source files separate from the engine source files. This includes the engine source files I changed. There is also instructions on how to setup SWIG to work with VC++. Look through the source files as I tried to document the interface in the comments where ever I could. This will hopefully make it so I can remember what I did 6 months from now.

Here is a link to a resource that shows how to structure your source in your project: www.garagegames.com/community/resources/view/21664

I included the directory structure in the zip file of how the sub-directories should look. Of course if you want to modify the engine source in the normal engine source directory that is up to you.

The diff file contains all the changes needed to the engine source files. Just ignore the lines that say "Only In". Also, use Wordpad to view the files if running under Windows. Notepad just does not handle *nix style files very well. I used Gnu DiffUtils for Windows to produce the diff.

Please have a look and see what needs to be improved not only for the actual code, but for the installation instructions. I have been getting burned out looking at the code and would like to get it into your hands now. I very much intend for you to add, change, improve, etc. If something is hard to setup or use then let me know and I will tweak this resource to help make it easier for the next person.

Have fun!!!

Install Instructions:

  1. Create a new project or use existing one.
  2. Copy the zip_source.zip file to your project source directory. Usually located in the C:/Torque/Torque 3D 1.2/My Projects/<project name>/source
  3. Unzip the contents into the source directory.
  4. Run the getEngineSourceFiles.bat to copy the needed engine sources to be modified.
  5. Look at the changes in changes_diff.txt to modify the source files you just copied into the project source directories.
  6. Add the following project source files to your projects DLL project in VC++: scriptT3D.cpp, scriptT3D.h, scriptT3D_wrap.cxx, app/mainLoop.cpp, console/compiledEval.cpp, console/console.h, console/consoleInternal.cpp, console/consoleInternal.h, console/scriptObjects.h, console/simObject.h
  7. Add the the following project sources files to your projects executable project in VC++: main/main.cpp.
  8. Set the DLL project to produce: _scriptT3D.pyd instead of the <projectname>.dll file.
  9. Everything should now compile correctly.
  10. The main app should run be compiled and will run as well because we modified it to use the _scriptT3D.pyd library as well. BTW, pyds are still DLLs. So they can be used like DLLs under Windows.
  11. Copy scriptT3D.py to your projects "game" directory. Copy the files in the pyscripts directory to the "game" directory.
  12. Now run the test scripts and read through them to learn how to use the code.


Python Interface Theory of Operation:
I realized there is not a good description of how this works at the function call level. There is some confusion to this effect. Here I will try and describe what takes place for the various interfaces and function calls. This may only be useful to those really needing to know how it works under the hood.
Notes:
TS means Torque Script.


  1. SimObject creation: For SimObject creation (and derived objects) I rely on the console to do the heavy lifting. The reason is there is a lot going on when creating an object and I figure it is best for the existing console code to handle this. This is generally handled through the use the Evaluate command. This is usually called like this scriptT3D.Sim().Evaluate(<string to evaluate>).
  2. Python objects are handled in Python of course. However, they can be imported into the T3D and will have a SimObject host. This SimObject will call methods that are detected at the time of import as executable. It will also access attributes of various types natively in TS. That means you can literally import a Python object and access it in TS as if it were a TS object. Calling a function on an import Python object directly calls the method on the corresponding Python object. It is very fast. Note: For the 1.2 MIT version this option was removed. I did not feel good about the code. If people request I can put this back in. I have not received a lot of feedback on this particular feature so I don't think it will be missed. You can always simulate this feature using callback exports anyway.
  3. Calling functions on a TS object from Python is fast, however it does require a lookup in Python to verify the attribute is actually a function, and then it calls the function directly. The only time it does not call the function directly is if the :: method notation is used. This is not a typical way to call a function. In that case it will use Evaluate. It is also not a recommended way to call a function and is only supported for legacy reasons. It is also not how you call functions in Python. You have to use special syntax to call a function that way.
  4. Callbacks are a way to call Python functions directly from TS. You can create global callbacks in TS that will call functions in Python. In fact, when you import a Python object into TS it creates callbacks for every attribute that is detected as a method or function. This allows for TS objects as part of their normal processing to call Python functions without doing anything special. For instance, you could create a 'think' callback against the AIPlayer namespace. Then every time an AI 'think' method is invoked it would actually call the Python method associated with the callback. Callbacks provide a very powerful way to invoke Python code as if it were part of the TS code.
Hopefully this explains how the functions are actually invoked in scriptT3D for the Python interface. It is as close to the metal as I could get it at this point. SWIG, the interface library that helps me create this interface is transitioning to more direct calls rather than using an intermediate Python object. I tried using this method and it failed to work properly. Once that gets more stable I may be able to get the calls be a little bit faster than they are now.

Python Issues:
You may need to tweak Python for this to work properly. The issue arises from Python development libraries using a debug symbol and requiring a separate debug library for development. I don't recommend using this additional library/libraries. This can be fixed by tweaking a header file in the Python headers. Look in C:/Python27/include/pyconfig.h. Change the lines that look for the _DEBUG symbol being present. Change the line that try to require the debug version of the Python lib to look like this:
ifdef _DEBUG
#				pragma comment(lib,"python27.lib")
#			else
#				pragma comment(lib,"python27.lib")
#			endif /* _DEBUG */
Also disable the Py_DEBUG symbol by commenting it out like this (notice the slashes):
#ifdef _DEBUG
//#	define Py_DEBUG
#endif

Changes:
5/29/2012: Changed how global variables and simobjects are accessed through Sim(). Example:
print Sim().o.objectname
Sim().o.objectname = object # illegal, will fail
print Sim().g.globalname
Sim().g.globalname = "blah, blah, blah"
9/28/2012: Fixed example for new API:
import sys
import scriptT3D
import sched, time
import Queue

# use for scheduling events
#   This allows us to have functions occur after a delay.
#   Good for doing tests that are created before the main loop, but execute in the main loop.
#   This may or may not be used in this program
s = sched.scheduler(time.time, time.sleep)

# just assigning the object to a variable
#   Just a preference
engine = scriptT3D
# Each time you run engine.Sim() it creates an object to access the console.
#   Not a big deal, but just to help you be aware of that.  I had issues trying
#   to create a singleton.
console = engine.Sim()
conObjects = console.o
conGlobals = console.g

# put everything in a giant exception check for testing
#   Not recommended for normal functioning.
#   I got tired of everything freezing before the engine loop...
try:

    # init
    #   Sends command line args to engine
    if not engine.init(len(sys.argv),sys.argv):
        sys.exit(1)

    # console consumer callback
    #   Sends console output to Python stdout.
    def consumerCallback(level, line):
        # notice you can tell the level # (normal, warning, error) of the message
        print level, line

    # export consumer
    engine.ExportConsumer(consumerCallback)

    # execute our own startup
    #   For my setup I rename game.cs (which is called by default) in the root directory to main_hook.cs.
    #   I create an empty game.cs so that the engine does not complain.
    #   If you prefer to use the game.cs as is then this should just give an error in the console output.
    #   My intent is to be able to more finely control the startup sequence.  Eventually I will have
    #   Python be in charge of more of the startup like creating objects and setting up the canvas.
    #   This will take some time to figure out, but in the meantime we can use the normal TS startup.
    #   Another intent is to allow pieces of the engine to be tested out.  This gives you the ability
    #   to create objects and test their interfaces without the rest of the engine getting in the way.

    # create main hook
    def main_hook():
        print "executing main_hook()"
        console.Evaluate(""" exec("main_hook.cs"); """)

    # execute the main_hook
    main_hook();

    # export a function
    def exfunction(data):
        print data
        return len(data)
    # exported into global namespace
    engine.ExportCallback(exfunction,"exfunction")
    # call using evaluate
    print console.Evaluate("""exfunction("hello");""")
    # call using execute
    #   Takes a SimObject (name or id), length of the argument list, and an argument list.
    #   If no SimObject, ie a global function, then provide empty string "".
    #   The first argument is the function name.
    #   It may make sense to make this simpler.
    arglist = ["exfunction","world"]
    print console.Execute("",len(arglist),arglist)

    # messing with global data
    console.g.global_variable = "Some Data"
    print console.g.global_variable
    console.g.another_global = "Some more data..."

    # creating a SimObject and get the id as a string
    #   It is best to let T3D create its own objects.  Hence the use of the evaluate function
    simobj = console.Evaluate("""%temp = new SimObject(UniqueObjectName){attrib1="some value";}; return %temp;""")
    print simobj # prints the id
    # use id to get a reference to a python SimObject
    psimobj = console.FindObject(simobj)
    # now print out attrib1
    print psimobj.attrib1
    # another way, if you know the object name or if you have the number, problem is you cannot put the number in from a variable easily
    print console.o.UniqueObjectName.attrib1
    # can set it this way too
    console.o.UniqueObjectName.attrib1 = "some other value"
    # make more attributes
    console.o.UniqueObjectName.attrib2 = "can also make new attributes"
    # get the object Torque Script equivalent
    #   Can be a way to save object states to a database...
    print repr(psimobj)

    # export a queue object
    q = Queue.Queue()
    # returns the extScriptObject created to hold the exported object
    #   Mainly useful for using in TS functions
    qobj = engine.ExportObject(q)
    # this just shows the object type
    #   Notice the unique name generated by the export.
    #   This keeps the namespace unique for each object.
    print repr(qobj)
    # get the id for use in evaluate scripts
    print qobj.getID()
    # show the functions and variables that got exported from the queue object
    #   This calls the 'dump' function of the SimObject.
    #   There is a lot of junk in the 'dump', but you can see the methods that got exported like: put, qsize, get
    qobj.dump()
    # testing putting something on the queue
    console.Evaluate("""{0!s}.put("{1!s}");""".format(qobj.getID(),'a message on the queue'))
    # now test pulling from the queue
    print "queue message:",q.get_nowait()

    # function callbacks
    #   These are very interesting because I had to make some decisions to get
    #   the ability to use any function as a callback, but still have the ability
    #   to detect which SimObject, if any, it was called against.  If I had not
    #   made this decision then it would be harder to export entire objects unless
    #   I used a different callback mechanism.  I finally decided to use one solid
    #   mechanism, but for every callback you can still see the SimObject if any
    #   the function was called against.  This is possible because functions are
    #   objects themselves and are capable of having attributes set on them.

    # lets create a function to test this on
    def test_callback():
        if(hasattr(test_callback,"__SimObject__")):
            print "Called from the namespace of this object:",test_callback.__SimObject__,test_callback.__SimObject__.getName()
        else:
            print "Not called from SimObject namespace"

    # now lets export some callbacks
    #   callback export syntax: ExportCallback(<function>,"name","namespace"=None,"usage"=None,override=True)
    #   function is the function or method
    #   name is the function name in TS
    #   namespace is the namespace, can be None if not used
    #   usage is the usage string, can be None if not used
    #   override is if this will override existing functions, default is True, if false it will kick back an error if override is attempted
    #   Every parameter after name is optional.
    engine.ExportCallback(test_callback,"test_callback") # global namespace
    engine.ExportCallback(test_callback,"test_callback","SimObject") # SimObject namespace
    console.Evaluate("""test_callback();""") # global call
    console.Evaluate("""{0!s}.test_callback();""".format(simobj)) # test against simobject we created earlier

    # Callbacks against class methods are the same as above.  Python class callbacks already
    #   know which Python class they are called on.  Just use the __SimObject__ attribute to find
    #   out which SimObject called the callback if any.

    # some more info on object exporting
    # complex class
    class cattrib(object):
        def __init__(self):
            self.attr1 = ['a','b']
            self.attr2 = (0,1)
            self.attr3 = {'one':1,'two':2}
            self.attr4 = int(5)
            self.attr5 = float(1.2)
            self.attr6 = str('doodle')
    # export an instance of the cattrib object
    cobj = engine.ExportObject(cattrib())
    # use id to call functions in TS
    # accessing arrays, tuples, dictionaries, stings, and numbers are supported
    #   When setting attributes from TS the functions try and maintain data if
    #   the attribute already exists.  If not it will default to a type like string
    #   or dictionary as they are easist to convert to a Python representation.
    #   So if you want a particular datatype then define it on the Python side first.
    #   Also note that if the key or index for a sequence (array, tuple, dictionary)
    #   is of the wrong type it will fail to set or read the sequence.  Tuples and strings
    #   are not able to be written as they are immutable types.
    console.Evaluate("""echo({0!s}.attr1[1]);""".format(cobj.getId()))
    console.Evaluate("""echo({0!s}.attr2[0]);""".format(cobj.getId()))
    console.Evaluate("""echo({0!s}.attr3["two"]);""".format(cobj.getId()))
    console.Evaluate("""echo({0!s}.attr4);""".format(cobj.getId()))
    console.Evaluate("""echo({0!s}.attr5);""".format(cobj.getId()))
    console.Evaluate("""echo({0!s}.attr6);""".format(cobj.getId()))

    # hwnd handle
    #   This is so other apps can use the T3D hwnd handle.  I have tested this with
    #   wxPython and it does work.  However, wxPython event system interferes with the
    #   T3D event system so it is not recommended to be used with wxPython.  Other
    #   packages I would try to use this with is pyOgre and pyGame.  Those have independent
    #   event systems that are not needed.  Now, these graphicssystems are not necessarily
    #   any better than T3D, it is I just wanted to have the capability.  There are other
    #   GUI systems that might make sense to use with T3D.
    hwnd = engine.gethwnd()

    # run the main loop
    while engine.tick():
        # run any scheduled events
        s.run()

    # normal shutdown
    engine.shutdown()

except Exception,e:
    print e

Contributed:
Look at the posts made by Guy Allard to this resource. He has created some really useful code templates for testing out the engine interface.

Credits:
Thanks to everyone in this community for just being your awesome selves.
Thanks to:
GarageGames for making an awesome engine.
Python developers, of which there are too many to count.
Vince Gee for giving me really good ideas on where to take this interface.
Guy Allard for being the guinea pig.
My Creator, for giving me the drive to create.

About the author

I love programming, I love programming things that go click, whirr, boom. For organized T3D Links visit: http://demolishun.com/?page_id=67

Page «Previous 1 2 3 Last »
#1
05/20/2012 (9:57 pm)
i always like your blogs.
but my little knowledge never was sufficient to understand all of your lines.
your information on python from your last couple of threads has gave me an inspiration to learn python.moreover blender,make human and many other excellent software was built with python(instead of c++).
i actually love c#.but your last two thread has changed my mind.

so i am on the way to learn it.and then first thing will be your this resource.

two excellent resource in same day.
what a nice community! ! !
thanks to all of you.
#2
05/20/2012 (10:40 pm)
@Ahsan,
Thank you for your kind words.

Python is a great tool, but it is made even more powerful by combining with C/C++. A great example is lxml. lxml combines the C libraries for using xml and xslt and is VERY fast. Python makes it more accessible and easier to use. So a combination of Python and C/C++ is better than each alone. Besides Python is written in C. Python is great high level and C = speed.

Let me know if you need help understanding the scriptT3D interface/API. I wrote the resource up quickly so it may not be very easy to put together.

#3
05/21/2012 (3:22 am)
Quote:
Let me know if you need help understanding the scriptT3D interface/API.

thanks.
Quote:
Python is a great tool, but it is made even more powerful by combining with C/C++. A great example is lxml. lxml combines the C libraries for using xml and xslt and is VERY fast. Python makes it more accessible and easier to use. So a combination of Python and C/C++ is better than each alone. Besides Python is written in C. Python is great high level and C = speed.

another excellent informative comment from you.i love this type of writings.
#4
05/24/2012 (3:16 am)
@Frank,

Any chance we can have a bit more info on how to actually get this up and running? I've made the changes from the diff, recompiled fine, but can't figure out how to actually use it.
#5
05/24/2012 (4:38 am)
OK, figured it out. After making the changes to the engine source files, you need to add scriptT3D.cpp, scriptT3D.h and scriptT3D_wrap.cxx to the project.

To get that to compile, you need to set up VS with an additional include directory which points at your pythonDir\include and an additional library directory which points at your pythonDir\libs

Then, after you've compiled the dll, it needs to be renamed to _scriptT3D.pyd

Lastly, copy scriptT3D.py and the test python scripts into your game directory, and you will be able to run the test scripts.

Cool.
#6
05/24/2012 (6:14 am)
I was going to, but you did it for me. When I get a chance I will update the resource. Thanks for figuring out the steps.
#7
05/25/2012 (2:16 am)
Gotta say, this has potential.
SQL
XML
SHA
IRC client
pyCurl or urllib for file streaming

can all be added without source code edits. Nice.
#8
05/27/2012 (8:21 am)
If anyone wants a total barebones python script which performs the absolute minimum T3D startup to give a functioning windows console into which torque script commands can be entered, rename your main.cs to something else, and replace it with an empty main.cs. Then create a new python file containing:

import sys
import scriptT3D

engine = scriptT3D
console = engine.Sim()

try:

    # init
    #   Sends command line args to engine
    if not engine.init(len(sys.argv),sys.argv):
        sys.exit(1)

    # set up console consumer
    def consumerCallback(level, line):
        print level, line
    engine.ExportConsumer(consumerCallback)

    # enable the windows console
    console.enableWinConsole(1)

    # run the main loop
    while engine.tick():
        pass

    # normal shutdown
    engine.shutdown()

except Exception,e:
    print e

Then run this file.
#9
05/27/2012 (11:50 am)
@Guy,
That is really cool! I was wondering how to do that.
#10
05/28/2012 (7:23 am)
@Frank, I find it really useful for testing out stuff without all of the graphical bloat getting in the way.

Here's a little function to make creating torque objects from python a bit neater:

# utility function to create torque objects
    # @param torqueClassName the torque class to create
    # @param objectName the name to give the object
    # @param datafields list of script fields to set on the new object
    #
    # @eg sword = createTorqueObject("item", "Bastard sword", ["dataBlock = BastardSwordData", "static = true", "rotate = true"]) 
    def createTorqueObject(torqueClassName, objectName, dataFields):
        ts = "%obj = new " + torqueClassName + "(" + objectName + "){"
        for field in dataFields:
            ts += field + ";"
        ts += "};"
        ts += "return %obj;"
        objId = console.Evaluate(ts)
        obj = console.FindObject(objId)
        if obj is None:
            print 1, "Could not create object\n" + ts
        return obj

so instead of doing
objId = console.Evaluate("""%obj = new ScriptObject(testObj){
                            class = testClass;
                            someValue = 42;
                            };
                            return %obj;""")
obj = console.FindObject(objId)

you can do
obj = createTorqueObject("scriptObject",
                         "testObj",
                         ["class = testClass",
                          "someValue = 42"])
#11
05/28/2012 (12:15 pm)
And following in from the minimal console torque, here's a script that contains the absolute bare minimum needed to initialize the canvas and display a message.

import sys
import scriptT3D

engine = scriptT3D
console = engine.Sim()

try:

    # init
    #   Sends command line args to engine
    if not engine.init(len(sys.argv),sys.argv):
        sys.exit(1)

    # set up console consumer
    def consumerCallback(level, line):
        print level, line
    engine.ExportConsumer(consumerCallback)

    # enable the windows console
    console.enableWinConsole(1)

    # enable logging to console.log
    console.setLogMode(6)

    # utility function to create torque objects
    # @param torqueClassName the torque class to create
    # @param objectName the name to give the object
    # @param datafields list of script fields to set on the new object
    #
    # @eg sword = createTorqueObject("item", "Bastard sword", ["dataBlock = BastardSwordData", "static = true", "rotate = true"]) 
    def createTorqueObject(torqueClassName, objectName, dataFields):
        ts = "%obj = new " + torqueClassName + "(" + objectName + ")"
        if dataFields.__len__() > 0:
            ts += "{"
            for field in dataFields:
                ts += field + ";"
            ts += "}"
        ts += "; return %obj;"
        objId = console.Evaluate(ts)
        obj = console.FindObject(objId)
        if obj is None:
            console.error("Could not create object\n" + ts)
        return obj

    # the canvas will not initialize unless GuiDefaultProfile and GuiToolTipProfile exist
    if console.FindObject("GuiDefaultProfile") is None:
        createTorqueObject("GuiControlProfile", "GuiDefaultProfile", [])

    if console.FindObject("GuiToolTipProfile") is None:
        createTorqueObject("GuiControlProfile", "GuiToolTipProfile", [])

    # create the canvas
    if console.FindObject("Canvas") is None:
        canvas = createTorqueObject("GuiCanvas", "Canvas", [])

    # set the window title
    if canvas is not None:
        canvas.setWindowTitle("ScriptT3D")

    # we need a render pass manager to actually render stuff
    if console.FindObject("DiffuseRenderPassManager") is None:
        createTorqueObject("RenderPassManager", "DiffuseRenderPassManager", [])

    # and for some reason, we also need a light manager to be set here
    # let's just use basic lighting for now
    console.setLightManager("Basic Lighting")

    # activate direct input so that we can process keys
    console.activateDirectInput();

    # and let's bind the escape key here so that we can exit
    actionMap = console.FindObject("GlobalActionMap")
    if actionMap is not None:
        actionMap.bindCmd("keyboard", "escape", "quit();", "");


    # set up a gui profile for some black text
    createTorqueObject("GuiControlProfile", "GuiTextProfile",
                       ["fontSize = 48",
                        "fontColor = \"50 50 50\"",
                        "justify = \"center\""])

    # create a text control
    txt = createTorqueObject("GuiTextCtrl", "",
                             ["profile = GuiTextProfile",
                              "position = \"200 100\"",
                              "extent = \"200 100\"",
                              "horizSizing = \"center\"",
                              "verSizing = \"center\"",
                              "text = \"Hello ScriptT3D!!\""])
    
    # stick it on the canvas
    canvas.setContent(txt)
    

    # run the main loop
    while engine.tick():
        pass

    # normal shutdown
    engine.shutdown()

except Exception,e:
    print e
#12
05/28/2012 (5:18 pm)
Man, that last one was something I was struggling with. I really really wanted a minimum engine setup, but every time I added something from the scripts it would need something else somewhere else.

Thanks for making that. I will have to tweak the resource to include file links for your awesome code!

Edit:
Guy, this simple gui example is fantastic! Definitely a good starting point to build from. I am going to try and add some more examples like that. This would definitely help someone learn the engine from the ground up.
#13
05/29/2012 (1:19 am)
@Guy,
You can use repr to serialize an object. So you could have some fun with this feature like this.

[code]
obId = console.Evaluate("""return new SimObject();""")
obj = console.FindObject(objId)

obj.attrib1 = "replacename"
obj.setName("Hello Object")

sobj = repr(obj)

# do some search and replace to change the name and the SimObject to some other object

# then either save that or evaluate, wammo you can create any SimObject type without affecting the current game. Useful for autogenerating levels objects or objects that would cause issues if actually created outside of a mission say

# it lends to generating objects for databases too

# it is probably slower than your function though

# Also there is a function in the library that does this, and would be faster than python as it is written in C. I should have checked earlier. There is a console function called spawnObject. Go figure there was already an easier way to do this.
#14
05/29/2012 (1:57 am)
Umm, ignore the bit about spawnObject. It is doing strange things.
Edit:
It looks like there is a bug in the automagic function callback for the Sim() object. I need to track down what the issue is. On the second call is somehow replaces the reference to the function itself with the result of the console function. This is strange and I am not sure what is causing this.
#15
05/29/2012 (4:16 am)
Alright, spawnObject would be okay if it were not for some funny interactions inside the engine. Lets see if I can explain this:

  1. "spawnObject" is looked up dynamically and is checked to determine if it is a SimObject, a global variable, or a function name.
  2. Then the Sim() object returns either a SimObject, the variables value, or a function object reference.
  3. If it has the parenthesis afterwards then it will attempt to call whatever comes back.
Remember that sequence. Now I found out when you call the spawnObject console function it will set a global variable to contain the objects newly created id. This global variable is called "$spawnObject". Now to put this in context any check for a global variable does not need to have the '$' at the beginning because internally the engine will append the '$' to the beginning of a global variable search. Specifically Con::getVariable adds a dollar sign to the beginning of a variable search.

So if you look at the sequence above you will find that because we are checking for a global variable before we check for a function by the same name you can see why this function is causing issues. The function spawnObject gets called the first time, it sets the global variable spawnObject to the id value, then if it gets called again the lookup finds the global variable instead.

I am not 100% sure how to deal with this issue. I can change the order and check for functions first. That would solve the issue for this case. However, we may run into a different issue later with a variable access gets messed up by a global function definition. Right now I am stuck because the initial __getattr__ call in Python cannot tell if we are looking for a variable or function. So I cannot tell absolutely at lookup time. Now I can change the interface to make it so that global variables, simobjects, and functions are accessed differently. This will keep naming conflicts from occurring. The interface is not as simple, but it does remove this scenario and potentially helps with hours of scratching ones head.

I propose to take it a direction like this:
# functions stay the same
Sim().echo("hello console")
# globals are accessed like this
Sim().g.globalname
# simobjects are accessed like this
Sim().o.simobjectname
This will actually speed up the code a bit as I no longer will have to check what is what. I will just determine if it exists and if it does then return the data, reference, or call the function.

I will think on this for a bit. Let me know your thoughts on this.
#16
05/29/2012 (8:30 am)
So what you're saying is that if there's a global variable and a function that share the same name, the interface gets confused as there's no way to differentiate between them?
Tricky.
I can see that causing multiple issues down the line.
#17
05/29/2012 (11:42 am)
Yes. The really tricky part is that spawnObject creates the variable spawnObject. So code that you have tested to work could fail later.

# this succeeds
console.spawnObject("GuiControlProfile", "None", "GuiDefaultProfile")
# this fails because the spawnObject sets a global spawnObject
# It now returns the object id as a string for the previous object
# Python complains how you cannot call a string object as a function
console.spawnObject("GuiControlProfile", "None", "GuiToolTipProfile")

edit
I am thinking we should use really short syntax to prevent naming collisions with function names we might want to access. Or we could have functions accessed the same way: Sim().f.spawnObject() for example. Another possibility is using the array index for globals: Sim()["$gvariable"] or Sim()["gvariable"]. With that we could have a second arg to explicitly differentiate between global variables and simobjects. Maybe send a tuple to the array index syntax.

What are your thoughts on syntax? Do you have a preference?
#18
05/29/2012 (11:56 am)
It's possible to get the spawnObject function to clean up after itself by deleting the global variable that it adds. That would make the spawnObject code work. But, there will still be the underlying issue of getting global variables mangled up with global functions at some point.

I think it's probably quite lucky that this has shown up now instead of later.

I think your suggested syntax would work ok. You could do:

tsGlobalVars = Sim().g
tsObjects = Sim().o

and then they would be prettier to work with,
someValue = tsGlobalVars.someGlobalVar
someObject = tsObjects.Obj

also, if that speeds things up a bit, that's gotta be good
#19
05/29/2012 (12:32 pm)
Quote:I think it's probably quite lucky that this has shown up now instead of later.
I agree 150% with that observation.

Yeah, that would work nicely. I will work through this and get this fixed ASAP. This is definitely a nasty bug right now.

#20
05/29/2012 (4:18 pm)
Here is an example to show the 1.1 version changes:
import sys
import traceback
import scriptT3D

engine = scriptT3D
console = engine.Sim()


# init
#   Sends command line args to engine
if not engine.init(len(sys.argv),sys.argv):
    sys.exit(1)

try:

    # set up console consumer
    def consumerCallback(level, line):
        print level, line
    engine.ExportConsumer(consumerCallback)

    # enable the windows console
    print console.enableWinConsole
    console.enableWinConsole(1)

    console.echo("hello")
    console.echo("hello")
    print "echo func:",console.echo

    print console["spawnObject"]
    print console.spawnObject
    func = console["spawnObject"]
    func("SimObject","None")
    print console.spawnObject("SimObject","None","test")
    print console.spawnObject
    print console["spawnObject"]

    print "test global access",console.g.spawnObject
    #console.o.sobject = 5  # this will fail
    print console.o.test

except Exception:
    traceback.print_exc()
    #console.quit()

# run the main loop
while engine.tick():
    pass

# normal shutdown
engine.shutdown()
Page «Previous 1 2 3 Last »