diff --git a/direct/Config.pp b/direct/Config.pp new file mode 100644 index 0000000000..a17d61df91 --- /dev/null +++ b/direct/Config.pp @@ -0,0 +1,18 @@ +// +// Config.pp +// +// This file defines certain configuration variables that are written +// into the various make scripts. It is processed by ppremake (along +// with the Sources.pp files in each of the various directories) to +// generate build scripts appropriate to each environment. +// +// There are not too many variables to declare at this level; most of +// them are defined in the DTOOL-specific Config.pp. + + +// Where should we find PANDA? This will come from the environment +// variable if it is set. +#if $[eq $[PANDA],] + #define PANDA /usr/local/panda +#endif + diff --git a/direct/Package.pp b/direct/Package.pp new file mode 100644 index 0000000000..4c0cf021d0 --- /dev/null +++ b/direct/Package.pp @@ -0,0 +1,34 @@ +// +// Package.pp +// +// This file defines certain configuration variables that are to be +// written into the various make scripts. It is processed by ppremake +// (along with the Sources.pp files in each of the various +// directories) to generate build scripts appropriate to each +// environment. +// +// This is the package-specific file, which should be at the top of +// every source hierarchy. It generally gets the ball rolling, and is +// responsible for explicitly including all of the relevent Config.pp +// files. + + + +// What is the name and version of this source tree? +#if $[eq $[PACKAGE],] + #define PACKAGE direct + #define VERSION 0.80 +#endif + + +// Pull in the package-level Config file. This contains a few +// configuration variables that the user might want to fine-tune. +#include $[THISDIRPREFIX]Config.pp + + +// Also get the PANDA Package file and everything that includes. +#if $[not $[isdir $[PANDA]]] + #error Directory defined by $PANDA not found! Are you attached properly? +#endif + +#include $[PANDA]/Package.pp diff --git a/direct/Sources.pp b/direct/Sources.pp new file mode 100644 index 0000000000..08436ca73d --- /dev/null +++ b/direct/Sources.pp @@ -0,0 +1,10 @@ +// This is the toplevel directory. It contains configure.in and other +// stuff. + +#define DIR_TYPE toplevel + +#define SAMPLE_SOURCE_FILE src/directbase/directbase.cxx +#define REQUIRED_TREES dtool panda + +#define EXTRA_DIST \ + Sources.pp Config.pp Package.pp diff --git a/direct/metalibs/Sources.pp b/direct/metalibs/Sources.pp new file mode 100644 index 0000000000..93e28f9e7c --- /dev/null +++ b/direct/metalibs/Sources.pp @@ -0,0 +1,7 @@ +// This is a group directory: a directory level above a number of +// source subdirectories. + +#define DIR_TYPE group + +// The metalibs directory always depends on the src directory. +#define DEPENDS src diff --git a/direct/metalibs/direct/Sources.pp b/direct/metalibs/direct/Sources.pp new file mode 100644 index 0000000000..ec8566df09 --- /dev/null +++ b/direct/metalibs/direct/Sources.pp @@ -0,0 +1,20 @@ +// DIR_TYPE "metalib" indicates we are building a shared library that +// consists mostly of references to other shared libraries. Under +// Windows, this directly produces a DLL (as opposed to the regular +// src libraries, which don't produce anything but a pile of OBJ files +// under Windows). + +#define DIR_TYPE metalib +#define BUILDING_DLL BUILDING_DIRECT + +#define COMPONENT_LIBS \ + directbase dcparse showbase + +#define OTHER_LIBS panda dtool + +#begin metalib_target + #define TARGET direct + + #define SOURCES direct.cxx +#end metalib_target + diff --git a/direct/metalibs/direct/direct.cxx b/direct/metalibs/direct/direct.cxx new file mode 100644 index 0000000000..eecbfba8c7 --- /dev/null +++ b/direct/metalibs/direct/direct.cxx @@ -0,0 +1,9 @@ +// Filename: direct.cxx +// Created by: drose (18May00) +// +//////////////////////////////////////////////////////////////////// + +// This is a dummy file whose sole purpose is to give the compiler +// something to compile when making libdirect.so in NO_DEFER mode, +// which generates an empty library that itself links with all the +// other shared libraries that make up libdirect. diff --git a/direct/src/Sources.pp b/direct/src/Sources.pp new file mode 100644 index 0000000000..fb8681dab2 --- /dev/null +++ b/direct/src/Sources.pp @@ -0,0 +1,4 @@ +// This is a group directory: a directory level above a number of +// source subdirectories. + +#define DIR_TYPE group diff --git a/direct/src/actor/Actor.py b/direct/src/actor/Actor.py new file mode 100644 index 0000000000..7a1f309845 --- /dev/null +++ b/direct/src/actor/Actor.py @@ -0,0 +1,580 @@ +"""Actor module: contains the Actor class""" + +from PandaObject import * + +class Actor(PandaObject, ShowBase.NodePath): + """Actor class: Contains methods for creating, manipulating + and playing animations on characters""" + + #create the Actor class DirectNotify category + notify = directNotify.newCategory("Actor") + partPrefix = "__Actor_" + + #special methods + + def __init__(self, models=None, anims=None, other=None): + """__init__(self, string | string:string{}, string:string{} | + string:(string:string{}){}, Actor=None) + Actor constructor: can be used to create single or multipart + actors. If another Actor is supplied as an argument this + method acts like a copy constructor. Single part actors are + created by calling with a model and animation dictionary + (animName:animPath{}) as follows: + + a = Actor("panda-3k.egg", {"walk":"panda-walk.egg" \ + "run":"panda-run.egg"}) + + This could be displayed and animated as such: + + a.reparentTo(render) + a.loop("walk") + a.stop() + + Multipart actors expect a dictionary of parts and a dictionary + of animation dictionaries (partName:(animName:animPath{}){}) as + below: + + a = Actor( + + # part dictionary + {"head":"char/dogMM/dogMM_Shorts-head-mod", \ + "torso":"char/dogMM/dogMM_Shorts-torso-mod", \ + "legs":"char/dogMM/dogMM_Shorts-legs-mod"} , \ + + # dictionary of anim dictionaries + {"head":{"walk":"char/dogMM/dogMM_Shorts-head-walk", \ + "run":"char/dogMM/dogMM_Shorts-head-run"}, \ + "torso":{"walk":"char/dogMM/dogMM_Shorts-torso-walk", \ + "run":"char/dogMM/dogMM_Shorts-torso-run"}, \ + "legs":{"walk":"char/dogMM/dogMM_Shorts-legs-walk", \ + "run":"char/dogMM/dogMM_Shorts-legs-run"} \ + }) + + In addition multipart actor parts need to be connected together + in a meaningful fashion: + + a.attach("head", "torso", "joint-head") + a.attach("torso", "legs", "joint-hips") + + + Other useful Acotr class functions: + + #fix actor eye rendering + a.drawInFront("joint-pupil?", "eyes*") + + #fix bounding volumes - this must be done after drawing + #the actor for a few frames, otherwise it has no effect + a.fixBounds() + + """ + + # initial our NodePath essence + NodePath.__init__(self) + + # create data structures + self.__partBundleDict = {} + self.__animControlDict = {} + + if (other == None): + # act like a normal contructor + + # create base hierarchy + self.assign(hidden.attachNewNode('actor')) + self.setGeomNode(self.attachNewNode('actorGeom')) + + # load models + # make sure we have models + if (models): + # if this is a dictionary + if (type(models)==type({})): + # then it must be multipart actor + for partName in models.keys(): + self.loadModel(models[partName], partName) + else: + # else it is a single part actor + self.loadModel(models) + + # load anims + # make sure the actor has animations + if (anims): + if (len(anims) >= 1): + # if so, does it have a dictionary of dictionaries + if (type(anims[anims.keys()[0]])==type({})): + # then it must be multipart + for partName in anims.keys(): + self.loadAnims(anims[partName], partName) + else: + # else it is not multipart + self.loadAnims(anims) + + else: + # act like a copy constructor + + # copy the scene graph elements of other + otherCopy = other.copyTo(hidden) + # assign these elements to ourselve + self.assign(otherCopy) + self.setGeomNode(otherCopy.getChild(0)) + + # copy the part dictionary from other + self.__copyPartBundles(other) + + # copy the anim dictionary from other + self.__copyAnimControls(other) + + + def __str__(self): + """__str__(self) + Actor print function""" + return "Actor: partBundleDict = %s, animControlDict = %s" % \ + (self.__partBundleDict, self.__animControlDict) + + + # accessing + + def getPartNames(self): + """getPartNames(self): + Return list of Actor part names. If not multipart, + returns modelRoot""" + return self.__partBundleDict.keys() + + def getGeomNode(self): + """getGeomNode(self) + Return the node that contains all actor geometry""" + return self.__geomNode + + def setGeomNode(self, node): + """setGeomNode(self, node) + Set the node that contains all actor geometry""" + self.__geomNode = node + + def getFrameRate(self, animName=None, partName=None): + """getFrameRate(self, string, string=None) + Return duration of given anim name and given part. + If no anim specified, use the currently playing anim. + If no part specified, return anim durations of first part""" + if (partName == None): + partName = self.__animControlDict.keys()[0] + + if (animName==None): + animName = self.getCurrentAnim(partName) + + # get duration for named part only + if (self.__animControlDict.has_key(partName)): + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + return animControl.getFrameRate() + else: + Actor.notify.warning("no part named %s" % (partName)) + + return None + + def getPlayRate(self, animName=None, partName=None): + """getPlayRate(self, string=None, string=None) + Return the play rate of given anim for a given part. + If no part is given, assume first part in dictionary. + If no anim is given, find the current anim for the part""" + if (partName==None): + partName = self.__animControlDict.keys()[0] + + if (animName==None): + animName = self.getCurrentAnim(partName) + + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + return animControl.getPlayRate() + else: + return None + + def setPlayRate(self, rate, animName=None, partName=None): + """getPlayRate(self, float, string=None, string=None) + Set the play rate of given anim for a given part. + If no part is given, set for all parts in dictionary. + If no anim is given, find the current anim for the part""" + # make a list of partNames for loop below + if (partName==None): + partNames = self.__animControlDict.keys() + else: + partNames = [] + partNames.append(partName) + + # for each part in list, set play rate on given or current anim + for partName in partNames: + if (animName==None): + animName = self.getCurrentAnim(partName) + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + animControl.setPlayRate(rate) + + def getDuration(self, animName=None, partName=None): + """getDuration(self, string, string=None) + Return duration of given anim name and given part. + If no anim specified, use the currently playing anim. + If no part specified, return anim duration of first part""" + if (partName == None): + partName = self.__animControlDict.keys()[0] + + if (animName==None): + animName = self.getCurrentAnim(partName) + + # get duration for named part only + if (self.__animControlDict.has_key(partName)): + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + return (animControl.getNumFrames() / \ + animControl.getFrameRate()) + else: + Actor.notify.warning("no part named %s" % (partName)) + + return None + + def getCurrentAnim(self, partName=None): + """getCurrentAnim(self, string=None) + Return the anim current playing on the actor. If part not + specified return current anim of first part in dictionary""" + if (partName==None): + partName = self.__animControlDict.keys()[0] + + # loop through all anims for named part and find if any are playing + if (self.__animControlDict.has_key(partName)): + for animName in self.__animControlDict[partName].keys(): + if (self.__getAnimControl(animName, partName).isPlaying()): + return animName + else: + Actor.notify.warning("no part named %s" % (partName)) + + # we must have found none, or gotten an error + return None + + + + # arranging + + def getPart(self, partName): + """getPart(self, string) + Find the named part in the partBundleDict and return it, or + return None if not present""" + if (self.__partBundleDict.has_key(partName)): + return self.__partBundleDict[partName] + else: + return None + + def removePart(self, partName): + """removePart(Self, string) + Remove the geometry and animations of the named part if present + NOTE: this will remove parented geometry also!""" + # remove the geometry + if (self.__partBundleDict.has_key(partName)): + self.__partBundleDict[partName].removeNode() + del(self.__partBundleDict[partName]) + # remove the animations + if (self.__animControlDict.has_key(partName)): + del(self.__animControlDict[partName]) + + + def hidePart(self, partName): + """hidePart(self, string) + Make the given part not render, even though still in the tree. + NOTE: this functionality will be effected by the 'attach' method""" + if (self.__partBundleDict.has_key(partName)): + self.__partBundleDict[partName].hide() + else: + Actor.notify.warning("no part named %s!" % (partName)) + + def showPart(self, partName): + """showPart(self, string) + Make the given part render while in the tree. + NOTE: this functionality will be effected by the 'attach' method""" + if (self.__partBundleDict.has_key(partName)): + self.__partBundleDict[partName].show() + else: + Actor.notify.warning("no part named %s!" % (partName)) + + def instance(self, partName, anotherPart, jointName): + """instance(self, string, string, string) + Instance one actor part to another at a joint called jointName""" + if (self.__partBundleDict.has_key(partName)): + if (self.__partBundleDict.has_key(anotherPart)): + joint = NodePath(self.__partBundleDict[anotherPart], \ + "**/" + jointName) + if (joint.isEmpty()): + Actor.notify.warning("%s not found!" % (jointName)) + else: + return self.__partBundleDict[partName].instanceTo(joint) + else: + Actor.notify.warning("no part named %s!" % (anotherPart)) + else: + Actor.notify.warning("no part named %s!" % (partName)) + + + def attach(self, partName, anotherPart, jointName): + """attach(self, string, string, string) + Attach one actor part to another at a joint called jointName""" + if (self.__partBundleDict.has_key(partName)): + if (self.__partBundleDict.has_key(anotherPart)): + joint = NodePath(self.__partBundleDict[anotherPart], \ + "**/" + jointName) + if (joint.isEmpty()): + Actor.notify.warning("%s not found!" % (jointName)) + else: + self.__partBundleDict[partName].reparentTo(joint) + else: + Actor.notify.warning("no part named %s!" % (anotherPart)) + else: + Actor.notify.warning("no part named %s!" % (partName)) + + + def drawInFront(self, frontPartName, backPartName, root=None): + """drawInFront(self, string, string=None) + Arrange geometry so the frontPart is drawn properly wrt backPart. + Takes an optional argument root as the start of the search for the + given parts""" + + # start search from self if no root given + if (root==None): + root = self + + # make the back part have the proper transition + backPart = NodePath(root, "**/"+backPartName) + if (backPart.isEmpty()): + Actor.notify.warning("no part named %s!" % (backPartName)) + else: + (backPart.getBottomArc()).setTransition(DirectRenderTransition()) + + #reparent the front parts to the back parts + frontParts = self.findAllMatches( "**/"+frontPartName) + numFrontParts = frontParts.getNumPaths() + for partNum in range(0, numFrontParts): + (frontParts.getPath(partNum)).reparentTo(backPart) + + + def fixBounds(self, part=None): + """fixBounds(self, nodePath=None) + Force recomputation of bounding spheres for all geoms + in a given part. If no part specified, fix all geoms + in this actor""" + + # if no part name specified fix all parts + if (part==None): + part = self + + # update all characters first + charNodes = part.findAllMatches("**/+Character") + numCharNodes = charNodes.getNumPaths() + for charNum in range(0, numCharNodes): + (charNodes.getPath(charNum)).node().update() + + # for each geomNode, iterate through all geoms and force update + # of bounding spheres by marking current bounds as stale + geomNodes = part.findAllMatches("**/+GeomNode") + numGeomNodes = geomNodes.getNumPaths() + for nodeNum in range(0, numGeomNodes): + thisGeomNode = geomNodes.getPath(nodeNum) + numGeoms = thisGeomNode.node().getNumGeoms() + for geomNum in range(0, numGeoms): + thisGeom = thisGeomNode.node().getGeom(geomNum) + thisGeom.markBoundStale() + Actor.notify.info("fixing bounds for node %s, geom %s" % \ + (nodeNum, geomNum)) + thisGeomNode.node().markBoundStale() + + def showBounds(self): + """showBounds(self) + Show the bounds of all actor geoms""" + geomNodes = self.findAllMatches("**/+GeomNode") + numGeomNodes = geomNodes.getNumPaths() + + for nodeNum in range(0, numGeomNodes): + geomNodes.getPath(nodeNum).showBounds() + + def hideBounds(self): + """hideBounds(self) + Hide the bounds of all actor geoms""" + geomNodes = self.findAllMatches("**/+GeomNode") + numGeomNodes = geomNodes.getNumPaths() + + for nodeNum in range(0, numGeomNodes): + geomNodes.getPath(nodeNum).hideBounds() + + + # actions + + def stop(self, animName=None, partName=None): + """stop(self, string=None, string=None) + Stop named animation on the given part of the actor. + If no name specified then stop all animations on the actor""" + if (animName == None): + #loop and stop ALL anims + for animControl in self.__animControlDict[partName].keys(): + self.__animControlDict[partName][animControl].stop() + else: + #stop the specified anim + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + animControl.stop() + + def play(self, animName, partName=None): + """play(self, string, string=None) + Play the given animation on the given part of the actor. + If no part is specified, try to play on all parts""" + if (partName == None): + # loop all parts + for partName in self.__animControlDict.keys(): + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + animControl.play() + else: + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + animControl.play() + + + def loop(self, animName, restart=1, partName=None): + """loop(self, string, int=1, string=None) + Loop the given animation on the given part of the actor, + restarting at zero frame if requested. If no part name + is given then try to loop on all parts""" + if (partName == None): + # loop all parts + for partName in self.__animControlDict.keys(): + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + animControl.loop(restart) + else: + # loop a specific part + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + animControl.loop(restart) + + def pose(self, animName, frame, partName=None): + """pose(self, string, int, string=None) + Pose the actor in position found at given frame in the specified + animation for the specified part. If no part is specified attempt + to apply pose to all parts""" + if (partName==None): + # pose all parts + for partName in self.__animControlDict.keys(): + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + animControl.pose(frame) + else: + # pose a specific part + animControl = self.__getAnimControl(animName, partName) + if (animControl != None): + animControl.pose(frame) + + + #private + + def __getAnimControl(self, animName, partName): + """__getAnimControl(self, string, string) + Search the animControl dictionary for given anim and part. + Return the animControl if present, or None otherwise""" + if (self.__animControlDict.has_key(partName)): + if (self.__animControlDict[partName].has_key(animName)): + return self.__animControlDict[partName][animName] + else: + # anim was not present + Actor.notify.warning("couldn't find anim: %s" % (animName)) + else: + # part was not present + Actor.notify.warning("couldn't find part: %s" % (partName)) + + return None + + + def loadModel(self, modelPath, partName="modelRoot"): + """loadModel(self, string, string="modelRoot") + Actor model loader. Takes a model name (ie file path) and + a partName (defaults to "modelRoot")""" + + Actor.notify.info("in loadModel: %s , part: %s" % \ + (modelPath, partName)) + + # load the model and extract its part bundle + model = loader.loadModelCopy(modelPath) + + + bundle = NodePath(model, "**/+PartBundleNode") + if (bundle.isEmpty()): + Actor.notify.warning("%s is not a character!" % (modelPath)) + else: + # we rename this node to make Actor copying easier + bundle.node().setName(Actor.partPrefix + partName) + bundle.reparentTo(self.__geomNode) + model.removeNode() + + #make this mimic mutli-part by giving it a default part anme + self.__partBundleDict[partName] = bundle + + + def loadAnims(self, anims, partName="modelRoot"): + """loadAnims(self, string:string{}, string="modelRoot") + Actor anim loader. Takes an optional partName (defaults to + 'modelRoot' for non-multipart actors) and dict of corresponding + anims in the form animName:animPath{}""" + + Actor.notify.info("in loadAnims: %s, part: %s" % (anims, partName)) + + animDict = {} + + for animName in anims.keys(): + + #load the anim and get its anim bundle + anim = loader.loadModelCopy(anims[animName]) + animBundle = \ + (NodePath(anim, "**/+AnimBundleNode").node()).getBundle() + + #bind anim + bundleNode = (self.__partBundleDict[partName]).node() + animControl = (bundleNode.getBundle()).bindAnim(animBundle, -1) + if (animControl == None): + Actor.notify.error("Null AnimControl: %s" % (animName)) + else: + animDict[animName] = animControl + + # add this part's dictionary to animation dictionary + self.__animControlDict[partName] = animDict + + + def __copyPartBundles(self, other): + """__copyPartBundles(self, Actor) + Copy the part bundle dictionary from another actor as this + instance's own. NOTE: this method does not actually copy geometry""" + for partName in other.__partBundleDict.keys(): + print("copyPart: copying part named = %s" % (partName)) + # find the part in our tree + partBundle = self.find("**/" + Actor.partPrefix + partName) + if (partBundle != None): + # store the part bundle + self.__partBundleDict[partName] = partBundle + else: + Actor.notify.error("couldn't find matching part: %s" % \ + partName) + + + def __copyAnimControls(self, other): + """__copyAnimControls(self, Actor) + Get the anims from the anim control's in the anim control + dictionary of another actor. Bind these anim's to the part + bundles in our part bundle dict that have matching names, and + store the resulting anim controls in our own part bundle dict""" + for partName in other.__animControlDict.keys(): + print("copyAnim: partName = %s" % (partName)) + self.__animControlDict[partName] = {} + for animName in other.__animControlDict[partName].keys(): + print(" anim: %s" % (animName)) + # get the anim + animBundle = \ + other.__animControlDict[partName][animName].getAnim() + # get the part + partBundleNode = (self.__partBundleDict[partName].node()) + # bind the anim + animControl = \ + (partBundleNode.getBundle().bindAnim(animBundle, -1)) + if (animControl == None): + Actor.notify.error("Null animControl: %s" % (animName)) + else: + # store the anim control + self.__animControlDict[partName][animName] = animControl + diff --git a/direct/src/actor/Sources.pp b/direct/src/actor/Sources.pp new file mode 100644 index 0000000000..a03ea8c336 --- /dev/null +++ b/direct/src/actor/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/direct/src/configfiles/Configrc b/direct/src/configfiles/Configrc new file mode 100644 index 0000000000..f744845e6e --- /dev/null +++ b/direct/src/configfiles/Configrc @@ -0,0 +1 @@ +model-path /beta/direct/install diff --git a/direct/src/configfiles/Sources.pp b/direct/src/configfiles/Sources.pp new file mode 100644 index 0000000000..218c937554 --- /dev/null +++ b/direct/src/configfiles/Sources.pp @@ -0,0 +1,2 @@ +#define INSTALL_DATA \ + direct.init Configrc diff --git a/direct/src/configfiles/direct.init b/direct/src/configfiles/direct.init new file mode 100644 index 0000000000..d8d302d2f2 --- /dev/null +++ b/direct/src/configfiles/direct.init @@ -0,0 +1,7 @@ +ATTACH panda +MODREL ETC_PATH etc +SEP PYTHONPATH : +MODREL PYTHONPATH lib +MODREL PYTHONPATH lib/py +MODREL PYTHONPATH src/all/python + diff --git a/direct/src/dcparse/Makefile.gnu b/direct/src/dcparse/Makefile.gnu new file mode 100644 index 0000000000..e8ccff4bb9 --- /dev/null +++ b/direct/src/dcparse/Makefile.gnu @@ -0,0 +1,34 @@ +OFILES = \ + dcParser.o dcLexer.o \ + dcAtomicField.o dcClass.o dcFile.o dcMolecularField.o \ + dcSubatomicType.o indent.o \ + dcparse.o + +# Cheesy dependencies. +HFILES = \ + dcAtomicField.h dcClass.h dcClassDescription.h dcFile.h \ + dcLexerDefs.h dcMolecularField.h dcParser.h dcParserDefs.h \ + dcSubatomicType.h dcbase.h indent.h + +dcparse : $(OFILES) + g++ -o $@ $(OFILES) + +dcParser.cxx : dcParser.yxx + bison -d --name-prefix=dcyy -o $@ $< + mv $@.h dcParser.h + +dcLexer.cxx : dcLexer.lxx + flex -Pdcyy -ot.lex $< + sed '/#include /d' t.lex >$@ + rm -f t.lex + +%.o: %.cxx $(HFILES) + g++ -c -g -Wall -o $@ $< + +clean: + rm -f *.o dcparse + +zip: + rm -f dcparse.zip + zip dcparse.zip Makefile.gnu *.cxx *.h *.lxx *.yxx *.dc \ + dc.dsp dc.dsw diff --git a/direct/src/dcparse/Sources.pp b/direct/src/dcparse/Sources.pp new file mode 100644 index 0000000000..5338bf00ec --- /dev/null +++ b/direct/src/dcparse/Sources.pp @@ -0,0 +1,29 @@ +#define OTHER_LIBS interrogatedb:c dconfig:c dtoolutil:c dtoolbase:c dtool:m +#define YACC_PREFIX dcyy + +#begin lib_target + #define TARGET dcparse + + #define SOURCES \ + dcAtomicField.cxx dcAtomicField.h dcClass.cxx dcClass.h \ + dcFile.cxx dcFile.h dcLexer.lxx dcLexerDefs.h \ + dcMolecularField.cxx dcMolecularField.h dcParser.yxx \ + dcParserDefs.h dcSubatomicType.cxx dcSubatomicType.h dcbase.h \ + indent.cxx indent.h + + #define EXTRA_DIST test.dc dc.dsp dc.dsw + + #define IGATESCAN all +#end lib_target + +#begin test_bin_target + #define TARGET dcparse + #define LOCAL_LIBS dcparse + #define OTHER_LIBS $[OTHER_LIBS] pystub + + #define SOURCES \ + dcparse.cxx + + #define EXTRA_DIST test.dc dc.dsp dc.dsw +#end test_bin_target + diff --git a/direct/src/dcparse/dc.dsp b/direct/src/dcparse/dc.dsp new file mode 100644 index 0000000000..5502433804 --- /dev/null +++ b/direct/src/dcparse/dc.dsp @@ -0,0 +1,176 @@ +# Microsoft Developer Studio Project File - Name="dc" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=dc - Win32 Debug +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "dc.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "dc.mak" CFG="dc - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "dc - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "dc - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "dc - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir "Release" +# PROP Intermediate_Dir "Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "dc - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir "Debug" +# PROP Intermediate_Dir "Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "dc - Win32 Release" +# Name "dc - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\dcAtomicField.cxx +# End Source File +# Begin Source File + +SOURCE=.\dcClass.cxx +# End Source File +# Begin Source File + +SOURCE=.\dcFile.cxx +# End Source File +# Begin Source File + +SOURCE=.\dcLexer.cxx +# End Source File +# Begin Source File + +SOURCE=.\dcMolecularField.cxx +# End Source File +# Begin Source File + +SOURCE=.\dcparse.cxx +# End Source File +# Begin Source File + +SOURCE=.\dcParser.cxx +# End Source File +# Begin Source File + +SOURCE=.\dcSubatomicType.cxx +# End Source File +# Begin Source File + +SOURCE=.\indent.cxx +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# Begin Source File + +SOURCE=.\dcAtomicField.h +# End Source File +# Begin Source File + +SOURCE=.\dcbase.h +# End Source File +# Begin Source File + +SOURCE=.\dcClass.h +# End Source File +# Begin Source File + +SOURCE=.\dcClassDescription.h +# End Source File +# Begin Source File + +SOURCE=.\dcFile.h +# End Source File +# Begin Source File + +SOURCE=.\dcLexerDefs.h +# End Source File +# Begin Source File + +SOURCE=.\dcMolecularField.h +# End Source File +# Begin Source File + +SOURCE=.\dcParser.h +# End Source File +# Begin Source File + +SOURCE=.\dcParserDefs.h +# End Source File +# Begin Source File + +SOURCE=.\dcSubatomicType.h +# End Source File +# Begin Source File + +SOURCE=.\indent.h +# End Source File +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/direct/src/dcparse/dc.dsw b/direct/src/dcparse/dc.dsw new file mode 100644 index 0000000000..19535d4c6c --- /dev/null +++ b/direct/src/dcparse/dc.dsw @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "dc"=.\dc.dsp - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/direct/src/dcparse/dcAtomicField.cxx b/direct/src/dcparse/dcAtomicField.cxx new file mode 100644 index 0000000000..e08b24be2f --- /dev/null +++ b/direct/src/dcparse/dcAtomicField.cxx @@ -0,0 +1,240 @@ +// Filename: dcAtomicField.cxx +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "dcAtomicField.h" +#include "indent.h" + +#include + +ostream & +operator << (ostream &out, const DCAtomicField::ElementType &et) { + out << et._type; + if (et._divisor != 1) { + out << " / " << et._divisor; + } + return out; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::get_number +// Access: Public +// Description: Returns a unique index number associated with this +// field. This is defined implicitly when the .dc +// file(s) are read. +//////////////////////////////////////////////////////////////////// +int DCAtomicField:: +get_number() const { + return _number; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::get_name +// Access: Public +// Description: Returns the name of this field. +//////////////////////////////////////////////////////////////////// +string DCAtomicField:: +get_name() const { + return _name; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::get_num_elements +// Access: Public +// Description: Returns the number of elements of the atomic field. +//////////////////////////////////////////////////////////////////// +int DCAtomicField:: +get_num_elements() const { + return _elements.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::get_element_type +// Access: Public +// Description: Returns the numeric type of the nth element of the +// field. +//////////////////////////////////////////////////////////////////// +DCSubatomicType DCAtomicField:: +get_element_type(int n) const { + assert(n >= 0 && n < (int)_elements.size()); + return _elements[n]._type; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::get_element_divisor +// Access: Public +// Description: Returns the divisor associated with the nth element +// of the field. This implements an implicit +// fixed-point system; floating-point values are to be +// multiplied by this value before encoding into a +// packet, and divided by this number after decoding. +//////////////////////////////////////////////////////////////////// +int DCAtomicField:: +get_element_divisor(int n) const { + assert(n >= 0 && n < (int)_elements.size()); + return _elements[n]._divisor; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::is_required +// Access: Public +// Description: Returns true if the "required" flag is set for this +// field, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCAtomicField:: +is_required() const { + return (_flags & F_required) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::is_broadcast +// Access: Public +// Description: Returns true if the "broadcast" flag is set for this +// field, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCAtomicField:: +is_broadcast() const { + return (_flags & F_broadcast) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::is_p2p +// Access: Public +// Description: Returns true if the "p2p" flag is set for this +// field, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCAtomicField:: +is_p2p() const { + return (_flags & F_p2p) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::is_ram +// Access: Public +// Description: Returns true if the "ram" flag is set for this +// field, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCAtomicField:: +is_ram() const { + return (_flags & F_ram) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::is_db +// Access: Public +// Description: Returns true if the "db" flag is set for this +// field, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCAtomicField:: +is_db() const { + return (_flags & F_db) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::is_clsend +// Access: Public +// Description: Returns true if the "clsend" flag is set for this +// field, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCAtomicField:: +is_clsend() const { + return (_flags & F_clsend) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::is_clrecv +// Access: Public +// Description: Returns true if the "clrecv" flag is set for this +// field, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCAtomicField:: +is_clrecv() const { + return (_flags & F_clrecv) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::is_aisend +// Access: Public +// Description: Returns true if the "aisend" flag is set for this +// field, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCAtomicField:: +is_aisend() const { + return (_flags & F_aisend) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::is_airecv +// Access: Public +// Description: Returns true if the "airecv" flag is set for this +// field, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCAtomicField:: +is_airecv() const { + return (_flags & F_airecv) != 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +DCAtomicField:: +DCAtomicField() { + _number = 0; + _flags = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCAtomicField::write +// Access: Public +// Description: Generates a parseable description of the object to +// the indicated output stream. +//////////////////////////////////////////////////////////////////// +void DCAtomicField:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) + << _name << "("; + + if (!_elements.empty()) { + Elements::const_iterator ei = _elements.begin(); + out << (*ei); + ++ei; + while (ei != _elements.end()) { + out << ", " << (*ei); + ++ei; + } + } + out << ")"; + + if ((_flags & F_required) != 0) { + out << " required"; + } + if ((_flags & F_broadcast) != 0) { + out << " broadcast"; + } + if ((_flags & F_p2p) != 0) { + out << " p2p"; + } + if ((_flags & F_ram) != 0) { + out << " ram"; + } + if ((_flags & F_db) != 0) { + out << " db"; + } + if ((_flags & F_clsend) != 0) { + out << " clsend"; + } + if ((_flags & F_clrecv) != 0) { + out << " clrecv"; + } + if ((_flags & F_aisend) != 0) { + out << " aisend"; + } + if ((_flags & F_airecv) != 0) { + out << " airecv"; + } + + out << "; // atomic " << _number << "\n"; +} diff --git a/direct/src/dcparse/dcAtomicField.h b/direct/src/dcparse/dcAtomicField.h new file mode 100644 index 0000000000..331595635e --- /dev/null +++ b/direct/src/dcparse/dcAtomicField.h @@ -0,0 +1,74 @@ +// Filename: dcAtomicField.h +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef DCATOMICFIELD_H +#define DCATOMICFIELD_H + +#include "dcbase.h" +#include "dcSubatomicType.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Class : DCAtomicField +// Description : A single atomic field of a Distributed Class, as read +// from a .dc file. This defines an interface to the +// Distributed Class, and is always implemented as a +// remote procedure method. +//////////////////////////////////////////////////////////////////// +class DCAtomicField { +PUBLISHED: + int get_number() const; + string get_name() const; + + int get_num_elements() const; + DCSubatomicType get_element_type(int n) const; + int get_element_divisor(int n) const; + + bool is_required() const; + bool is_broadcast() const; + bool is_p2p() const; + bool is_ram() const; + bool is_db() const; + bool is_clsend() const; + bool is_clrecv() const; + bool is_aisend() const; + bool is_airecv() const; + +public: + DCAtomicField(); + void write(ostream &out, int indent_level = 0) const; + +public: + // These members define the primary interface to the atomic field + // definition as read from the file. + int _number; + string _name; + + class ElementType { + public: + DCSubatomicType _type; + int _divisor; + }; + + typedef vector Elements; + Elements _elements; + + enum Flags { + F_required = 0x0001, + F_broadcast = 0x0002, + F_p2p = 0x0004, + F_ram = 0x0008, + F_db = 0x0010, + F_clsend = 0x0020, + F_clrecv = 0x0040, + F_aisend = 0x0080, + F_airecv = 0x0100 + }; + + int _flags; // A bitmask union of any of the above values. +}; + +#endif diff --git a/direct/src/dcparse/dcClass.cxx b/direct/src/dcparse/dcClass.cxx new file mode 100644 index 0000000000..9a1b4bc760 --- /dev/null +++ b/direct/src/dcparse/dcClass.cxx @@ -0,0 +1,340 @@ +// Filename: dcClass.cxx +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "dcClass.h" +#include "indent.h" + +#include + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_number +// Access: Public +// Description: Returns a unique index number associated with this +// class. This is defined implicitly when the .dc +// file(s) are read. +//////////////////////////////////////////////////////////////////// +int DCClass:: +get_number() const { + return _number; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_name +// Access: Public +// Description: Returns the name of this class. +//////////////////////////////////////////////////////////////////// +string DCClass:: +get_name() const { + return _name; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::has_parent +// Access: Public +// Description: Returns true if this class inherits from some other +// class, false if it does not. +//////////////////////////////////////////////////////////////////// +bool DCClass:: +has_parent() const { + return !_parents.empty(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_parent +// Access: Public +// Description: Returns the parent class this class inherits from, if +// any. It is an error to call this unless has_parent() +// returned true. +//////////////////////////////////////////////////////////////////// +DCClass *DCClass:: +get_parent() const { + assert(has_parent()); + return _parents.front(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_num_atomics +// Access: Public +// Description: Returns the number of atomic fields defined directly +// in this class, ignoring inheritance. +//////////////////////////////////////////////////////////////////// +int DCClass:: +get_num_atomics() { + return _atomic_fields.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_atomic +// Access: Public +// Description: Returns the nth atomic field in the class. This is +// not necessarily the field with index n; this is the +// nth field defined in the class directly, ignoring +// inheritance. +//////////////////////////////////////////////////////////////////// +DCAtomicField *DCClass:: +get_atomic(int n) { + assert(n >= 0 && n < (int)_atomic_fields.size()); + return _atomic_fields[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_atomic_by_name +// Access: Public +// Description: Returns a pointer to the DCAtomicField that shares +// the indicated name. If the named field is not found +// in the current class, the parent classes will be +// searched, so the value returned may not actually be a +// field within this class. Returns NULL if there is no +// such field defined. +//////////////////////////////////////////////////////////////////// +DCAtomicField *DCClass:: +get_atomic_by_name(const string &name) { + AtomicsByName::const_iterator ni; + ni = _atomics_by_name.find(name); + if (ni != _atomics_by_name.end()) { + return (*ni).second; + } + + // We didn't have such a field, so check our parents. + Parents::iterator pi; + for (pi = _parents.begin(); pi != _parents.end(); ++pi) { + DCAtomicField *result = (*pi)->get_atomic_by_name(name); + if (result != (DCAtomicField *)NULL) { + return result; + } + } + + // Nobody knew what this field is. + return (DCAtomicField *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_num_inherited_atomics +// Access: Public +// Description: Returns the total number of atomic fields defined in +// this class and all ancestor classes. +//////////////////////////////////////////////////////////////////// +int DCClass:: +get_num_inherited_atomics() { + if (!_parents.empty()) { + // This won't work for multiple dclass inheritance. + return _parents.front()->get_num_atomics() + get_num_atomics(); + } + return get_num_atomics(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_inherited_atomic +// Access: Public +// Description: Returns the nth atomic field in the class and all of +// its ancestors. This *is* the field corresponding to +// the given index number, since the fields are ordered +// consecutively beginning at the earliest inherited +// fields. +//////////////////////////////////////////////////////////////////// +DCAtomicField *DCClass:: +get_inherited_atomic(int n) { + if (!_parents.empty()) { + // This won't work for multiple dclass inheritance. + n -= _parents.front()->get_num_inherited_atomics(); + } + return get_atomic(n); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_num_moleculars +// Access: Public +// Description: Returns the number of molecular fields defined directly +// in this class, ignoring inheritance. +//////////////////////////////////////////////////////////////////// +int DCClass:: +get_num_moleculars() { + return _molecular_fields.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_molecular +// Access: Public +// Description: Returns the nth molecular field in the class. This is +// not necessarily the field with index n; this is the +// nth field defined in the class directly, ignoring +// inheritance. +//////////////////////////////////////////////////////////////////// +DCMolecularField *DCClass:: +get_molecular(int n) { + assert(n >= 0 && n < (int)_molecular_fields.size()); + return _molecular_fields[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_molecular_by_name +// Access: Public +// Description: Returns a pointer to the DCMolecularField that shares +// the indicated name. If the named field is not found +// in the current class, the parent classes will be +// searched, so the value returned may not actually be a +// field within this class. Returns NULL if there is no +// such field defined. +//////////////////////////////////////////////////////////////////// +DCMolecularField *DCClass:: +get_molecular_by_name(const string &name) { + MolecularsByName::const_iterator ni; + ni = _moleculars_by_name.find(name); + if (ni != _moleculars_by_name.end()) { + return (*ni).second; + } + + // We didn't have such a field, so check our parents. + Parents::iterator pi; + for (pi = _parents.begin(); pi != _parents.end(); ++pi) { + DCMolecularField *result = (*pi)->get_molecular_by_name(name); + if (result != (DCMolecularField *)NULL) { + return result; + } + } + + // Nobody knew what this field is. + return (DCMolecularField *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_num_inherited_moleculars +// Access: Public +// Description: Returns the total number of molecular fields defined in +// this class and all ancestor classes. +//////////////////////////////////////////////////////////////////// +int DCClass:: +get_num_inherited_moleculars() { + if (!_parents.empty()) { + // This won't work for multiple dclass inheritance. + return _parents.front()->get_num_moleculars() + get_num_moleculars(); + } + return get_num_moleculars(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::get_inherited_molecular +// Access: Public +// Description: Returns the nth molecular field in the class and all of +// its ancestors. This *is* the field corresponding to +// the given index number, since the fields are ordered +// consecutively beginning at the earliest inherited +// fields. +//////////////////////////////////////////////////////////////////// +DCMolecularField *DCClass:: +get_inherited_molecular(int n) { + if (!_parents.empty()) { + // This won't work for multiple dclass inheritance. + n -= _parents.front()->get_num_inherited_moleculars(); + } + return get_molecular(n); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +DCClass:: +DCClass() { +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +DCClass:: +~DCClass() { + AtomicFields::iterator ai; + for (ai = _atomic_fields.begin(); ai != _atomic_fields.end(); ++ai) { + delete (*ai); + } + MolecularFields::iterator mi; + for (mi = _molecular_fields.begin(); mi != _molecular_fields.end(); ++mi) { + delete (*mi); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::write +// Access: Public +// Description: Generates a parseable description of the object to +// the indicated output stream. +//////////////////////////////////////////////////////////////////// +void DCClass:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) + << "dclass " << _name; + + if (!_parents.empty()) { + Parents::const_iterator pi = _parents.begin(); + out << " : " << (*pi)->_name; + ++pi; + while (pi != _parents.end()) { + out << ", " << (*pi)->_name; + ++pi; + } + } + out << " { // index " << _number << "\n"; + + AtomicFields::const_iterator ai; + for (ai = _atomic_fields.begin(); ai != _atomic_fields.end(); ++ai) { + (*ai)->write(out, indent_level + 2); + } + + MolecularFields::const_iterator mi; + for (mi = _molecular_fields.begin(); mi != _molecular_fields.end(); ++mi) { + (*mi)->write(out, indent_level + 2); + } + + indent(out, indent_level) << "};\n"; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::add_field +// Access: Public +// Description: Adds the newly-allocated atomic field to the class. +// The class becomes the owner of the pointer and will +// delete it when it destructs. Returns true if the +// field is successfully added, or false if there was a +// name conflict. +//////////////////////////////////////////////////////////////////// +bool DCClass:: +add_field(DCAtomicField *field) { + bool inserted = _atomics_by_name.insert + (AtomicsByName::value_type(field->_name, field)).second; + + if (!inserted) { + return false; + } + + field->_number = get_num_inherited_atomics(); + _atomic_fields.push_back(field); + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCClass::add_field +// Access: Public +// Description: Adds the newly-allocated molecular field to the class. +// The class becomes the owner of the pointer and will +// delete it when it destructs. Returns true if the +// field is successfully added, or false if there was a +// name conflict. +//////////////////////////////////////////////////////////////////// +bool DCClass:: +add_field(DCMolecularField *field) { + bool inserted = _moleculars_by_name.insert + (MolecularsByName::value_type(field->_name, field)).second; + + if (!inserted) { + return false; + } + + field->_number = get_num_inherited_moleculars(); + _molecular_fields.push_back(field); + return true; +} diff --git a/direct/src/dcparse/dcClass.h b/direct/src/dcparse/dcClass.h new file mode 100644 index 0000000000..4a79255ae9 --- /dev/null +++ b/direct/src/dcparse/dcClass.h @@ -0,0 +1,76 @@ +// Filename: dcClass.h +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef DCCLASS_H +#define DCCLASS_H + +#include "dcbase.h" +#include "dcAtomicField.h" +#include "dcMolecularField.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : DCClass +// Description : Defines a particular DistributedClass as read from an +// input .dc file. +//////////////////////////////////////////////////////////////////// +class DCClass { +PUBLISHED: + int get_number() const; + string get_name() const; + + bool has_parent() const; + DCClass *get_parent() const; + + int get_num_atomics(); + DCAtomicField *get_atomic(int n); + DCAtomicField *get_atomic_by_name(const string &name); + + int get_num_inherited_atomics(); + DCAtomicField *get_inherited_atomic(int n); + + int get_num_moleculars(); + DCMolecularField *get_molecular(int n); + DCMolecularField *get_molecular_by_name(const string &name); + + int get_num_inherited_moleculars(); + DCMolecularField *get_inherited_molecular(int n); + +public: + DCClass(); + ~DCClass(); + + void write(ostream &out, int indent_level = 0) const; + bool add_field(DCAtomicField *field); + bool add_field(DCMolecularField *field); + +public: + // These members define the primary interface to the distributed + // class as read from the file. + int _number; + string _name; + + typedef vector Parents; + Parents _parents; + + typedef vector AtomicFields; + AtomicFields _atomic_fields; + + typedef vector MolecularFields; + MolecularFields _molecular_fields; + +public: + // These members are built up during parsing for the convenience of + // the parser. + typedef map AtomicsByName; + AtomicsByName _atomics_by_name; + + typedef map MolecularsByName; + MolecularsByName _moleculars_by_name; +}; + +#endif diff --git a/direct/src/dcparse/dcFile.cxx b/direct/src/dcparse/dcFile.cxx new file mode 100644 index 0000000000..b729cb4db1 --- /dev/null +++ b/direct/src/dcparse/dcFile.cxx @@ -0,0 +1,192 @@ +// Filename: dcFile.cxx +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "dcFile.h" +#include "dcParserDefs.h" +#include "dcLexerDefs.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +DCFile:: +DCFile() { +} + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::Destructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +DCFile:: +~DCFile() { + Classes::iterator ci; + for (ci = _classes.begin(); ci != _classes.end(); ++ci) { + delete (*ci); + } +} + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::read +// Access: Public +// Description: Opens and reads the indicated .dc file by name. The +// distributed classes defined in the file will be +// appended to the set of distributed classes already +// recorded, if any. +// +// Returns true if the file is successfully read, false +// if there was an error (in which case the file might +// have been partially read). +//////////////////////////////////////////////////////////////////// +bool DCFile:: +read(const string &filename) { + ifstream in(filename.c_str()); + + if (!in) { + cerr << "Cannot open " << filename << " for reading.\n"; + return false; + } + + return read(in, filename); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::read +// Access: Public +// Description: Parses the already-opened input stream for +// distributed class descriptions. The filename +// parameter is optional and is only used when reporting +// errors. +// +// The distributed classes defined in the file will be +// appended to the set of distributed classes already +// recorded, if any. +// +// Returns true if the file is successfully read, false +// if there was an error (in which case the file might +// have been partially read). +//////////////////////////////////////////////////////////////////// +bool DCFile:: +read(istream &in, const string &filename) { + dc_init_parser(in, filename, *this); + dcyyparse(); + dc_cleanup_parser(); + + return (dc_error_count() == 0); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::write +// Access: Public +// Description: Opens the indicated filename for output and writes a +// parseable description of all the known distributed +// classes to the file. +// +// Returns true if the description is successfully +// written, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCFile:: +write(const string &filename) const { + ofstream out(filename.c_str()); + + if (!out) { + cerr << "Can't open " << filename << " for output.\n"; + return false; + } + return write(out, filename); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::write +// Access: Public +// Description: Writes a parseable description of all the known +// distributed classes to the file. The filename +// parameter is optional and is only used when reporting +// errors. +// +// Returns true if the description is successfully +// written, false otherwise. +//////////////////////////////////////////////////////////////////// +bool DCFile:: +write(ostream &out, const string &filename) const { + Classes::const_iterator ci; + for (ci = _classes.begin(); ci != _classes.end(); ++ci) { + (*ci)->write(out); + out << "\n"; + } + + if (out.fail()) { + cerr << "I/O error writing " << filename << ".\n"; + return false; + } + + return true; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::get_num_classes +// Access: Public +// Description: Returns the number of classes read from the .dc +// file(s). +//////////////////////////////////////////////////////////////////// +int DCFile:: +get_num_classes() { + return _classes.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::get_class +// Access: Public +// Description: Returns the nth class read from the .dc file(s). +//////////////////////////////////////////////////////////////////// +DCClass *DCFile:: +get_class(int n) { + assert(n >= 0 && n < (int)_classes.size()); + return _classes[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::get_class_by_name +// Access: Public +// Description: Returns the class that has the indicated name, or +// NULL if there is no such class. +//////////////////////////////////////////////////////////////////// +DCClass *DCFile:: +get_class_by_name(const string &name) { + ClassesByName::const_iterator ni; + ni = _classes_by_name.find(name); + if (ni != _classes_by_name.end()) { + return (*ni).second; + } + + return (DCClass *)NULL; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCFile::add_class +// Access: Public +// Description: Adds the newly-allocated distributed class definition +// to the file. The DCFile becomes the owner of the +// pointer and will delete it when it destructs. +// Returns true if the class is successfully added, or +// false if there was a name conflict. +//////////////////////////////////////////////////////////////////// +bool DCFile:: +add_class(DCClass *dclass) { + bool inserted = _classes_by_name.insert + (ClassesByName::value_type(dclass->_name, dclass)).second; + + if (!inserted) { + return false; + } + + dclass->_number = get_num_classes(); + _classes.push_back(dclass); + return true; +} diff --git a/direct/src/dcparse/dcFile.h b/direct/src/dcparse/dcFile.h new file mode 100644 index 0000000000..f5b4d36cfb --- /dev/null +++ b/direct/src/dcparse/dcFile.h @@ -0,0 +1,53 @@ +// Filename: dcFile.h +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef DCFILE_H +#define DCFILE_H + +#include "dcbase.h" +#include "dcClass.h" + +#include +#include + +//////////////////////////////////////////////////////////////////// +// Class : DCFile +// Description : Represents the complete list of Distributed Class +// descriptions as read from a .dc file. +//////////////////////////////////////////////////////////////////// +class DCFile { +PUBLISHED: + DCFile(); + ~DCFile(); + + bool read(const string &filename); + bool read(istream &in, const string &filename = string()); + + bool write(const string &filename) const; + bool write(ostream &out, const string &filename = string()) const; + + int get_num_classes(); + DCClass *get_class(int n); + + DCClass *get_class_by_name(const string &name); + +public: + bool add_class(DCClass *dclass); + +public: + // This vector is the primary interface to the distributed classes + // read from the file. + typedef vector Classes; + Classes _classes; + +public: + // This map is built up during parsing for the convenience of the parser. + typedef map ClassesByName; + ClassesByName _classes_by_name; +}; + +#endif + + diff --git a/direct/src/dcparse/dcLexer.lxx b/direct/src/dcparse/dcLexer.lxx new file mode 100644 index 0000000000..c73b9eb099 --- /dev/null +++ b/direct/src/dcparse/dcLexer.lxx @@ -0,0 +1,415 @@ +/* +// Filename: dcLexer.lxx +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// +*/ + +%{ +#include "dcLexerDefs.h" +#include "dcParserDefs.h" +#include "dcParser.h" +#include "indent.h" + +#include + +static int yyinput(void); // declared by flex. +extern "C" int dcyywrap(); + +//////////////////////////////////////////////////////////////////// +// Static variables +//////////////////////////////////////////////////////////////////// + +// We'll increment line_number and col_number as we parse the file, so +// that we can report the position of an error. +static int line_number = 0; +static int col_number = 0; + +// current_line holds as much of the current line as will fit. Its +// only purpose is for printing it out to report an error to the user. +static const int max_error_width = 1024; +static char current_line[max_error_width + 1]; + +static int error_count = 0; +static int warning_count = 0; + +// This is the pointer to the current input stream. +static istream *inp = NULL; + +// This is the name of the dc file we're parsing. We keep it so we +// can print it out for error messages. +static string dc_filename; + + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the lexer. +//////////////////////////////////////////////////////////////////// + +void +dc_init_lexer(istream &in, const string &filename) { + inp = ∈ + dc_filename = filename; + line_number = 0; + col_number = 0; + error_count = 0; + warning_count = 0; +} + +int +dc_error_count() { + return error_count; +} + +int +dc_warning_count() { + return warning_count; +} + + +//////////////////////////////////////////////////////////////////// +// Internal support functions. +//////////////////////////////////////////////////////////////////// + +int +dcyywrap(void) { + return 1; +} + +void +dcyyerror(const string &msg) { + cerr << "\nError"; + if (!dc_filename.empty()) { + cerr << " in " << dc_filename; + } + cerr + << " at line " << line_number << ", column " << col_number << ":\n" + << current_line << "\n"; + indent(cerr, col_number-1) + << "^\n" << msg << "\n\n"; + + error_count++; +} + +void +dcyywarning(const string &msg) { + cerr << "\nWarning"; + if (!dc_filename.empty()) { + cerr << " in " << dc_filename; + } + cerr + << " at line " << line_number << ", column " << col_number << ":\n" + << current_line << "\n"; + indent(cerr, col_number-1) + << "^\n" << msg << "\n\n"; + + warning_count++; +} + +// Now define a function to take input from an istream instead of a +// stdio FILE pointer. This is flex-specific. +static void +input_chars(char *buffer, int &result, int max_size) { + assert(inp != NULL); + if (*inp) { + inp->read(buffer, max_size); + result = inp->gcount(); + + if (line_number == 0) { + // This is a special case. If we are reading the very first bit + // from the stream, copy it into the current_line array. This + // is because the \n.* rule below, which fills current_line + // normally, doesn't catch the first line. + strncpy(current_line, yytext, max_error_width); + current_line[max_error_width] = '\0'; + line_number++; + col_number = 0; + + // Truncate it at the newline. + char *end = strchr(current_line, '\n'); + if (end != NULL) { + *end = '\0'; + } + } + + } else { + // End of file or I/O error. + result = 0; + } +} +#undef YY_INPUT +#define YY_INPUT(buffer, result, max_size) input_chars(buffer, result, max_size) + +// read_char reads and returns a single character, incrementing the +// supplied line and column numbers as appropriate. A convenience +// function for the scanning functions below. +static int +read_char(int &line, int &col) { + int c = yyinput(); + if (c == '\n') { + line++; + col = 0; + } else { + col++; + } + return c; +} + +// scan_quoted_string reads a string delimited by quotation marks and +// returns it. +static string +scan_quoted_string() { + string result; + + // We don't touch the current line number and column number during + // scanning, so that if we detect an error while scanning the string + // (e.g. an unterminated string), we'll report the error as + // occurring at the start of the string, not at the end--somewhat + // more convenient for the user. + + // Instead of adjusting the global line_number and col_number + // variables, we'll operate on our own local variables for the + // interim. + int line = line_number; + int col = col_number; + + int c; + c = read_char(line, col); + while (c != '"' && c != EOF) { + result += c; + c = read_char(line, col); + } + + if (c == EOF) { + dcyyerror("This quotation mark is unterminated."); + } + + line_number = line; + col_number = col; + + return result; +} + +// eat_c_comment scans past all characters up until the first */ +// encountered. +static void +eat_c_comment() { + // As above, we'll operate on our own local copies of line_number + // and col_number within this function. + + int line = line_number; + int col = col_number; + + int c, last_c; + + last_c = '\0'; + c = read_char(line, col); + while (c != EOF && !(last_c == '*' && c == '/')) { + if (last_c == '/' && c == '*') { + dcyywarning("This comment contains a nested /* symbol--possibly unclosed?"); + } + last_c = c; + c = read_char(line, col); + } + + if (c == EOF) { + dcyyerror("This comment marker is unclosed."); + } + + line_number = line; + col_number = col; +} + + + +// accept() is called below as each piece is pulled off and +// accepted by the lexer; it increments the current column number. +inline void accept() { + col_number += yyleng; +} + +%} + +INTEGERNUM ([+-]?([0-9]+)) +REALNUM ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?) + +%% + +%{ +%} + +\n.* { + // New line. Save a copy of the line so we can print it out for the + // benefit of the user in case we get an error. + + strncpy(current_line, yytext+1, max_error_width); + current_line[max_error_width] = '\0'; + line_number++; + col_number=0; + + // Return the whole line to the lexer, except the newline character, + // which we eat. + yyless(1); +} + +[ \t] { + // Eat whitespace. + accept(); +} + +"//".* { + // Eat C++-style comments. + accept(); +} + +"/*" { + // Eat C-style comments. + accept(); + eat_c_comment(); +} + + +"dclass" { + accept(); + return KW_DCLASS; +} + +"int8" { + accept(); + return KW_INT8; +} + +"int16" { + accept(); + return KW_INT16; +} + +"int32" { + accept(); + return KW_INT32; +} + +"int64" { + accept(); + return KW_INT64; +} + +"uint8" { + accept(); + return KW_UINT8; +} + +"uint16" { + accept(); + return KW_UINT16; +} + +"uint32" { + accept(); + return KW_UINT32; +} + +"uint64" { + accept(); + return KW_UINT64; +} + +"float64" { + accept(); + return KW_FLOAT64; +} + +"string" { + accept(); + return KW_STRING; +} + +mol[0-9]+ { + // A molecular keyword. + accept(); + dcyylval.u.integer = atoi(dcyytext + 3); + dcyylval.str = yytext; + return KW_MOL; +} + +"required" { + accept(); + return KW_REQUIRED; +} + +"broadcast" { + accept(); + return KW_BROADCAST; +} + +"p2p" { + accept(); + return KW_P2P; +} + +"ram" { + accept(); + return KW_RAM; +} + +"db" { + accept(); + return KW_DB; +} + +"clsend" { + accept(); + return KW_CLSEND; +} + +"clrecv" { + accept(); + return KW_CLRECV; +} + +"aisend" { + accept(); + return KW_AISEND; +} + +"airecv" { + accept(); + return KW_AIRECV; +} + +{INTEGERNUM} { + // An integer number. + accept(); + dcyylval.u.integer = atoi(dcyytext); + dcyylval.str = yytext; + return INTEGER; +} + +{REALNUM} { + // A floating-point number. + accept(); + dcyylval.u.real = atof(dcyytext); + dcyylval.str = yytext; + return REAL; +} + +["] { + // Quoted string. + accept(); + dcyylval.str = scan_quoted_string(); + return STRING; +} + +[A-Za-z_][A-Za-z_0-9]* { + // Identifier. + accept(); + dcyylval.str = yytext; + return IDENTIFIER; +} + + +. { + // Send any other printable character as itself. + accept(); + return dcyytext[0]; +} + diff --git a/direct/src/dcparse/dcLexerDefs.h b/direct/src/dcparse/dcLexerDefs.h new file mode 100644 index 0000000000..47c6ce8484 --- /dev/null +++ b/direct/src/dcparse/dcLexerDefs.h @@ -0,0 +1,20 @@ +// Filename: dcLexerDefs.h +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef DCLEXERDEFS_H +#define DCLEXERDEFS_H + +#include "dcbase.h" + +void dc_init_lexer(istream &in, const string &filename); +int dc_error_count(); +int dc_warning_count(); + +void dcyyerror(const string &msg); +void dcyywarning(const string &msg); + +int dcyylex(); + +#endif diff --git a/direct/src/dcparse/dcMolecularField.cxx b/direct/src/dcparse/dcMolecularField.cxx new file mode 100644 index 0000000000..4674da603e --- /dev/null +++ b/direct/src/dcparse/dcMolecularField.cxx @@ -0,0 +1,92 @@ +// Filename: dcMolecularField.cxx +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "dcMolecularField.h" +#include "dcAtomicField.h" +#include "indent.h" + +#include + + +//////////////////////////////////////////////////////////////////// +// Function: DCMolecularField::get_number +// Access: Public +// Description: Returns a unique index number associated with this +// field. This is defined implicitly when the .dc +// file(s) are read. +//////////////////////////////////////////////////////////////////// +int DCMolecularField:: +get_number() const { + return _number; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCMolecularField::get_name +// Access: Public +// Description: Returns the name of this field. +//////////////////////////////////////////////////////////////////// +string DCMolecularField:: +get_name() const { + return _name; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCMolecularField::get_num_atomics +// Access: Public +// Description: Returns the number of atomic fields that make up this +// molecular field. +//////////////////////////////////////////////////////////////////// +int DCMolecularField:: +get_num_atomics() const { + return _fields.size(); +} + +//////////////////////////////////////////////////////////////////// +// Function: DCMolecularField::get_atomic +// Access: Public +// Description: Returns the nth atomic field that makes up this +// molecular field. This may or may not be a field of +// this particular class; it might be defined in a +// parent class. +//////////////////////////////////////////////////////////////////// +DCAtomicField *DCMolecularField:: +get_atomic(int n) const { + assert(n >= 0 && n < (int)_fields.size()); + return _fields[n]; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCMolecularField::Constructor +// Access: Public +// Description: +//////////////////////////////////////////////////////////////////// +DCMolecularField:: +DCMolecularField() { + _number = 0; +} + +//////////////////////////////////////////////////////////////////// +// Function: DCMolecularField::write +// Access: Public +// Description: Generates a parseable description of the object to +// the indicated output stream. +//////////////////////////////////////////////////////////////////// +void DCMolecularField:: +write(ostream &out, int indent_level) const { + indent(out, indent_level) << _name; + + if (!_fields.empty()) { + Fields::const_iterator fi = _fields.begin(); + out << " : " << (*fi)->_name; + ++fi; + while (fi != _fields.end()) { + out << ", " << (*fi)->_name; + ++fi; + } + } + + out << "; // molecular " << _number << "\n"; +} + diff --git a/direct/src/dcparse/dcMolecularField.h b/direct/src/dcparse/dcMolecularField.h new file mode 100644 index 0000000000..8d4831467d --- /dev/null +++ b/direct/src/dcparse/dcMolecularField.h @@ -0,0 +1,46 @@ +// Filename: dcMolecularField.h +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef DCMOLECULARFIELD_H +#define DCMOLECULARFIELD_H + +#include "dcbase.h" + +#include + +class DCAtomicField; + +//////////////////////////////////////////////////////////////////// +// Class : DCMolecularField +// Description : A single molecular field of a Distributed Class, as +// read from a .dc file. This represents a combination +// of two or more related atomic fields, that will often +// be treated as a unit. +//////////////////////////////////////////////////////////////////// +class DCMolecularField { +PUBLISHED: + int get_number() const; + string get_name() const; + + int get_num_atomics() const; + DCAtomicField *get_atomic(int n) const; + +public: + DCMolecularField(); + void write(ostream &out, int indent_level = 0) const; + +public: + // These members define the primary interface to the molecular field + // definition as read from the file. + int _number; + string _name; + + typedef vector Fields; + Fields _fields; +}; + +#endif + + diff --git a/direct/src/dcparse/dcParser.yxx b/direct/src/dcparse/dcParser.yxx new file mode 100644 index 0000000000..4fe3a8d476 --- /dev/null +++ b/direct/src/dcparse/dcParser.yxx @@ -0,0 +1,295 @@ +// Filename: dcParser.yxx +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +%{ +#include "dcLexerDefs.h" +#include "dcParserDefs.h" +#include "dcFile.h" +#include "dcClass.h" +#include "dcAtomicField.h" + +// Because our token type contains objects of type string, which +// require correct copy construction (and not simply memcpying), we +// cannot use bison's built-in auto-stack-grow feature. As an easy +// solution, we ensure here that we have enough yacc stack to start +// with, and that it doesn't ever try to grow. +#define YYINITDEPTH 1000 +#define YYMAXDEPTH 1000 + +static DCFile *dc_file = (DCFile *)NULL; +static DCClass *current_class = (DCClass *)NULL; +static DCAtomicField *current_atomic = (DCAtomicField *)NULL; +static DCMolecularField *current_molecular = (DCMolecularField *)NULL; + +//////////////////////////////////////////////////////////////////// +// Defining the interface to the parser. +//////////////////////////////////////////////////////////////////// + +void +dc_init_parser(istream &in, const string &filename, DCFile &file) { + dc_file = &file; + dc_init_lexer(in, filename); +} + +void +dc_cleanup_parser() { + dc_file = (DCFile *)NULL; +} + +%} + +%token INTEGER +%token REAL +%token STRING IDENTIFIER + +%token KW_DCLASS + +%token KW_INT8 +%token KW_INT16 +%token KW_INT32 +%token KW_INT64 +%token KW_UINT8 +%token KW_UINT16 +%token KW_UINT32 +%token KW_UINT64 +%token KW_FLOAT64 +%token KW_STRING + +%token KW_MOL + +%token KW_REQUIRED +%token KW_BROADCAST +%token KW_P2P +%token KW_RAM +%token KW_DB +%token KW_CLSEND +%token KW_CLRECV +%token KW_AISEND +%token KW_AIRECV + +%type dclass_name +%type atomic_name +%type type_token + +%% + +dc: + empty + | dc ';' + | dc dclass + ; + +dclass: + KW_DCLASS IDENTIFIER +{ + current_class = new DCClass; + current_class->_name = $2; + if (!dc_file->add_class(current_class)) { + yyerror("Duplicate class name: " + current_class->_name); + } +} + dclass_derivation '{' dclass_fields '}' + ; + +dclass_name: + IDENTIFIER +{ + DCFile::ClassesByName::const_iterator ni; + ni = dc_file->_classes_by_name.find($1); + if (ni == dc_file->_classes_by_name.end()) { + yyerror("Unknown class: " + $1); + $$ = (DCClass *)NULL; + } else { + $$ = (*ni).second; + } +} + +dclass_derivation: + empty + | ':' base_list + ; + +base_list: + dclass_name +{ + if ($1 != (DCClass *)NULL) { + current_class->_parents.push_back($1); + } +} + | base_list ',' dclass_name +{ + if ($3 != (DCClass *)NULL) { + current_class->_parents.push_back($3); + } +} + ; + +dclass_fields: + empty + | dclass_fields ';' + | dclass_fields atomic_field + | dclass_fields molecular_field + ; + +atomic_field: + IDENTIFIER '(' +{ + current_atomic = new DCAtomicField; + current_atomic->_name = $1; + if (!current_class->add_field(current_atomic)) { + yyerror("Duplicate atomic field name: " + current_atomic->_name); + } +} + parameter_list ')' atomic_flags + ; + +atomic_name: + IDENTIFIER +{ + $$ = current_class->get_atomic_by_name($1); + if ($$ == (DCAtomicField *)NULL) { + yyerror("Unknown atomic field: " + $1); + } +} + +parameter_list: + empty + | nonempty_parameter_list + ; + +nonempty_parameter_list: + subatomic_type + | nonempty_parameter_list ',' subatomic_type + ; + +subatomic_type: + type_token +{ + DCAtomicField::ElementType et; + et._type = $1; + et._divisor = 1; + current_atomic->_elements.push_back(et); +} + | type_token '/' INTEGER +{ + DCAtomicField::ElementType et; + et._type = $1; + et._divisor = $3; + current_atomic->_elements.push_back(et); +} + ; + +type_token: + KW_INT8 +{ + $$ = ST_int8; +} + | KW_INT16 +{ + $$ = ST_int16; +} + | KW_INT32 +{ + $$ = ST_int32; +} + | KW_INT64 +{ + $$ = ST_int64; +} + | KW_UINT8 +{ + $$ = ST_uint8; +} + | KW_UINT16 +{ + $$ = ST_uint16; +} + | KW_UINT32 +{ + $$ = ST_uint32; +} + | KW_UINT64 +{ + $$ = ST_uint64; +} + | KW_FLOAT64 +{ + $$ = ST_float64; +} + | KW_STRING +{ + $$ = ST_string; +} + ; + +atomic_flags: + empty + | atomic_flags KW_REQUIRED +{ + current_atomic->_flags |= DCAtomicField::F_required; +} + | atomic_flags KW_BROADCAST +{ + current_atomic->_flags |= DCAtomicField::F_broadcast; +} + | atomic_flags KW_P2P +{ + current_atomic->_flags |= DCAtomicField::F_p2p; +} + | atomic_flags KW_RAM +{ + current_atomic->_flags |= DCAtomicField::F_ram; +} + | atomic_flags KW_DB +{ + current_atomic->_flags |= DCAtomicField::F_db; +} + | atomic_flags KW_CLSEND +{ + current_atomic->_flags |= DCAtomicField::F_clsend; +} + | atomic_flags KW_CLRECV +{ + current_atomic->_flags |= DCAtomicField::F_clrecv; +} + | atomic_flags KW_AISEND +{ + current_atomic->_flags |= DCAtomicField::F_aisend; +} + | atomic_flags KW_AIRECV +{ + current_atomic->_flags |= DCAtomicField::F_airecv; +} + ; + +molecular_field: + IDENTIFIER ':' +{ + current_molecular = new DCMolecularField; + current_molecular->_name = $1; + if (!current_class->add_field(current_molecular)) { + yyerror("Duplicate molecular field name: " + current_molecular->_name); + } +} + molecular_atom_list + ; + +molecular_atom_list: + atomic_name +{ + if ($1 != (DCAtomicField *)NULL) { + current_molecular->_fields.push_back($1); + } +} + | molecular_atom_list ',' atomic_name +{ + if ($3 != (DCAtomicField *)NULL) { + current_molecular->_fields.push_back($3); + } +} + ; + +empty: + ; diff --git a/direct/src/dcparse/dcParserDefs.h b/direct/src/dcparse/dcParserDefs.h new file mode 100644 index 0000000000..450b507fce --- /dev/null +++ b/direct/src/dcparse/dcParserDefs.h @@ -0,0 +1,43 @@ +// Filename: dcParserDefs.h +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef DCPARSERDEFS_H +#define DCPARSERDEFs_H + +#include "dcbase.h" +#include "dcSubatomicType.h" + +class DCFile; +class DCClass; +class DCAtomicField; + +void dc_init_parser(istream &in, const string &filename, DCFile &file); +void dc_cleanup_parser(); +int dcyyparse(); + +// This structure holds the return value for each token. +// Traditionally, this is a union, and is declared with the %union +// declaration in the parser.y file, but unions are pretty worthless +// in C++ (you can't include an object that has member functions in a +// union), so we'll use a class instead. That means we need to +// declare it externally, here. + +class DCTokenType { +public: + union U { + int integer; + double real; + DCClass *dclass; + DCAtomicField *atomic; + DCSubatomicType subatomic; + } u; + string str; +}; + +// The yacc-generated code expects to use the symbol 'YYSTYPE' to +// refer to the above class. +#define YYSTYPE DCTokenType + +#endif diff --git a/direct/src/dcparse/dcSubatomicType.cxx b/direct/src/dcparse/dcSubatomicType.cxx new file mode 100644 index 0000000000..ed68daaee7 --- /dev/null +++ b/direct/src/dcparse/dcSubatomicType.cxx @@ -0,0 +1,43 @@ +// Filename: dcSubatomicType.cxx +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "dcSubatomicType.h" + +ostream & +operator << (ostream &out, DCSubatomicType type) { + switch (type) { + case ST_int8: + return out << "int8"; + + case ST_int16: + return out << "int16"; + + case ST_int32: + return out << "int32"; + + case ST_int64: + return out << "int64"; + + case ST_uint8: + return out << "uint8"; + + case ST_uint16: + return out << "uint16"; + + case ST_uint32: + return out << "uint32"; + + case ST_uint64: + return out << "uint64"; + + case ST_float64: + return out << "float64"; + + case ST_string: + return out << "string"; + } + + return out << "invalid type: " << (int)type; +} diff --git a/direct/src/dcparse/dcSubatomicType.h b/direct/src/dcparse/dcSubatomicType.h new file mode 100644 index 0000000000..1ff431c22c --- /dev/null +++ b/direct/src/dcparse/dcSubatomicType.h @@ -0,0 +1,40 @@ +// Filename: dcSubatomicType.h +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef DCSUBATOMICTYPE_H +#define DCSUBATOMICTYPE_H + +#include "dcbase.h" + +BEGIN_PUBLISH +//////////////////////////////////////////////////////////////////// +// Enum : DCSubatomicType +// Description : This defines the numeric type of each element of a +// DCAtomicField; that is, the particular values that +// will get added to the message when the atomic field +// method is called. +//////////////////////////////////////////////////////////////////// +enum DCSubatomicType { + ST_int8, + ST_int16, + ST_int32, + ST_int64, + + ST_uint8, + ST_uint16, + ST_uint32, + ST_uint64, + + ST_float64, + + ST_string, +}; +END_PUBLISH + +ostream &operator << (ostream &out, DCSubatomicType type); + +#endif + + diff --git a/direct/src/dcparse/dcbase.h b/direct/src/dcparse/dcbase.h new file mode 100644 index 0000000000..46cc8e1021 --- /dev/null +++ b/direct/src/dcparse/dcbase.h @@ -0,0 +1,59 @@ +// Filename: dcbase.h +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef DCBASE_H +#define DCBASE_H + +// This file defines a few headers and stuff necessary for compilation +// of this project. Most compiler-specific decisions should be +// grouped up here. + +#ifdef WIN32 +/* C4786: 255 char debug symbols */ +#pragma warning (disable : 4786) +/* C4503: decorated name length exceeded */ +#pragma warning (disable : 4503) +#endif /* WIN32_VC */ + +#include +#include + +// These header files are needed to compile dcLexer.cxx, the output +// from flex. flex doesn't create a perfectly windows-friendly source +// file right out of the box. +#ifdef WIN32 +#include +#include +#else +#include +#endif + +using namespace std; + +#ifdef CPPPARSER +// We define the macro PUBLISHED to mark C++ methods that are to be +// published via interrogate to scripting languages. However, if +// we're not running the interrogate pass (CPPPARSER isn't defined), +// this maps to public. +#define PUBLISHED __published +#else +#define PUBLISHED public +#endif + +/* + We define the macros BEGIN_PUBLISH and END_PUBLISH to bracket + functions and global variable definitions that are to be published + via interrogate to scripting languages. + */ +#ifdef CPPPARSER +#define BEGIN_PUBLISH __begin_publish +#define END_PUBLISH __end_publish +#else +#define BEGIN_PUBLISH +#define END_PUBLISH +#endif + +#endif + diff --git a/direct/src/dcparse/dcparse.cxx b/direct/src/dcparse/dcparse.cxx new file mode 100644 index 0000000000..c291fe2ade --- /dev/null +++ b/direct/src/dcparse/dcparse.cxx @@ -0,0 +1,30 @@ +// Filename: dcparse.cxx +// Created by: drose (05Oct00) +// +//////////////////////////////////////////////////////////////////// + +#include "dcbase.h" +#include "dcFile.h" + +int +main(int argc, char *argv[]) { + if (argc < 2) { + cerr << + "dcparse - a simple program to read one or more .dc files and report their\n" + "contents to standard output.\n\n"; + return (1); + } + + DCFile file; + for (int i = 1; i < argc; i++) { + if (!file.read(argv[i])) { + return (1); + } + } + + if (!file.write(cout, "standard output")) { + return (1); + } + + return (0); +} diff --git a/direct/src/dcparse/indent.cxx b/direct/src/dcparse/indent.cxx new file mode 100644 index 0000000000..0ccd4d5ef4 --- /dev/null +++ b/direct/src/dcparse/indent.cxx @@ -0,0 +1,18 @@ +// Filename: indent.cxx +// Created by: drose (05May00) +// +//////////////////////////////////////////////////////////////////// + +#include "indent.h" + +//////////////////////////////////////////////////////////////////// +// Function: indent +// Description: +//////////////////////////////////////////////////////////////////// +ostream & +indent(ostream &out, int indent_level) { + for (int i = 0; i < indent_level; i++) { + out << ' '; + } + return out; +} diff --git a/direct/src/dcparse/indent.h b/direct/src/dcparse/indent.h new file mode 100644 index 0000000000..13641019d6 --- /dev/null +++ b/direct/src/dcparse/indent.h @@ -0,0 +1,29 @@ +// Filename: indent.h +// Created by: drose (16Jan99) +// +//////////////////////////////////////////////////////////////////// + +#ifndef INDENT_H +#define INDENT_H + +#include "dcbase.h" + +// We rename indent() so it won't clash with the similar function +// defined in Panda. +#define indent dcindent + +//////////////////////////////////////////////////////////////////// +// Function: indent +// Description: A handy function for doing text formatting. This +// function simply outputs the indicated number of +// spaces to the given output stream, returning the +// stream itself. Useful for indenting a series of +// lines of text by a given amount. +//////////////////////////////////////////////////////////////////// +ostream & +indent(ostream &out, int indent_level); + +#endif + + + diff --git a/direct/src/dcparse/test.dc b/direct/src/dcparse/test.dc new file mode 100644 index 0000000000..baf86cced5 --- /dev/null +++ b/direct/src/dcparse/test.dc @@ -0,0 +1,21 @@ +// Define a distributed object with a position. + +dclass Movable { + + // Declare a few atomic fields + set_xyz(int16 / 1000, int16 / 1000, int16 / 1000) required broadcast ram; + set_hpr(int16, int16, int16) required broadcast ram; + + // And a molecular field + set_xyzhpr : set_xyz, set_hpr; +}; + +// Define a distributed object with a position and an attitude. +dclass Attitude : Movable { + // methods are inherited from parent class. + + set_attitude(int8); + + // This molecular field uses some inherited atomic fields. + set_xyzhprattitude : set_xyz, set_hpr, set_attitude; +}; diff --git a/direct/src/directbase/Sources.pp b/direct/src/directbase/Sources.pp new file mode 100644 index 0000000000..629a049177 --- /dev/null +++ b/direct/src/directbase/Sources.pp @@ -0,0 +1,10 @@ +#begin lib_target + #define TARGET directbase + + #define SOURCES \ + directbase.cxx directbase.h directsymbols.h \ + + #define INSTALL_HEADERS \ + directbase.h directsymbols.h + +#end lib_target diff --git a/direct/src/directbase/directbase.cxx b/direct/src/directbase/directbase.cxx new file mode 100644 index 0000000000..56204cb25d --- /dev/null +++ b/direct/src/directbase/directbase.cxx @@ -0,0 +1,6 @@ +// Filename: directbase.cc +// Created by: drose (15Sep00) +// +//////////////////////////////////////////////////////////////////// + +#include "directbase.h" diff --git a/direct/src/directbase/directbase.h b/direct/src/directbase/directbase.h new file mode 100644 index 0000000000..d55fc80d6b --- /dev/null +++ b/direct/src/directbase/directbase.h @@ -0,0 +1,18 @@ +/* + * Filename: directbase.h + * Created by: drose (12Sep00) + * + */ + +/* This file is included at the beginning of every header file and/or + C or C++ file. It must be compilable for C as well as C++ files, + so no C++-specific code or syntax can be put here. */ + +#ifndef DIRECTBASE_H +#define DIRECTBASE_H + +#include +#include "directsymbols.h" + +#endif + diff --git a/direct/src/directbase/directsymbols.h b/direct/src/directbase/directsymbols.h new file mode 100644 index 0000000000..97c308d149 --- /dev/null +++ b/direct/src/directbase/directsymbols.h @@ -0,0 +1,30 @@ +/* +// Filename: directsymbols.h +// Created by: drose (18Feb00) +// +//////////////////////////////////////////////////////////////////// +*/ + +#ifndef DIRECTSYMBOLS_H +#define DIRECTSYMBOLS_H + +/* See dtoolsymbols.h for a rant on the purpose of this file. */ + +#if defined(PENV_WIN32) && !defined(CPPPARSER) + +#ifdef BUILDING_DIRECT + #define EXPCL_DIRECT __declspec(dllexport) + #define EXPTP_DIRECT +#else + #define EXPCL_DIRECT __declspec(dllimport) + #define EXPTP_DIRECT extern +#endif + +#else /* !PENV_WIN32 */ + +#define EXPCL_DIRECT +#define EXPTP_DIRECT + +#endif /* PENV_WIN32 */ + +#endif diff --git a/direct/src/distributed/ClientDistClass.py b/direct/src/distributed/ClientDistClass.py new file mode 100644 index 0000000000..c5de65454b --- /dev/null +++ b/direct/src/distributed/ClientDistClass.py @@ -0,0 +1,21 @@ +"""ClientDistClass module: contains the ClientDistClass class""" + +from PandaModules import * +import DirectNotifyGlobal + +class ClientDistClass: + + def __init__(self, dcClass): + self.number = dcClass.get_number() + self.name = dcClass.get_name() + self.atomicFields=[] + self.molecularFields=[] + self.parseAtomicFields(dcClass) + self.parseMolecularFields(dcClass) + return None + + def parseAtomicFields(dcClass): + for i in range(0,dcClass.get_num_inherited_atomics()): + self.atomicFields.append((dcClass.get_inherited_atomic(i)) + + diff --git a/direct/src/distributed/ClientRepository.py b/direct/src/distributed/ClientRepository.py new file mode 100644 index 0000000000..abd94a4ee7 --- /dev/null +++ b/direct/src/distributed/ClientRepository.py @@ -0,0 +1,84 @@ +"""ClientRepository module: contains the ClientRepository class""" + +from PandaModules import * +from TaskManagerGlobal import * +import Task +import DirectNotifyGlobal + +class ClientRepository: + defaultServerPort = 5150 + notify = DirectNotifyGlobal.directNotify.newCategory("ClientRepository") + + def __init__(self, dcFileName): + self.number2cdc={} + self.name2cdc={} + self.parseDcFile(dcFileName) + return None + + def parseDcFile(dcFileName): + dcFile = DCFile() + dcFile.read(dcFileName) + return self.parseDcClasses(dcFile) + + def parseDcClasses(dcFile): + numClasses = dcFile.get_num_classes() + for i in range(0, numClasses): + # Create a clientDistClass from the dcClass + clientDistClass = self.parseClass(dcFile.getClass()) + # List the cdc in the number and name dictionaries + self.number2cdc[clientDistClass.getNumber()]=clientDistClass + self.name2cdc[clientDistClass.getName()]=clientDistClass + return None + + def parseClass(dcClass): + + + def connect(self, serverName="localhost", + serverPort=defaultServerPort): + self.qcm=QueuedConnectionManager() + self.tcpConn = self.qcm.openTCPClientConnection( + serverName, serverPort, 1000) + self.qcr=QueuedConnectionReader(self.qcm, 0) + self.cw=ConnectionWriter(self.qcm, 0) + self.startReaderPollTask() + + def startReaderPollTask(self): + task = Task.Task(self.readerPollUntilEmpty) + taskMgr.spawnTaskNamed(task, "readerPollTask") + return None + + def readerPollUntilEmpty(self, task): + while self.readerPollOnce(): + pass + return Task.cont + + def readerPollOnce(self): + availGetVal = self.qcr.dataAvailable() + if availGetVal: + datagram = NetDatagram() + readRetVal = self.qcr.getData(datagram) + if readRetVal: + self.handleDatagram(datagram) + else: + ClientRepository.notify.warning("getData returned false") + return availGetVal + + def handleDatagram(datagram): + print 'Got it!' + return None + + def sendLoginMsg(self): + datagram = Datagram() + # Add message type + datagram.addUint16(1) + # Add swid + datagram.addString("1234567890123456789012345678901234") + # Add IP Address + datagram.addUint32(0) + # Add UDP port + datagram.addUint16(5150) + # Send the message + self.cw.send(datagram, self.tcpConn) + + + diff --git a/direct/src/distributed/DistributedActor.py b/direct/src/distributed/DistributedActor.py new file mode 100644 index 0000000000..545fffabec --- /dev/null +++ b/direct/src/distributed/DistributedActor.py @@ -0,0 +1,8 @@ +"""DistributedActor module: contains the DistributedActor class""" + +from DistributedNode import * +import Actor + +class DistributedActor(DistributedNode, Actor.Actor): + """Distributed Actor class:""" + pass diff --git a/direct/src/distributed/DistributedNode.py b/direct/src/distributed/DistributedNode.py new file mode 100644 index 0000000000..ec35e41c8a --- /dev/null +++ b/direct/src/distributed/DistributedNode.py @@ -0,0 +1,7 @@ +"""DistributedNode module: contains the DistributedNode class""" + +from DistributedObject import * + +class DistributedNode(DistributedObject, ShowBase.NodePath): + """Distributed Node class:""" + pass diff --git a/direct/src/distributed/DistributedObject.py b/direct/src/distributed/DistributedObject.py new file mode 100644 index 0000000000..749577c563 --- /dev/null +++ b/direct/src/distributed/DistributedObject.py @@ -0,0 +1,7 @@ +"""DistributedObject module: contains the DistributedObject class""" + +from PandaObject import * + +class DistributedObject(PandaObject): + """Distributed Object class:""" + pass diff --git a/direct/src/distributed/ServerRepository.py b/direct/src/distributed/ServerRepository.py new file mode 100644 index 0000000000..532d770a04 --- /dev/null +++ b/direct/src/distributed/ServerRepository.py @@ -0,0 +1,90 @@ +"""ServerRepository module: contains the ServerRepository class""" + +from PandaModules import * +from TaskManagerGlobal import * +import Task +import DirectNotifyGlobal + +class ServerRepository: + + def __init__(self, tcpPort, udpPort): + self.qcm = QueuedConnectionManager() + self.qcl = QueuedConnectionListener(self.qcm, 0) + self.qcr = QueuedConnectionReader(self.qcm, 0) + self.cw = ConnectionWriter(self.qcm,0) + self.tcpRendezvous = self.qcm.openTCPServerRendezvous(tcpPort, 10) + print self.tcpRendezvous + self.qcl.addConnection(self.tcpRendezvous) + self.startListenerPollTask() + self.startReaderPollTask() + self.startResetPollTask() + return None + + def startListenerPollTask(self): + task = Task.Task(self.listenerPoll) + taskMgr.spawnTaskNamed(task, "serverListenerPollTask") + return None + + def listenerPoll(self, task): + if self.qcl.newConnectionAvailable(): + print "New connection is available" + rendezvous = PointerToConnection() + netAddress = NetAddress() + newConnection = PointerToConnection() + retVal = self.qcl.getNewConnection(rendezvous, netAddress, + newConnection) + if retVal: + # Crazy dereferencing + newConnection=newConnection.p() + self.qcr.addConnection(newConnection) + print "Got a connection!" + self.lastConnection = newConnection + else: + ServerRepository.notify.warning( + "getNewConnection returned false") + return Task.cont + + def startReaderPollTask(self): + task = Task.Task(self.readerPollUntilEmpty) + taskMgr.spawnTaskNamed(task, "serverReaderPollTask") + return None + + def readerPollUntilEmpty(self, task): + while self.readerPollOnce(): + pass + return Task.cont + + def readerPollOnce(self): + availGetVal = self.qcr.dataAvailable() + if availGetVal: + datagram = NetDatagram() + readRetVal = self.qcr.getData(datagram) + if readRetVal: + self.handleDatagram(datagram) + else: + ClientRepository.notify.warning("getData returned false") + return availGetVal + + def handleDatagram(self, datagram): + print "Server got a datagram!" + dgi = DatagramIterator(datagram) + print dgi.getUint16() + print dgi.getString() + print dgi.getUint32() + print dgi.getUint16() + + newDatagram = Datagram() + datagram.addUint16(2) + datagram.addUint8(ord('s')) + self.cw.send(datagram, self.lastConnection) + return None + + def startResetPollTask(self): + return None + + def resetPollUntilEmpty(self): + return None + + def resetPollOnce(self): + return None + diff --git a/direct/src/distributed/Sources.pp b/direct/src/distributed/Sources.pp new file mode 100644 index 0000000000..a03ea8c336 --- /dev/null +++ b/direct/src/distributed/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/direct/src/extensions/NodePath-extensions.py b/direct/src/extensions/NodePath-extensions.py new file mode 100644 index 0000000000..695239a47c --- /dev/null +++ b/direct/src/extensions/NodePath-extensions.py @@ -0,0 +1,479 @@ + """ + NodePath-extensions module: contains methods to extend functionality + of the NodePath class + """ + + + # private methods + + def __getBlend(self, blendType): + """__getBlend(self, string) + Return the C++ blend class corresponding to blendType string + """ + import LerpBlendHelpers + + if (blendType == "easeIn"): + return LerpBlendHelpers.LerpBlendHelpers.easeIn + elif (blendType == "easeOut"): + return LerpBlendHelpers.LerpBlendHelpers.easeOut + elif (blendType == "easeInOut"): + return LerpBlendHelpers.LerpBlendHelpers.easeInOut + elif (blendType == "noBlend"): + return LerpBlendHelpers.LerpBlendHelpers.noBlend + else: + raise Exception("Error: NodePath.__getBlend: Unknown blend type") + + + def __lerp(self, functor, time, blendType, taskName=None): + """__lerp(self, functor, float, string, string) + Basic lerp functionality used by other lerps. + Fire off a lerp. Make it a task if taskName given.""" + import Lerp + # make the lerp + lerp = Lerp.Lerp(functor, time, (self.__getBlend(blendType))) + + from TaskManagerGlobal import * + # make the task function + def lerpTaskFunc(task): + import Task + import ClockObject + dt = ClockObject.ClockObject.getGlobalClock().getDt() + task.lerp.setStepSize(dt) + task.lerp.step() + if (task.lerp.isDone()): + return(Task.done) + else: + return(Task.cont) + + # make the lerp task + lerpTask = Task.Task(lerpTaskFunc) + lerpTask.lerp = lerp + + if (taskName == None): + # don't spawn a task, return one instead + return lerpTask + else: + # spawn the lerp task + taskMgr.spawnTaskNamed(lerpTask, taskName) + return lerpTask + + def __autoLerp(self, functor, time, blendType, taskName): + """_autoLerp(self, functor, float, string, string) + This lerp uses C++ to handle the stepping. Bonus is + its more efficient, trade-off is there is less control""" + import AutonomousLerp + from ShowBaseGlobal import * + + # make a lerp that lives in C++ land + lerp = AutonomousLerp.AutonomousLerp(functor, time, + self.__getBlend(blendType), + base.eventHandler) + lerp.start() + return lerp + + + # user callable lerp methods + def lerpColor(self, *posArgs, **keyArgs): + """lerpColor(self, *positionArgs, **keywordArgs) + determine which lerpColor* to call based on arguments + """ + if (len(posArgs) == 2): + return apply(self.lerpColorVBase4, posArgs, keyArgs) + elif (len(posArgs) == 3): + return apply(self.lerpColorVBase4VBase4, posArgs, keyArgs) + elif (len(posArgs) == 5): + return apply(self.lerpColorRGBA, posArgs, keyArgs) + elif (len(posArgs) == 9): + return apply(self.lerpColorRGBARGBA, posArgs, keyArgs) + else: + # bad args + raise Exception("Error: NodePath.lerpColor: bad number of args") + + + def lerpColorRGBA(self, r, g, b, a, time, blendType="noBlend", + auto=None, task=None): + """lerpColorRGBA(self, float, float, float, float, float, + string="noBlend", string=none, string=none) + """ + import ColorLerpFunctor + # just end rgba values, use current color rgba values for start + startColor = self.getColor() + functor = ColorLerpFunctor.ColorLerpFunctor(self, + startColor[0], startColor[1], + startColor[2], startColor[3], + r, g, b, a) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + def lerpColorRGBARGBA(self, sr, sg, sb, sa, er, eg, eb, ea, time, + blendType="noBlend", auto=None, task=None): + """lerpColorRGBARGBA(self, float, float, float, float, float, + float, float, float, float, string="noBlend", string=none, string=none) + """ + import ColorLerpFunctor + # start and end rgba values + functor = ColorLerpFunctor.ColorLerpFunctor(self, sr, sg, sb, sa, + er, eg, eb, ea) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + def lerpColorVBase4(self, endColor, time, blendType="noBlend", + auto=None, task=None): + """lerpColorVBase4(self, VBase4, float, string="noBlend", string=none, + string=none) + """ + import ColorLerpFunctor + # just end vec4, use current color for start + startColor = self.getColor() + functor = ColorLerpFunctor.ColorLerpFunctor(self, startColor, endColor) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + def lerpColorVBase4VBase4(self, startColor, endColor, time, + blendType="noBlend", auto=None, task=None): + """lerpColorVBase4VBase4(self, VBase4, VBase4, float, string="noBlend", + string=none, string=none) + """ + import ColorLerpFunctor + # start color and end vec + functor = ColorLerpFunctor.ColorLerpFunctor(self, startColor, endColor) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + + def lerpHpr(self, *posArgs, **keyArgs): + """lerpHpr(self, *positionArgs, **keywordArgs) + Determine whether to call lerpHprHPR or lerpHprVBase3 + based on first argument + """ + # check to see if lerping with + # three floats or a VBase3 + if (len(posArgs) == 4): + return apply(self.lerpHprHPR, posArgs, keyArgs) + elif(len(posArgs) == 2): + return apply(self.lerpHprVBase3, posArgs, keyArgs) + else: + # bad args + raise Exception("Error: NodePath.lerpHpr: bad number of args") + + def lerpHprHPR(self, h, p, r, time, blendType="noBlend", auto=None, + task=None, other=None): + """lerpHprHPR(self, float, float, float, float, string="noBlend", + string=none, string=none, NodePath=none) + Perform a hpr lerp with three floats as the end point + """ + import HprLerpFunctor + # it's individual hpr components + if (other != None): + # lerp wrt other + startHpr = self.getHpr(other) + functor = HprLerpFunctor.HprLerpFunctor(self, + startHpr[0], startHpr[1], startHpr[2], + h, p, r, other) + else: + startHpr = self.getHpr() + functor = HprLerpFunctor.HprLerpFunctor(self, + startHpr[0], startHpr[1], startHpr[2], + h, p, r) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + + def lerpHprVBase3(self, hpr, time, blendType="noBlend", auto=None, + task=None, other=None): + """lerpHprVBase3(self, VBase3, float, string="noBlend", string=none, + string=none, NodePath=None) + Perform a hpr lerp with a VBase3 as the end point + """ + import HprLerpFunctor + # it's a vbase3 hpr + if (other != None): + # lerp wrt other + functor = HprLerpFunctor.HprLerpFunctor(self, (self.getHpr(other)), + hpr, other) + else: + functor = HprLerpFunctor.HprLerpFunctor(self, (self.getHpr()), + hpr) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + + def lerpPos(self, *posArgs, **keyArgs): + """lerpPos(self, *positionArgs, **keywordArgs) + Determine whether to call lerpPosXYZ or lerpPosPoint3 + based on the first argument + """ + # check to see if lerping with three + # floats or a Point3 + if (len(posArgs) == 4): + return apply(self.lerpPosXYZ, posArgs, keyArgs) + elif(len(posArgs) == 2): + return apply(self.lerpPosPoint3, posArgs, keyArgs) + else: + # bad number off args + raise Exception("Error: NodePath.lerpPos: bad number of args") + + def lerpPosXYZ(self, x, y, z, time, blendType="noBlend", auto=None, + task=None, other=None): + """lerpPosXYZ(self, float, float, float, float, string="noBlend", + string=None, NodePath=None) + Perform a pos lerp with three floats as the end point + """ + import PosLerpFunctor + if (other != None): + # lerp wrt other + startPos = self.getPos(other) + functor = PosLerpFunctor.PosLerpFunctor(self, + startPos[0], startPos[1], startPos[2], + x, y, z, other) + else: + startPos = self.getPos() + functor = PosLerpFunctor.PosLerpFunctor(self, startPos[0], + startPos[1], startPos[2], x, y, z) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + def lerpPosPoint3(self, pos, time, blendType="noBlend", auto=None, + task=None, other=None): + """lerpPosPoint3(self, Point3, float, string="noBlend", string=None, + string=None, NodePath=None) + Perform a pos lerp with a Point3 as the end point + """ + import PosLerpFunctor + if (other != None): + #lerp wrt other + functor = PosLerpFunctor.PosLerpFunctor(self, (self.getPos(other)), + pos, other) + else: + functor = PosLerpFunctor.PosLerpFunctor(self, (self.getPos()), pos) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + + def lerpPosHpr(self, *posArgs, **keyArgs): + """lerpPosHpr(self, *positionArgs, **keywordArgs) + Determine whether to call lerpPosHprXYZHPR or lerpHprPoint3VBase3 + based on first argument + """ + # check to see if lerping with + # six floats or a Point3 and a VBase3 + if (len(posArgs) == 7): + return apply(self.lerpPosHprXYZHPR, posArgs, keyArgs) + elif(len(posArgs) == 3): + return apply(self.lerpPosHprPoint3VBase3, posArgs, keyArgs) + else: + # bad number off args + raise Exception("Error: NodePath.lerpPosHpr: bad number of args") + + def lerpPosHprPoint3VBase3(self, pos, hpr, time, blendType="noBlend", + auto=None, task=None, other=None): + """lerpPosHprPoint3VBase3(self, Point3, VBase3, string="noBlend", + string=none, string=none, NodePath=None) + """ + import PosHprLerpFunctor + if (other != None): + # lerp wrt other + startPos = self.getPos(other) + startHpr = self.getHpr(other) + functor = PosHprLerpFunctor.PosHprLerpFunctor(self, + startPos, pos, + startHpr, hpr, other) + else: + startPos = self.getPos() + startHpr = self.getHpr() + functor = PosHprLerpFunctor.PosHprLerpFunctor(self, + startPos, pos, + startHpr, hpr) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + def lerpPosHprXYZHPR(self, x, y, z, h, p, r, time, blendType="noBlend", + auto=None, task=None, other=None): + """lerpPosHpr(self, float, string="noBlend", string=none, + string=none, NodePath=None) + """ + import PosHprLerpFunctor + if (other != None): + # lerp wrt other + startPos = self.getPos(other) + startHpr = self.getHpr(other) + functor = PosHprLerpFunctor.PosHprLerpFunctor(self, + startPos[0], startPos[1], + startPos[2], x, y, z, + startHpr[0], startHpr[1], + startHpr[2], h, p, r, + other) + else: + startPos = self.getPos() + startHpr = self.getHpr() + functor = PosHprLerpFunctor.PosHprLerpFunctor(self, + startPos[0], startPos[1], + startPos[2], x, y, z, + startHpr[0], startHpr[1], + startHpr[2], h, p, r) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + + def lerpPosHprScale(self, pos, hpr, scale, time, blendType="noBlend", + auto=None, task=None, other=None): + """lerpPosHpr(self, Point3, VBase3, float, float, string="noBlend", + string=none, string=none, NodePath=None) + Only one case, no need for extra args. Call the appropriate lerp + (auto, spawned, or blocking) based on how(if) a task name is given + """ + import PosHprScaleLerpFunctor + if (other != None): + # lerp wrt other + startPos = self.getPos(other) + startHpr = self.getHpr(other) + startScale = self.getScale(other) + functor = PosHprScaleLerpFunctor.PosHprScaleLerpFunctor(self, + startPos, pos, + startHpr, hpr, + startScale, scale, other) + else: + startPos = self.getPos() + startHpr = self.getHpr() + startScale = self.getScale() + functor = PosHprScaleLerpFunctor.PosHprScaleLerpFunctor(self, + startPos, pos, + startHpr, hpr, + startScale, scale) + + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + + def lerpScale(self, *posArgs, **keyArgs): + """lerpSclae(self, *positionArgs, **keywordArgs) + Determine whether to call lerpScaleXYZ or lerpScaleaseV3 + based on the first argument + """ + # check to see if lerping with three + # floats or a Point3 + if (len(posArgs) == 4): + return apply(self.lerpScaleXYZ, posArgs, keyArgs) + elif(len(posArgs) == 2): + return apply(self.lerpScaleVBase3, posArgs, keyArgs) + else: + # bad number off args + raise Exception("Error: NodePath.lerpScale: bad number of args") + + def lerpScaleVBase3(self, scale, time, blendType="noBlend", auto=None, + task=None, other=None): + """lerpPos(self, VBase3, float, string="noBlend", string=none, + string=none, NodePath=None) + """ + import ScaleLerpFunctor + if (other != None): + # lerp wrt other + functor = ScaleLerpFunctor.ScaleLerpFunctor(self, + (self.getScale(other)), + scale, other) + else: + functor = ScaleLerpFunctor.ScaleLerpFunctor(self, + (self.getScale()), scale) + + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + def lerpScaleXYZ(self, sx, sy, sz, time, blendType="noBlend", + auto=None, task=None, other=None): + """lerpPos(self, float, float, float, float, string="noBlend", + string=none, string=none, NodePath=None) + """ + import ScaleLerpFunctor + if (other != None): + # lerp wrt other + startScale = self.getScale(other) + functor = ScaleLerpFunctor.ScaleLerpFunctor(self, + startScale[0], startScale[1], + startScale[2], sx, sy, sz, other) + else: + startScale = self.getScale() + functor = ScaleLerpFunctor.ScaleLerpFunctor(self, + startScale[0], startScale[1], + startScale[2], sx, sy, sz) + #determine whether to use auto, spawned, or blocking lerp + if (auto != None): + return self.__autoLerp(functor, time, blendType, auto) + elif (task != None): + return self.__lerp(functor, time, blendType, task) + else: + return self.__lerp(functor, time, blendType) + + + + + + + + + + + + + + + diff --git a/direct/src/extensions/Sources.pp b/direct/src/extensions/Sources.pp new file mode 100644 index 0000000000..a03ea8c336 --- /dev/null +++ b/direct/src/extensions/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/direct/src/ffi/FFIConstants.py b/direct/src/ffi/FFIConstants.py new file mode 100644 index 0000000000..0bc8139461 --- /dev/null +++ b/direct/src/ffi/FFIConstants.py @@ -0,0 +1,31 @@ + +# create a DirectNotify category for FFI modules +from DirectNotifyGlobal import * +notify = directNotify.newCategory("FFI") + +# This is the name of the file that the global functions and values +# will be stored +globalModuleName = 'PandaGlobals' + +# This is the name of the file that the importing code will be stored +importModuleName = 'PandaModules' + +# This is the name of the file where the static helper class will be stored +staticModuleName = 'PandaStatic' + +# A header for all the generated files +generatedHeader = '# This file is automatically generated. It would be unwise to edit.\n\n' + +# These modules should come from somewhere outside this program +# Maybe in an environment variable, or by looking at what you are +# attached to? +CodeModuleNameList = [] + +# This is the module that contains the interrogate functions +InterrogateModuleName = None + +# Should FFI output C++ comments with the source code? +wantComments = 1 + +# Should FFI output type assertions? +wantTypeChecking = 1 diff --git a/direct/src/ffi/FFIEnvironment.py b/direct/src/ffi/FFIEnvironment.py new file mode 100644 index 0000000000..48891928d4 --- /dev/null +++ b/direct/src/ffi/FFIEnvironment.py @@ -0,0 +1,23 @@ +import FFIConstants + +class FFIEnvironment: + def __init__(self): + self.types = {} + self.globalFunctions = [] + self.globalValues = [] + + def addType(self, typeDescriptor, name): + if self.types.has_key(name): + FFIConstants.notify.warning('Redefining type named: ' + name) + self.types[name] = typeDescriptor + + def getTypeNamed(self, name): + try: + self.types[name] + except KeyError: + raise 'Type not found in FFIEnvironment' + + def addGlobalFunction(self, typeDescriptor): + self.globalFunctions.append(typeDescriptor) + def addGlobalValue(self, typeDescriptor): + self.globalValues.append(typeDescriptor) diff --git a/direct/src/ffi/FFIExternalObject.py b/direct/src/ffi/FFIExternalObject.py new file mode 100644 index 0000000000..fe7d176988 --- /dev/null +++ b/direct/src/ffi/FFIExternalObject.py @@ -0,0 +1,130 @@ + +import FFIConstants +import TypedObject + +WrapperClassMap = {} + + + +def getDowncastFunctions(thisClass, baseClass, chain): + if (thisClass == baseClass): + # Found it, return true + return 1 + elif (len(thisClass.__bases__) == 0): + # Not here, return 0 + return 0 + else: + # Look recursively in the classes thisClass inherits from + for base in thisClass.__bases__: + # If it finds it, append the base class's downcast function + # to the chain if it has one + if getDowncastFunctions(base, baseClass, chain): + downcastFuncName = 'downcastTo' + thisClass.__name__ + if base.__dict__.has_key(downcastFuncName): + FFIConstants.notify.info('Found downcast function %s in %s' % (downcastFuncName, base.__name__)) + chain.append(base.__dict__[downcastFuncName]) + return chain + + +class FFIExternalObject: + def __init__(self, *_args): + # By default, we do not manage our own memory + self.userManagesMemory = 0 + # Start with a null this pointer + self.this = 0 + + def asExactType(self): + return self.getType() + + def isTypedObject(self): + return isinstance(self, TypedObject.TypedObject) + + def setPointer(self): + if (self.this == 0): + # Null pointer, return None + return None + # If it is not a typed object, our work is done, just return the object + if (not self.isTypedObject()): + return self + # Ok, it is a typed object. See what type it really is and downcast + # to that type (if necessary) + exactWrapperClass = self.wrapperClassForTypeHandle(self.asExactType()) + # We do not need to downcast if we already have the same class + if (exactWrapperClass and (exactWrapperClass != self.__class__)): + # Create a new wrapper class instance + exactObject = exactWrapperClass(None) + # Get the downcast pointer that has had all the downcast funcs called + downcastObject = self.downcast(exactWrapperClass) + exactObject.this = downcastObject.this + exactObject.userManagesMemory = downcastObject.userManagesMemory + # Make sure the original downcast object does not get garbage collected + # so that the exactObject will not get gc'd thereby transferring ownership + # of the object to this new exactObject + downcastObject.userManagesMemory = 0 + return exactObject + else: + return self + + def wrapperClassForTypeHandle(self, aTypeHandle): + if WrapperClassMap.has_key(aTypeHandle.getIndex()): + return WrapperClassMap[aTypeHandle.getIndex()] + else: + return None + + def registerInTypeMap(self): + global WrapperClassMap + if self.isTypedObject(): + typeIndex = self.__class__.getClassType().getIndex() + WrapperClassMap[typeIndex] = self.__class__ + + def downcast(self, specificClass): + FFIConstants.notify.info('downcasting from %s to %s' % \ + (self.__class__.__name__, specificClass.__name__)) + downcastChain = getDowncastFunctions(specificClass, self.__class__, []) + FFIConstants.notify.info(downcastChain) + newObject = self + if (downcastChain == None): + return newObject + elif (downcastChain == 1): + return newObject + elif (downcastChain == 0): + return newObject + else: + for downcastFunc in downcastChain: + FFIConstants.notify.info('Downcasting %s using %s' % \ + (newObject.__class__.__name__, downcastFunc)) + newObject = downcastFunc(newObject) + return newObject + + def compareTo(self, other): + # By default, we compare the C++ pointers + # Some classes will override the compareTo operator with their own + # logic in C++ (like vectors and matrices for instance) + try: + if self.this < other.this: + return -1 + if self.this > other.this: + return 1 + else: + return 0 + except: + return 1 + + def __cmp__(self, other): + # Only use the C++ compareTo if they are the same class + if isinstance(other, self.__class__): + return self.compareTo(other) + # Otherwise, they must not be the same + else: + return 1 + + def __hash__(self): + return self.this + + + + + + + + diff --git a/direct/src/ffi/FFIInterrogateDatabase.py b/direct/src/ffi/FFIInterrogateDatabase.py new file mode 100644 index 0000000000..989533aa7c --- /dev/null +++ b/direct/src/ffi/FFIInterrogateDatabase.py @@ -0,0 +1,655 @@ + +# Note: do not import this file directly, it is meant to be used as part of +# a Python script (generatePythonCode) that sets up variables that this +# module depends on + +import string +import os +import compileall + +import FFIEnvironment +import FFITypes +import FFISpecs +import FFIRename +import FFIConstants +import FFIOverload + + +# FFIConstants.notify.setDebug(1) +FFIConstants.notify.info('Importing interrogate library: ' + FFIConstants.InterrogateModuleName) +# Note: we do a from lib import * here because we do not want +# to be dependent on the name of the interrogate library in this code +exec('from ' + FFIConstants.InterrogateModuleName + ' import *') + +# Import all the C++ modules +for CModuleName in FFIConstants.CodeModuleNameList: + FFIConstants.notify.info('Importing code library: ' + CModuleName) + exec('import ' + CModuleName) + +def constructGlobalFile(codeDir): + """ + Open a file that will hold the global values and functions code + """ + file = open(os.path.join(codeDir, FFIConstants.globalModuleName + '.py'), 'w') + return file + +def constructImportFile(codeDir): + """ + Open a file that will hold the global values and functions code + """ + file = open(os.path.join(codeDir, FFIConstants.importModuleName + '.py'), 'w') + return file + +def outputGlobalFileImports(file, methodList): + # Print the standard header + file.write(FFIConstants.generatedHeader) + + # Import the C modules + for CModuleName in FFIConstants.CodeModuleNameList: + file.write('import ' + CModuleName + '\n') + + moduleList = [] + for method in methodList: + returnType = method.typeDescriptor.returnType.recursiveTypeDescriptor() + if (not (returnType.foreignTypeName in moduleList)): + if (returnType.__class__ == FFITypes.ClassTypeDescriptor): + moduleList.append(returnType.foreignTypeName) + + for moduleName in moduleList: + file.write('import ' + moduleName + '\n') + + file.write('\n') + + +def outputImportFileImports(file, typeList): + """ + This is the file that we will import to get all the panda modules + """ + + # Print the standard header + file.write(FFIConstants.generatedHeader) + + file.write('# Import the interrogate module\n') + file.write('import ' + FFIConstants.InterrogateModuleName + '\n') + file.write('\n') + + file.write('# Import the C modules\n') + for CModuleName in FFIConstants.CodeModuleNameList: + file.write('import ' + CModuleName + '\n') + file.write('\n') + + # Filter out only the class and enum type descriptors (not const, pointers, etc) + classTypeList = [] + enumTypeList = [] + for type in typeList: + if (type.__class__ == FFITypes.ClassTypeDescriptor): + if (not type.isNested): + classTypeList.append(type) + elif (type.__class__ == FFITypes.EnumTypeDescriptor): + if (not type.isNested): + enumTypeList.append(type) + + # Sort the types based on inheritance, most generic first + classTypeList.sort(FFIOverload.inheritanceLevelSort) + + moduleList = [] + for type in classTypeList: + moduleList.append(type.foreignTypeName) + + file.write('import FFIExternalObject\n') + file.write('\n') + + file.write('# Import enums into the global name space\n') + for type in enumTypeList: + file.write('from ' + type.enumName + ' import *\n') + file.write('\n') + + file.write('# Import classes\n') + for moduleName in moduleList: + file.write('import ' + moduleName + '\n') + file.write('\n') + + file.write('# Import the global module file into our name space\n') + file.write('from ' + FFIConstants.globalModuleName + ' import *\n') + file.write('\n') + + file.write('# Now generate the classes\n') + for moduleName in moduleList: + file.write(moduleName + '.generateClass_' + moduleName + '()\n') + file.write('\n') + + file.write('# Now put the classes in the wrapper class map\n') + for moduleName in moduleList: + file.write('obj = ' + moduleName + '.' + moduleName + '(None)\n') + file.write('obj.registerInTypeMap()\n') + file.write('\n') + + file.write('# Now copy the classes into our own namespace\n') + for moduleName in moduleList: + file.write(moduleName + ' = ' + moduleName + '.' + moduleName + '\n') + file.write('\n') + +def generateStaticClass(codeDir): + """ + Create a file that will hold the static class definition + """ + file = open(os.path.join(codeDir, FFIConstants.staticModuleName + '.py'), 'w') + # Print the standard header + file.write(FFIConstants.generatedHeader) + file.write('class ' + FFIConstants.staticModuleName + ':\n') + file.write(' def __init__(self, function):\n') + file.write(' self.__call__ = function\n') + file.close() + return file + +def getTypeName(typeIndex, scoped=0): + """ + Return a fully specified type name for this type index + Return the scoped name if asked for it + """ + nameComponents = [] + name = '' + + + if scoped: + typeName = interrogate_type_scoped_name(typeIndex) + else: + typeName = interrogate_type_name(typeIndex) + + if typeIndex == 0: + FFIConstants.notify.debug('typeIndex 0: ' + typeName) + + if interrogate_type_is_wrapped(typeIndex): + typeName = getTypeName(interrogate_type_wrapped_type(typeIndex)) + if interrogate_type_is_const(typeIndex): + nameComponents.append('const') + if interrogate_type_is_pointer(typeIndex): + nameComponents.append('ptr') + if interrogate_type_is_signed(typeIndex): + # signed is now built into the type name + #nameComponents.append('signed') + pass + if interrogate_type_is_unsigned(typeIndex): + # unsigned is now built into the type name + #nameComponents.append('unsigned') + pass + if interrogate_type_is_long(typeIndex): + nameComponents.append('long') + if interrogate_type_is_longlong(typeIndex): + nameComponents.append('longLong') + if interrogate_type_is_short(typeIndex): + nameComponents.append('short') + if (len(nameComponents) > 0): + typeName = string.capitalize(typeName[0]) + typeName[1:] + nameComponents.append(typeName) + for i in range(len(nameComponents)): + if (i == 0): + name = name + nameComponents[i] + else: + name = name + string.capitalize(nameComponents[i][0]) + nameComponents[i][1:] + + FFIConstants.notify.debug('typeIndex: ' + `typeIndex` + ' typeName: ' + typeName + ' has name: ' + name) + + if not name: + FFIConstants.notify.warning('typeIndex: ' + `typeIndex` + ' typeName: ' + typeName + ' has no name') + + return name + + +class FFIInterrogateDatabase: + + def __init__(self): + self.typeIndexMap = {} + self.environment = FFIEnvironment.FFIEnvironment() + + def isDefinedType(self, typeIndex): + return self.typeIndexMap.has_key(typeIndex) + + def constructDescriptor(self, typeIndex): + if interrogate_type_is_atomic(typeIndex): + return self.constructPrimitiveTypeDescriptor(typeIndex) + + elif interrogate_type_is_enum(typeIndex): + return self.constructEnumTypeDescriptor(typeIndex) + + elif interrogate_type_is_wrapped(typeIndex): + if interrogate_type_is_pointer(typeIndex): + return self.constructPointerTypeDescriptor(typeIndex) + elif interrogate_type_is_const(typeIndex): + return self.constructConstTypeDescriptor(typeIndex) + + elif (interrogate_type_is_class(typeIndex) or + interrogate_type_is_struct(typeIndex) or + interrogate_type_is_union(typeIndex)): + return self.constructClassTypeDescriptor(typeIndex) + + elif (not interrogate_type_is_fully_defined(typeIndex)): + return self.constructClassTypeDescriptor(typeIndex) + + else: + raise 'A type in the interrogate database was not recognized: '+ `typeIndex` + + def constructPrimitiveTypeDescriptor(self, typeIndex): + if self.isDefinedType(typeIndex): + return self.typeIndexMap[typeIndex] + else: + descriptor = FFITypes.PrimitiveTypeDescriptor() + #descriptor.environment = self.environment + descriptor.atomicType = interrogate_type_atomic_token(typeIndex) + descriptor.foreignTypeName = \ + FFIRename.nonClassNameFromCppName(getTypeName(typeIndex)) + descriptor.typeIndex = typeIndex + self.typeIndexMap[typeIndex] = descriptor + return descriptor + + def constructEnumTypeDescriptor(self, typeIndex): + if self.isDefinedType(typeIndex): + return self.typeIndexMap[typeIndex] + else: + descriptor = FFITypes.EnumTypeDescriptor() + #descriptor.environment = self.environment + descriptor.isNested = interrogate_type_is_nested(typeIndex) + if descriptor.isNested: + outerTypeIndex = interrogate_type_outer_class(typeIndex) + descriptor.outerType = self.constructDescriptor(outerTypeIndex) + # Enums are ints in C++ but we do not want to redefine the int type + # So we will just call them enums + descriptor.enumName = FFIRename.classNameFromCppName(getTypeName(typeIndex)) + descriptor.foreignTypeName = '__enum__' + descriptor.enumName + numValues = interrogate_type_number_of_enum_values(typeIndex) + + # Store the names and values of the enum in a dictionary + for i in range(numValues): + value = interrogate_type_enum_value(typeIndex, i) + name = FFIRename.classNameFromCppName( + interrogate_type_enum_value_name(typeIndex, i)) + scopedName = FFIRename.classNameFromCppName( + interrogate_type_enum_value_scoped_name(typeIndex, i)) + descriptor.values[name] = value + + descriptor.typeIndex = typeIndex + self.typeIndexMap[typeIndex] = descriptor + return descriptor + + def constructPointerTypeDescriptor(self, typeIndex): + if self.isDefinedType(typeIndex): + return self.typeIndexMap[typeIndex] + descriptor = FFITypes.PointerTypeDescriptor() + #descriptor.environment = self.environment + descriptor.isNested = interrogate_type_is_nested(typeIndex) + if descriptor.isNested: + outerTypeIndex = interrogate_type_outer_class(typeIndex) + descriptor.outerType = self.constructDescriptor(outerTypeIndex) + descriptor.foreignTypeName = \ + FFIRename.nonClassNameFromCppName(getTypeName(typeIndex)) + descriptor.typeIndex = typeIndex + wrappedTypeIndex = interrogate_type_wrapped_type(typeIndex) + wrappedTypeDescriptor = self.constructDescriptor(wrappedTypeIndex) + descriptor.typeDescriptor = wrappedTypeDescriptor + self.typeIndexMap[typeIndex] = descriptor + return descriptor + + def constructConstTypeDescriptor(self, typeIndex): + if self.isDefinedType(typeIndex): + return self.typeIndexMap[typeIndex] + descriptor = FFITypes.ConstTypeDescriptor() + #descriptor.environment = self.environment + descriptor.isNested = interrogate_type_is_nested(typeIndex) + if descriptor.isNested: + outerTypeIndex = interrogate_type_outer_class(typeIndex) + descriptor.outerType = self.constructDescriptor(outerTypeIndex) + descriptor.foreignTypeName = \ + FFIRename.nonClassNameFromCppName(getTypeName(typeIndex)) + descriptor.typeIndex = typeIndex + wrappedTypeIndex = interrogate_type_wrapped_type(typeIndex) + wrappedTypeDescriptor = self.constructDescriptor(wrappedTypeIndex) + descriptor.typeDescriptor = wrappedTypeDescriptor + self.typeIndexMap[typeIndex] = descriptor + return descriptor + + def constructParentTypeDescriptors(self, typeIndex): + numParents = interrogate_type_number_of_derivations(typeIndex) + descriptors = [] + for i in range(numParents): + parentTypeIndex = interrogate_type_get_derivation(typeIndex, i) + if self.isDefinedType(parentTypeIndex): + parentTypeDescriptor = self.typeIndexMap[parentTypeIndex] + else: + parentTypeDescriptor = self.constructDescriptor(parentTypeIndex) + descriptors.append(parentTypeDescriptor) + return descriptors + + def constructNestedTypeDescriptors(self, typeIndex): + nestedTypes = [] + numNestedTypes = interrogate_type_number_of_nested_types(typeIndex) + for i in range(numNestedTypes): + nestedTypeIndex = interrogate_type_get_nested_type(typeIndex, i) + descriptor = self.constructDescriptor(nestedTypeIndex) + nestedTypes.append(descriptor) + return nestedTypes + + def constructClassTypeDescriptor(self, typeIndex): + if self.isDefinedType(typeIndex): + return self.typeIndexMap[typeIndex] + descriptor = FFITypes.ClassTypeDescriptor() + self.typeIndexMap[typeIndex] = descriptor + #descriptor.environment = self.environment + descriptor.isNested = interrogate_type_is_nested(typeIndex) + if descriptor.isNested: + outerTypeIndex = interrogate_type_outer_class(typeIndex) + descriptor.outerType = self.constructDescriptor(outerTypeIndex) + descriptor.foreignTypeName = FFIRename.classNameFromCppName(getTypeName(typeIndex)) + if FFIConstants.wantComments: + if interrogate_type_has_comment(typeIndex): + descriptor.comment = interrogate_type_comment(typeIndex) + descriptor.typeIndex = typeIndex + descriptor.instanceMethods = self.constructMemberFunctionSpecifications(typeIndex) + descriptor.upcastMethods = self.constructUpcastFunctionSpecifications(typeIndex) + # Constructing downcasts does not return the functions, it just puts them in the class + # See the comment in that function + self.constructDowncastFunctionSpecifications(typeIndex) + descriptor.filterOutStaticMethods() + descriptor.constructors = self.constructConstructorSpecifications(typeIndex) + descriptor.destructor = self.constructDestructorSpecification(typeIndex) + descriptor.parentTypes = self.constructParentTypeDescriptors(typeIndex) + descriptor.nestedTypes = self.constructNestedTypeDescriptors(typeIndex) + return descriptor + + def constructFunctionTypeDescriptors(self, functionIndex): + + # Store these values because they will be the same for all the wrappers + isVirtual = interrogate_function_is_virtual(functionIndex) + #environment = self.environment + foreignTypeName = interrogate_function_name(functionIndex) + if FFIConstants.wantComments: + prototype = interrogate_function_prototype(functionIndex) + if interrogate_function_has_comment(functionIndex): + comment = interrogate_function_comment(functionIndex) + else: + comment = '' + # Prepend lib to the module name it reports because that will be the name of + # the Python module we import. This is apparently stems from a makefile + # discrepency in the way we build the libraries + moduleName = 'lib' + interrogate_function_module_name(functionIndex) + typeIndex = functionIndex + + # Look at the Python wrappers for this function + numPythonWrappers = interrogate_function_number_of_python_wrappers(functionIndex) + + if numPythonWrappers == 0: + # If there are no Python wrappers, it is because interrogate could not handle + # something about the function. Just return an empty list + return [] + + wrapperDescriptors = [] + + # Iterate over the wrappers constructing a FunctionTypeDescriptor for each + for i in range(numPythonWrappers): + descriptor = FFITypes.FunctionTypeDescriptor() + descriptor.isVirtual = isVirtual + #descriptor.environment = environment + descriptor.foreignTypeName = foreignTypeName + if FFIConstants.wantComments: + descriptor.comment = comment + descriptor.prototype = prototype + descriptor.moduleName = moduleName + descriptor.typeIndex = typeIndex + pythonFunctionIndex = interrogate_function_python_wrapper(functionIndex, i) + descriptor.wrapperName = interrogate_wrapper_name(pythonFunctionIndex) + # Even if it does not have a return value, it reports void which is better + # for generating code, so I will not even ask here + # if interrogate_wrapper_has_return_value(pythonFunctionIndex): + returnType = interrogate_wrapper_return_type(pythonFunctionIndex) + descriptor.returnType = self.constructDescriptor(returnType) + descriptor.argumentTypes = self.constructFunctionArgumentTypes(pythonFunctionIndex) + descriptor.userManagesMemory = interrogate_wrapper_caller_manages_return_value(pythonFunctionIndex) + descriptor.returnValueDestructor = interrogate_wrapper_return_value_destructor(pythonFunctionIndex) + wrapperDescriptors.append(descriptor) + + return wrapperDescriptors + + def constructFunctionArgumentTypes(self, functionIndex): + numArgs = interrogate_wrapper_number_of_parameters(functionIndex) + arguments = [] + for argIndex in range(numArgs): + if interrogate_wrapper_parameter_has_name(functionIndex, argIndex): + name = FFIRename.nonClassNameFromCppName( + interrogate_wrapper_parameter_name(functionIndex, argIndex)) + else: + name = ('parameter' + `argIndex`) + descriptor = self.constructDescriptor( + interrogate_wrapper_parameter_type(functionIndex, argIndex)) + + argSpec = FFISpecs.MethodArgumentSpecification() + if interrogate_wrapper_parameter_is_this(functionIndex, argIndex): + argSpec.isThis = 1 + argSpec.name = name + argSpec.typeDescriptor = descriptor + arguments.append(argSpec) + return arguments + + def constructMemberFunctionSpecifications(self, typeIndex): + funcSpecs = [] + numFuncs = interrogate_type_number_of_methods(typeIndex) + for i in range(numFuncs): + funcIndex = interrogate_type_get_method(typeIndex, i) + typeDescs = self.constructFunctionTypeDescriptors(funcIndex) + for typeDesc in typeDescs: + funcSpec = FFISpecs.MethodSpecification() + funcSpec.name = FFIRename.methodNameFromCppName( + interrogate_function_name(funcIndex)) + funcSpec.typeDescriptor = typeDesc + funcSpec.index = funcIndex + funcSpecs.append(funcSpec) + return funcSpecs + + def constructUpcastFunctionSpecifications(self, typeIndex): + funcSpecs = [] + numFuncs = interrogate_type_number_of_derivations(typeIndex) + for i in range(numFuncs): + if interrogate_type_derivation_has_upcast(typeIndex, i): + funcIndex = interrogate_type_get_upcast(typeIndex, i) + typeDescs = self.constructFunctionTypeDescriptors(funcIndex) + for typeDesc in typeDescs: + funcSpec = FFISpecs.MethodSpecification() + funcSpec.name = FFIRename.methodNameFromCppName( + interrogate_function_name(funcIndex)) + funcSpec.typeDescriptor = typeDesc + funcSpec.index = funcIndex + funcSpecs.append(funcSpec) + return funcSpecs + + def constructDowncastFunctionSpecifications(self, typeIndex): + """ + The strange thing about downcast functions is that they appear in the + class they are being downcast TO, not downcast FROM. But they should be + built into the class they are being downcast from. For instance, a method + downcastToNode(ptrBoundedObject) will appear in Node's list of methods + but should be compiled into BoundedObject's class + """ + numFuncs = interrogate_type_number_of_derivations(typeIndex) + for i in range(numFuncs): + # Make sure this downcast is possible + if (not interrogate_type_derivation_downcast_is_impossible(typeIndex, i)): + if interrogate_type_derivation_has_downcast(typeIndex, i): + funcIndex = interrogate_type_get_downcast(typeIndex, i) + typeDescs = self.constructFunctionTypeDescriptors(funcIndex) + for typeDesc in typeDescs: + funcSpec = FFISpecs.MethodSpecification() + funcSpec.name = FFIRename.methodNameFromCppName( + interrogate_function_name(funcIndex)) + funcSpec.typeDescriptor = typeDesc + funcSpec.index = funcIndex + # Here we look for the class in the first argument + fromClass = typeDesc.argumentTypes[0].typeDescriptor.recursiveTypeDescriptor() + # Append this funcSpec to that class's downcast methods + fromClass.downcastMethods.append(funcSpec) + + def constructConstructorSpecifications(self, typeIndex): + funcSpecs = [] + numFuncs = interrogate_type_number_of_constructors(typeIndex) + for i in range(numFuncs): + funcIndex = interrogate_type_get_constructor(typeIndex, i) + typeDescs = self.constructFunctionTypeDescriptors(funcIndex) + for typeDesc in typeDescs: + funcSpec = FFISpecs.MethodSpecification() + funcSpec.name = 'constructor' + # funcSpec.name = FFIRename.methodNameFromCppName( + # interrogate_function_name(funcIndex)) + funcSpec.typeDescriptor = typeDesc + funcSpec.index = funcIndex + funcSpecs.append(funcSpec) + return funcSpecs + + def constructDestructorSpecification(self, typeIndex): + if (not interrogate_type_has_destructor(typeIndex)): + return None + funcIndex = interrogate_type_get_destructor(typeIndex) + typeDescs = self.constructFunctionTypeDescriptors(funcIndex) + if (len(typeDescs) == 0): + return None + for typeDesc in typeDescs: + funcSpec = FFISpecs.MethodSpecification() + funcSpec.name = 'destructor' + # funcSpec.name = FFIRename.methodNameFromCppName( + # interrogate_function_name(funcIndex)) + funcSpec.typeDescriptor = typeDesc + funcSpec.index = funcIndex + return funcSpec + + def addTypes(self): + for i in range(interrogate_number_of_global_types()): + self.constructDescriptor(interrogate_get_global_type(i)) + + def addEnvironmentTypes(self): + for descriptor in self.typeIndexMap.values(): + self.environment.addType(descriptor, descriptor.foreignTypeName) + + def constructGlobal(self, globalIndex): + # We really do not need the descriptor for the value, just + # the getter and setter + # descriptor = self.typeIndexMap[interrogate_element_type(globalIndex)] + if interrogate_element_has_getter(globalIndex): + getterIndex = interrogate_element_getter(globalIndex) + getter = self.constructGlobalFunction(getterIndex) + else: + getter = None + + if interrogate_element_has_setter(globalIndex): + setterIndex = interrogate_element_setter(globalIndex) + setter = self.constructGlobalFunction(setterIndex) + else: + setter = None + globalSpec = FFISpecs.GlobalValueSpecification() + globalSpec.getter = getter + globalSpec.setter = setter + # globalSpec.typeDescriptor = descriptor + cppName = interrogate_element_name(globalIndex) + globalSpec.name = FFIRename.classNameFromCppName(cppName) + return globalSpec + + def constructGlobalFunction(self, globalIndex): + descriptors = self.constructFunctionTypeDescriptors(globalIndex) + if (len(descriptors) == 0): + return None + for descriptor in descriptors: + funcSpec = FFISpecs.GlobalFunctionSpecification() + funcSpec.typeDescriptor = descriptor + funcSpec.name = FFIRename.methodNameFromCppName( + funcSpec.typeDescriptor.foreignTypeName) + funcSpec.index = globalIndex + return funcSpec + + def addGlobalFunctions(self): + numGlobals = interrogate_number_of_global_functions() + for i in range(numGlobals): + funcIndex = interrogate_get_global_function(i) + newGlob = self.constructGlobalFunction(funcIndex) + if newGlob: + self.environment.addGlobalFunction(newGlob) + + # Take all the global functions that have a Panda Class as their + # first argument and make them class methods on that class + # For example the global function + # get_distance(node1, node2) + # becomes: + # node1.getDistance(node2) + + # Functions that do not get moved will be stored here temporarily + tempGlobalFunctions = [] + for funcSpec in self.environment.globalFunctions: + # If there are any arguments + if (len(funcSpec.typeDescriptor.argumentTypes) > 0): + # If the first argument is a class type descriptor + methodArgSpec = funcSpec.typeDescriptor.argumentTypes[0] + argBaseType = methodArgSpec.typeDescriptor.recursiveTypeDescriptor() + if isinstance(argBaseType, FFITypes.ClassTypeDescriptor): + # Move this global function into the class + argBaseType.globalMethods.append(funcSpec) + else: + # Copy this function into the temp list + tempGlobalFunctions.append(funcSpec) + else: + # Copy this function into the temp list + tempGlobalFunctions.append(funcSpec) + # Now copy the temp list back over the real list + self.environment.globalFunctions = tempGlobalFunctions + + def addGlobalValues(self): + numGlobals = interrogate_number_of_globals() + for i in range(numGlobals): + globalIndex = interrogate_get_global(i) + newGlob = self.constructGlobal(globalIndex) + self.environment.addGlobalValue(newGlob) + + def generateCode(self, codeDir, extensionsDir): + FFIConstants.notify.info( 'Generating static class...') + generateStaticClass(codeDir) + + FFIConstants.notify.info( 'Generating type code...') + for type in self.environment.types.values(): + # Do not generate code for nested types at the top level + if (not type.isNested): + type.generateGlobalCode(codeDir, extensionsDir) + + FFIConstants.notify.info( 'Generating global value code...') + globalFile = constructGlobalFile(codeDir) + + # Make a list of all the global functions. This includes the normal + # global functions as well as the getters and setters on all the + # global values. This list is used to figure out what files to import + globalFunctions = self.environment.globalFunctions + for globalValue in self.environment.globalValues: + if globalValue.getter: + globalFunctions.append(globalValue.getter) + if globalValue.setter: + globalFunctions.append(globalValue.setter) + # Output all the imports based on this list of functions + outputGlobalFileImports(globalFile, globalFunctions) + + for type in self.environment.globalValues: + type.generateGlobalCode(globalFile) + + FFIConstants.notify.info( 'Generating global function code...') + for type in self.environment.globalFunctions: + type.generateGlobalCode(globalFile) + + globalFile.close() + + FFIConstants.notify.info( 'Generating import code...') + importFile = constructImportFile(codeDir) + outputImportFileImports(importFile, self.environment.types.values()) + + + compileall.compile_dir(codeDir) + + + def updateBindings(self): + FFIConstants.notify.info( 'Updating Bindings') + FFIConstants.notify.info( 'Adding Types...') + self.addTypes() + FFIConstants.notify.info( 'Adding global values...') + self.addGlobalValues() + FFIConstants.notify.info( 'Adding global functions...') + self.addGlobalFunctions() + self.addEnvironmentTypes() diff --git a/direct/src/ffi/FFIOverload.py b/direct/src/ffi/FFIOverload.py new file mode 100644 index 0000000000..1334ff2141 --- /dev/null +++ b/direct/src/ffi/FFIOverload.py @@ -0,0 +1,280 @@ + +from PythonUtil import * +from types import * +import string +import FFIConstants + +""" +Things that are not supported: + - Overloading a function based on an enum being differentiated from an int + - Type names from C++ cannot begin with __enum__ +""" + +AT_not_atomic = 0 +AT_int = 1 +AT_float = 2 +AT_double = 3 +AT_bool = 4 +AT_char = 5 +AT_void = 6 +AT_string = 7 + +def getTypeName(classTypeDesc, typeDesc): + """ + Map the interrogate primitive type names to python type names. + We assume that the module using this has imported the types module. + It is valid to pass in None for classTypeDesc if we are not in a class + """ + + typeName = typeDesc.getFullNestedName() + + # Atomic C++ types are type checked against the builtin + # Python types. This code sorts out the mapping + if typeDesc.isAtomic(): + + # Ints and bools are treated as ints. + # Enums are special and are not atomic, see below + if ((typeDesc.atomicType == AT_int) or + (typeDesc.atomicType == AT_bool)): + return 'types.IntType' + + # Floats and doubles are both floats in Python + elif ((typeDesc.atomicType == AT_float) or + (typeDesc.atomicType == AT_double)): + return 'types.FloatType' + + # Strings and individual chars are treated as Python strings + elif ((typeDesc.atomicType == AT_char) or + (typeDesc.atomicType == AT_string)): + return 'types.StringType' + + elif (typeDesc.atomicType == AT_void): + # Convert the void type to None type... I guess... + # So far we do not have any code that uses this + return 'types.NoneType' + + # If the type is an enum, we really want to treat it like an int + # To handle this, the type will have __enum__ prepended to the name + elif (typeName[0:8] == '__enum__'): + return 'types.IntType' + + # If it was not atomic or enum, it must be a class which is a + # bit trickier because we output different things depending on the + # scoping of the type. + else: + # Assuming the class and the module are the same name, return + # typeName.typeName (ie Node.Node) + # Unless we are in the same module. For instance, in Node.py, + # Node.Node is not defined, so just return Node. + nestedTypes = string.split(typeName, '.') + if (classTypeDesc and (classTypeDesc.foreignTypeName in nestedTypes)): + # Return the last type (SubClass) in the nested types + return nestedTypes[-1] + else: + # Return the full Module.Class.SubClass + return (nestedTypes[0] + '.' + typeName) + + +def inheritsFrom(type1, type2): + """ + Return true if type1 inherits from type2 + This works by recursively checking parentTypes for type1 + """ + if type1.parentTypes: + if type2 in type1.parentTypes: + return 1 + else: + result = 0 + for type in type1.parentTypes: + result = (result or inheritsFrom(type, type2)) + return result + else: + return 0 + +def getInheritanceLevel(type): +# if (len(type.parentTypes) == 0): +# return 0 + level = 0 + for parentType in type.parentTypes: + level = max(level, 1+getInheritanceLevel(parentType)) + for nestedType in type.nestedTypes: + level = max(level, 1+getInheritanceLevel(nestedType)) + return level + +def inheritanceLevelSort(type1, type2): + level1 = getInheritanceLevel(type1) + level2 = getInheritanceLevel(type2) + if (level1 == level2): + return 0 + elif (level1 < level2): + return -1 + elif (level1 > level2): + return 1 + + +def subclass(type1, type2): + """ + Helper funcion used in sorting classes by inheritance + """ + # If the types are the same, return 0 + if type1 == type2: + return 0 + # If you have no args, sort you first + elif (type1 == 0): + return 1 + elif (type2 == 0): + return -1 + # If class1 inherits from class2 return 1 + elif inheritsFrom(type1, type2): + return 1 + # If class2 inherits from class1 return -1 + elif inheritsFrom(type2, type1): + return -1 + else: + # This is the dont care case. We must specify a sorting + # rule just so it is not arbitrary + if (type1.foreignTypeName > type2.foreignTypeName): + return -1 + else: + return 1 + + +class FFIMethodArgumentTreeCollection: + def __init__(self, classTypeDesc, methodSpecList): + self.classTypeDesc = classTypeDesc + self.methodSpecList = methodSpecList + self.methodDict = {} + self.treeDict = {} + + def outputOverloadedMethodHeader(self, file): + indent(file, 1, 'def ' + self.methodSpecList[0].name + + '(self, *_args):\n') + indent(file, 2, 'numArgs = len(_args)\n') + + def outputOverloadedMethodFooter(self, file): + # If the overloaded function got all the way through the if statements + # it must have had the wrong number or type of arguments + indent(file, 2, "raise TypeError, 'Invalid arguments'\n\n") + + def setup(self): + for method in self.methodSpecList: + numArgs = len(method.typeDescriptor.thislessArgTypes()) + numArgsList = ifAbsentPut(self.methodDict, numArgs, []) + numArgsList.append(method) + for numArgs in self.methodDict.keys(): + methodList = self.methodDict[numArgs] + tree = FFIMethodArgumentTree(self.classTypeDesc, methodList) + treeList = ifAbsentPut(self.treeDict, numArgs, []) + treeList.append(tree) + + def generateCode(self, file, nesting): + self.setup() + self.outputOverloadedMethodHeader(file) + numArgsKeys = self.treeDict.keys() + numArgsKeys.sort() + for numArgs in numArgsKeys: + trees = self.treeDict[numArgs] + for tree in trees: + indent(file, 2, 'if (numArgs == ' + `numArgs` + '):\n') + tree.setup() + tree.traverse(file) + self.outputOverloadedMethodFooter(file) + +class FFIMethodArgumentTree: + """ + Tree is made from nested dictionaries. + The keys are methodNamed. + The values are [tree, methodSpec] + methodSpec may be None at any level + If tree is None, it is a leaf node and methodSpec will be defined + """ + def __init__(self, classTypeDesc, methodSpecList): + self.argSpec = None + self.classTypeDesc = classTypeDesc + self.methodSpecList = methodSpecList + # The actual tree is implemented as nested dictionaries + self.tree = {} + + def setup(self): + for methodSpec in self.methodSpecList: + argTypes = methodSpec.typeDescriptor.thislessArgTypes() + self.fillInArgTypes(argTypes, methodSpec) + + def fillInArgTypes(self, argTypes, methodSpec): + # If the method takes no arguments, we will assign a type index of 0 + if (len(argTypes) == 0): + self.tree[0] = [ + FFIMethodArgumentTree(self.classTypeDesc, + self.methodSpecList), + methodSpec] + + else: + self.argSpec = argTypes[0] + typeDesc = self.argSpec.typeDescriptor.recursiveTypeDescriptor() + + if (len(argTypes) == 1): + # If this is the last parameter, we are a leaf node, so store the + # methodSpec in this dictionary + self.tree[typeDesc] = [None, methodSpec] + else: + if self.tree.has_key(typeDesc): + # If there already is a tree here, jump into and pass the + # cdr of the arg list + subTree = self.tree[typeDesc][0] + subTree.fillInArgTypes(argTypes[1:], methodSpec) + else: + # Add a subtree for the rest of the arg list + subTree = FFIMethodArgumentTree(self.classTypeDesc, + self.methodSpecList) + subTree.fillInArgTypes(argTypes[1:], methodSpec) + # This subtree has no method spec + self.tree[typeDesc] = [subTree, None] + + def traverse(self, file, level=1): + # Make a copy of the keys so we can sort them in place + sortedKeys = self.tree.keys() + # Sort the keys based on inheritance hierarchy, most generic classes first + sortedKeys.sort(subclass) + for typeDesc in sortedKeys: + # See if this takes no arguments + if (typeDesc == 0): + # Output the function + methodSpec = self.tree[0][1] + indent(file, level+2, 'return ') + methodSpec.outputOverloadedCall(file, 0) + else: + typeName = getTypeName(self.classTypeDesc, typeDesc) + indent(file, level+2, 'if (isinstance(_args[' + `level-1` + '], ' + + typeName + + '))') + # If it is looking for a float, make it accept an integer too + if (typeName == 'types.FloatType'): + file.write(' or (isinstance(_args[' + `level-1` + '], ' + + 'types.IntType' + + '))') + file.write(':\n') + if (self.tree[typeDesc][0] != None): + self.tree[typeDesc][0].traverse(file, level+1) + else: + # Output the function + methodSpec = self.tree[typeDesc][1] + indent(file, level+3, 'return ') + numArgs = level + methodSpec.outputOverloadedCall(file, numArgs) + + + + + + + + + + + + + + + + + diff --git a/direct/src/ffi/FFIRename.py b/direct/src/ffi/FFIRename.py new file mode 100644 index 0000000000..14ba4e0330 --- /dev/null +++ b/direct/src/ffi/FFIRename.py @@ -0,0 +1,145 @@ +import FFIConstants +from string import * + + +pythonKeywords = ['and','del','for','is','raise','assert','elif','from','lambda','return','break','else','global','not','try','class','except','if','or','while','continue','exec','import','pass','def','finally','in','print'] + + +methodRenameDictionary = { + 'operator==' : 'eq', + 'operator!=' : 'ne', + 'operator<<' : '__lshift__', + 'operator>>' : '__rshift__', + 'operator<' : 'lessThan', + 'operator>' : 'greaterThan', + 'operator<=' : 'lessThanOrEqual', + 'operator>=' : 'greaterThanOrEqual', + 'operator=' : 'assign', + 'operator()' : '__call__', + 'operator[]' : '__getitem__', + 'operator++' : 'increment', + 'operator--' : 'decrement', + 'operator^' : '__xor__', + 'operator%' : '__mod__', + 'operator!' : 'logicalNot', + 'operator~' : 'bitwiseNot', + 'operator&' : '__and__', + 'operator&&' : 'logicalAnd', + 'operator|' : '__or__', + 'operator||' : 'logicalOr', + 'operator+' : '__add__', + 'operator-' : '__sub__', + 'operator*' : '__mul__', + 'operator/' : '__div__', + 'operator+=' : 'addEqual', + 'operator-=' : 'subtractEqual', + 'operator*=' : 'multiplyEqual', + 'operator/=' : 'divEqual', + 'operator,' : 'concatenate', + 'operator|=' : 'bitwiseOrEqual', + 'operator&=' : 'bitwiseAndEqual', + 'operator^=' : 'bitwiseXorEqual', + 'operator~=' : 'bitwiseNotEqual', + 'operator->' : 'dereference', + 'operator<<=' : 'leftShiftEqual', + 'operator>>=' : 'rightShiftEqual', + 'print' : 'Cprint' + } + +classRenameDictionary = { + 'Loader' : 'PandaLoader', + 'String' : 'CString', + 'LMatrix4f' : 'Mat4', + 'LMatrix3f' : 'Mat3', + 'LVecBase4f' : 'VBase4', + 'LVector4f' : 'Vec4', + 'LPoint4f' : 'Point4', + 'LVecBase3f' : 'VBase3', + 'LVector3f' : 'Vec3', + 'LPoint3f' : 'Point3', + 'LVecBase2f' : 'VBase2', + 'LVector2f' : 'Vec2', + 'LPoint2f' : 'Point2', + 'LMatrix4d' : 'Mat4D', + 'LMatrix3d' : 'Mat3D', + 'LVecBase4d' : 'VBase4D', + 'LVector4d' : 'Vec4D', + 'LPoint4d' : 'Point4D', + 'LVecBase3d' : 'VBase3D', + 'LVector3d' : 'Vec3D', + 'LPoint3d' : 'Point3D', + 'LVecBase2d' : 'VBase2D', + 'LVector2d' : 'Vec2D', + 'LPoint2d' : 'Point2D', + 'Plane' : 'PlaneBase', + 'Planef' : 'Plane', + 'Planed' : 'PlaneD', + 'Frustum' : 'FrustumBase', + 'Frustumf' : 'Frustum', + 'Frustumd' : 'FrustumD' + } + + +def checkKeyword(cppName): + if cppName in pythonKeywords: + cppName = '_' + cppName + return cppName + +# TODO: Make faster - this thing is horribly slow +def classNameFromCppName(cppName): + # initialize to empty string + className = '' + # These are the characters we want to strip out of the name + badChars = '!@#$%^&*()<>,.-=+~{}? ' + nextCap = 0 + firstChar = 1 + for char in cppName: + if (char in badChars): + continue + elif (char == '_'): + nextCap = 1 + continue + elif (nextCap or firstChar): + className = className + capitalize(char) + nextCap = 0 + firstChar = 0 + else: + className = className + char + if classRenameDictionary.has_key(className): + className = classRenameDictionary[className] + + if (className == ''): + FFIConstants.notify.warning('Renaming class: ' + cppName + ' to empty string') + # FFIConstants.notify.debug('Renaming class: ' + cppName + ' to: ' + className) + # Note we do not have to check for keywords because class name are capitalized + return className + +def nonClassNameFromCppName(cppName): + className = classNameFromCppName(cppName) + # Make the first character lowercase + newName = lower(className[0])+className[1:] + # Mangle names that happen to be python keywords so they are not anymore + newName = checkKeyword(newName) + return newName + +def methodNameFromCppName(cppName): + methodName = '' + badChars = ' ' + nextCap = 0 + for char in cppName: + if (char in badChars): + continue + elif (char == '_'): + nextCap = 1 + continue + elif nextCap: + methodName = methodName + capitalize(char) + nextCap = 0 + else: + methodName = methodName + char + if methodRenameDictionary.has_key(methodName): + methodName = methodRenameDictionary[methodName] + # Mangle names that happen to be python keywords so they are not anymore + methodName = checkKeyword(methodName) + return methodName + diff --git a/direct/src/ffi/FFISpecs.py b/direct/src/ffi/FFISpecs.py new file mode 100644 index 0000000000..022883e19a --- /dev/null +++ b/direct/src/ffi/FFISpecs.py @@ -0,0 +1,499 @@ + +import FFIConstants +import FFITypes +import FFIOverload +import string + +from PythonUtil import * + +class FunctionSpecification: + def __init__(self): + self.name = '' + self.typeDescriptor = None + self.index = 0 + self.overloaded = 0 + + def outputTypeChecking(self, methodClass, args, file, nesting): + """ + Output an assert statement to check the type of each arg in this method + This can be turned off with a command line parameter in generatePythonCode + It is valid to pass in None for methodClass if you are not in any methodClass + """ + if FFIConstants.wantTypeChecking: + for methodArgSpec in args: + typeDesc = methodArgSpec.typeDescriptor.recursiveTypeDescriptor() + typeName = FFIOverload.getTypeName(methodClass, typeDesc) + + # Special case: + # If it is looking for a float, accept an int as well + # C++ will cast it properly, and it is much more convenient + if (typeName == 'types.FloatType'): + indent(file, nesting, 'assert((isinstance(' + + methodArgSpec.name + ', types.FloatType) or isinstance(' + + methodArgSpec.name + ', types.IntType)))\n') + else: + indent(file, nesting, 'assert(isinstance(' + + methodArgSpec.name + ', ' + typeName + '))\n') + + def outputCFunctionComment(self, file, nesting): + """ + Output a docstring to the file describing the C++ call with type info + Also output the C++ comment from interrogate. + """ + if FFIConstants.wantComments: + indent(file, nesting, '"""\n') + + # Output the function prototype + if self.typeDescriptor.prototype: + indent(file, nesting, self.typeDescriptor.prototype + '\n') + + # Output the function comment + if self.typeDescriptor.comment: + # To insert tabs into the comment, replace all newlines with a newline+tabs + comment = string.replace(self.typeDescriptor.comment, + '\n', ('\n' + (' ' * nesting))) + indent(file, nesting, comment) + + indent(file, 0, '\n') + indent(file, nesting, '"""\n') + + def getFinalName(self): + """ + Return the name of the function given that it might be overloaded + If it is overloaded, prepend "overloaded", then append the types of + each argument to make it unique. + + So "getChild(int)" becomes "overloaded_getChild_int(int)" + """ + if self.overloaded: + name = 'overloaded_' + self.name + for methodArgSpec in self.typeDescriptor.argumentTypes: + name = name + '_' + methodArgSpec.typeDescriptor.foreignTypeName + return name + else: + return self.name + + def outputOverloadedCall(self, file, numArgs): + """ + Write the function call to call this overloaded method + For example: + self.overloaded_setPos_ptrNodePath_float_float_float(_args[0], _args[1], _args[2]) + """ + indent(file, 0, 'self.' + self.getFinalName() + '(') + for i in range(numArgs): + file.write('_args[' + `i` + ']') + if (i != (numArgs - 1)): + file.write(', ') + file.write(')\n') + + +class GlobalFunctionSpecification(FunctionSpecification): + def __init__(self): + FunctionSpecification.__init__(self) + + # Use generateCode when creating a global (non-class) function + def generateGlobalCode(self, file): + self.outputHeader(file) + self.outputBody(file) + self.outputFooter(file) + # Use generateMethodCode when creating a global->class function + def generateMethodCode(self, methodClass, file, nesting): + self.outputMethodHeader(methodClass, file, nesting) + self.outputMethodBody(methodClass, file, nesting) + self.outputMethodFooter(methodClass, file, nesting) + def generateInheritedUpcastMethodCode(self, methodClass, parentClass, file, nesting): + self.outputInheritedUpcastMethodHeader(methodClass, parentClass, file, nesting) + self.outputInheritedUpcastMethodBody(methodClass, parentClass, file, nesting) + self.outputInheritedUpcastMethodFooter(methodClass, parentClass, file, nesting) + + ################################################## + ## Global Function Code Generation + ################################################## + def outputHeader(self, file): + argTypes = self.typeDescriptor.argumentTypes + indent(file, 0, 'def ' + self.getFinalName() + '(') + for i in range(len(argTypes)): + file.write(argTypes[i].name) + if (i < (len(argTypes)-1)): + file.write(', ') + file.write('):\n') + def outputBody(self, file): + # The method body will look something like + # returnValue = PandaGlobal.method(arg) + # returnObject = NodePath() + # returnObject.this = returnValue + # returnObject.userManagesMemory = 1 (optional) + # return returnObject + self.outputCFunctionComment(file, 1) + argTypes = self.typeDescriptor.argumentTypes + self.outputTypeChecking(None, argTypes, file, 1) + indent(file, 1, 'returnValue = ' + self.typeDescriptor.moduleName + + '.' + self.typeDescriptor.wrapperName + '(') + for i in range(len(argTypes)): + file.write(argTypes[i].passName()) + if (i < (len(argTypes)-1)): + file.write(', ') + file.write(')\n') + returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() + returnType.generateReturnValueWrapper(file, self.typeDescriptor.userManagesMemory, 1, 1) + + def outputFooter(self, file): + indent(file, 0, '\n') + + ################################################## + ## Class Method Code Generation + ################################################## + def outputMethodHeader(self, methodClass, file, nesting): + argTypes = self.typeDescriptor.argumentTypes + indent(file, nesting+1, 'def ' + self.getFinalName() + '(') + for i in range(len(argTypes)): + # Instead of the first argument, put self + if (i == 0): + file.write('self') + else: + file.write(argTypes[i].name) + if (i < (len(argTypes)-1)): + file.write(', ') + file.write('):\n') + + def outputMethodBody(self, methodClass, file, nesting): + # The method body will look something like + # returnValue = PandaGlobal.method(self.this, arg) + # returnValue.userManagesMemory = 1 (optional) + # return returnValue + self.outputCFunctionComment(file, nesting+2) + argTypes = self.typeDescriptor.argumentTypes + self.outputTypeChecking(methodClass, argTypes[1:], file, nesting+2) + indent(file, nesting+2, 'returnValue = ' + self.typeDescriptor.moduleName + + '.' + self.typeDescriptor.wrapperName + '(') + for i in range(len(argTypes)): + # Instead of the first argument, put self.this + if (i == 0): + file.write('self.this') + else: + file.write(argTypes[i].passName()) + if (i < (len(argTypes)-1)): + file.write(', ') + file.write(')\n') + returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() + returnType.generateReturnValueWrapper(file, self.typeDescriptor.userManagesMemory, 1, nesting+2) + + def outputMethodFooter(self, methodClass, file, nesting): + indent(file, nesting+1, '\n') + + ################################################## + ## Upcast Class Method Code Generation + ################################################## + def outputInheritedUpcastMethodHeader(self, methodClass, parentClass, file, nesting): + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + indent(file, nesting+1, 'def ' + self.getFinalName() + '(self') + if (len(thislessArgTypes) > 0): + file.write(', ') + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].name) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write('):\n') + + def outputInheritedUpcastMethodBody(self, methodClass, parentClass, file, nesting): + # The method body will look something like + # upcastSelf = self.upcastToParentClass() + # returnValue = ParentClass.method(upcastSelf, arg) + # returnValue.userManagesMemory = 1 (optional) + # return returnValue + self.outputCFunctionComment(file, nesting+2) + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + self.outputTypeChecking(methodClass, thislessArgTypes, file, nesting+2) + if self.typeDescriptor.userManagesMemory: + indent(file, nesting+2, 'self.userManagesMemory = 1\n') + indent(file, nesting+2, 'upcastSelf = self.upcast' + 'To' + + parentClass.foreignTypeName + '()\n') + indent(file, nesting+2, 'returnValue = ' + parentClass.foreignTypeName + + '.' + self.typeDescriptor.wrapperName + '(upcastSelf.this') + if (len(thislessArgTypes) > 0): + file.write(', ') + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].passName()) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write(')\n') + returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() + # Generate the return value code with no downcast instructions + returnType.generateReturnValueWrapper(file, self.typeDescriptor.userManagesMemory, 1, nesting+2) + + def outputInheritedUpcastMethodFooter(self, methodClass, parentClass, file, nesting): + pass + + +class MethodSpecification(FunctionSpecification): + def __init__(self): + FunctionSpecification.__init__(self) + def isStatic(self): + for arg in self.typeDescriptor.argumentTypes: + if arg.isThis: + return 0 + return 1 + + def generateConstructorCode(self, methodClass, file, nesting): + self.outputConstructorHeader(methodClass, file, nesting) + self.outputConstructorBody(methodClass, file, nesting) + self.outputConstructorFooter(methodClass, file, nesting) + + def generateDestructorCode(self, methodClass, file, nesting): + self.outputDestructorHeader(methodClass, file, nesting) + self.outputDestructorBody(methodClass, file, nesting) + self.outputDestructorFooter(methodClass, file, nesting) + + def generateMethodCode(self, methodClass, file, nesting): + self.outputMethodHeader(methodClass, file, nesting) + self.outputMethodBody(methodClass, file, nesting) + self.outputMethodFooter(methodClass, file, nesting) + + def generateStaticCode(self, methodClass, file, nesting): + self.outputStaticHeader(methodClass, file, nesting) + self.outputStaticBody(methodClass, file, nesting) + self.outputStaticFooter(methodClass, file, nesting) + + def generateInheritedUpcastMethodCode(self, methodClass, parentClass, file, nesting): + self.outputInheritedUpcastMethodHeader(methodClass, parentClass, file, nesting) + self.outputInheritedUpcastMethodBody(methodClass, parentClass, file, nesting) + self.outputInheritedUpcastMethodFooter(methodClass, parentClass, file, nesting) + + def generateDowncastMethodCode(self, methodClass, file, nesting): + # The downcast method code is just like regular code, but the + # return value wrapper does not have downcasting instructions in + # it to prevent an infinite loop of downcasting + self.outputMethodHeader(methodClass, file, nesting) + self.outputMethodBody(methodClass, file, nesting, 0) # no downcast + self.outputMethodFooter(methodClass, file, nesting) + + def generateUpcastMethodCode(self, methodClass, file, nesting): + # The upcast method code is just like regular code, but the + # return value wrapper does not have downcasting instructions + self.outputMethodHeader(methodClass, file, nesting) + self.outputMethodBody(methodClass, file, nesting, 0) # no downcast + self.outputMethodFooter(methodClass, file, nesting) + + ################################################## + ## Constructor Code Generation + ################################################## + def outputConstructorHeader(self, methodClass, file, nesting): + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + indent(file, nesting+1, 'def ' + self.getFinalName() + '(self') + if (len(thislessArgTypes) > 0): + file.write(', ') + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].name) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write('):\n') + + def outputConstructorBody(self, methodClass, file, nesting): + # The method body will look something like + # self.this = panda.Class_constructor(arg) + # self.userManagesMemory = 1 (optional) + self.outputCFunctionComment(file, nesting+2) + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + self.outputTypeChecking(methodClass, thislessArgTypes, file, nesting+2) + indent(file, nesting+2, 'self.this = ' + self.typeDescriptor.moduleName + '.' + + self.typeDescriptor.wrapperName + '(') + # Do not pass self into the constructor + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].passName()) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write(')\n') + indent(file, nesting+2, 'assert(self.this != 0)\n') + if self.typeDescriptor.userManagesMemory: + indent(file, nesting+2, 'self.userManagesMemory = 1\n') + def outputConstructorFooter(self, methodClass, file, nesting): + indent(file, nesting+1, '\n') + + + ################################################## + ## Destructor Code Generation + ################################################## + def outputDestructorHeader(self, methodClass, file, nesting): + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + indent(file, nesting+1, 'def ' + self.getFinalName() + '(self') + if (len(thislessArgTypes) > 0): + file.write(', ') + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].name) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write('):\n') + + def outputDestructorBody(self, methodClass, file, nesting): + # The method body will look something like + # panda.Class_destructor(self.this) + self.outputCFunctionComment(file, nesting+2) + indent(file, nesting+2, self.typeDescriptor.moduleName + '.' + + self.typeDescriptor.wrapperName + '(self.this)\n') + + def outputDestructorFooter(self, methodClass, file, nesting): + indent(file, nesting+1, '\n') + + ################################################## + ## Method Code Generation + ################################################## + def outputMethodHeader(self, methodClass, file, nesting): + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + indent(file, nesting+1, 'def ' + self.getFinalName() + '(self') + if (len(thislessArgTypes) > 0): + file.write(', ') + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].name) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write('):\n') + + def outputMethodBody(self, methodClass, file, nesting, needsDowncast=1): + # The method body will look something like + # returnValue = panda.Class_method(self.this, arg) + # returnValue.userManagesMemory = 1 (optional) + # return returnValue + self.outputCFunctionComment(file, nesting+2) + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + self.outputTypeChecking(methodClass, thislessArgTypes, file, nesting+2) + indent(file, nesting+2, 'returnValue = ' + self.typeDescriptor.moduleName + '.' + + self.typeDescriptor.wrapperName + '(') + file.write('self.this') + if (len(thislessArgTypes) > 0): + file.write(', ') + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].passName()) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write(')\n') + returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() + returnType.generateReturnValueWrapper(file, self.typeDescriptor.userManagesMemory, needsDowncast, nesting+2) + + def outputMethodFooter(self, methodClass, file, nesting): + indent(file, nesting+1, '\n') + + + ################################################## + ## Static Method Code Generation + ################################################## + def outputStaticHeader(self, methodClass, file, nesting): + argTypes = self.typeDescriptor.argumentTypes + indent(file, nesting+1, 'def ' + self.getFinalName() + '(') + for i in range(len(argTypes)): + file.write(argTypes[i].name) + if (i < (len(argTypes)-1)): + file.write(', ') + file.write('):\n') + + def outputStaticBody(self, methodClass, file, nesting): + # The method body will look something like + # returnValue = panda.class_method(self.this, arg) + # returnValue.userManagesMemory = 1 (optional) + # return returnValue + self.outputCFunctionComment(file, nesting+2) + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + self.outputTypeChecking(methodClass, thislessArgTypes, file, nesting+2) + indent(file, nesting+2, 'returnValue = ' + self.typeDescriptor.moduleName + '.' + + self.typeDescriptor.wrapperName + '(') + # Static methods do not take the this parameter + if (len(thislessArgTypes) > 0): + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].passName()) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write(')\n') + returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() + returnType.generateReturnValueWrapper(file, self.typeDescriptor.userManagesMemory, 1, nesting+2) + + def outputStaticFooter(self, methodClass, file, nesting): + indent(file, nesting+1, self.getFinalName() + ' = ' + + FFIConstants.staticModuleName + '.' + FFIConstants.staticModuleName + + '(' + self.getFinalName() + ')\n') + indent(file, nesting+1, '\n') + + + + ################################################## + ## Upcast Method Code Generation + ################################################## + def outputInheritedUpcastMethodHeader(self, methodClass, parentClass, file, nesting): + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + indent(file, nesting+1, 'def ' + self.getFinalName() + '(self') + if (len(thislessArgTypes) > 0): + file.write(', ') + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].name) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write('):\n') + + def outputInheritedUpcastMethodBody(self, methodClass, parentClass, file, nesting): + # The method body will look something like + # upcastSelf = self.upcastToParentClass() + # returnValue = libpanda.method(upcastSelf.this, arg) + # returnValue.userManagesMemory = 1 (optional) + # return returnValue + self.outputCFunctionComment(file, nesting+2) + argTypes = self.typeDescriptor.argumentTypes + thislessArgTypes = self.typeDescriptor.thislessArgTypes() + self.outputTypeChecking(methodClass, thislessArgTypes, file, nesting+2) + indent(file, nesting+2, 'upcastSelf = self.upcast' + 'To' + + parentClass.foreignTypeName + '()\n') + indent(file, nesting+2, 'returnValue = ' + self.typeDescriptor.moduleName + + '.' + self.typeDescriptor.wrapperName + '(upcastSelf.this') + if (len(thislessArgTypes) > 0): + file.write(', ') + for i in range(len(thislessArgTypes)): + file.write(thislessArgTypes[i].passName()) + if (i < (len(thislessArgTypes)-1)): + file.write(', ') + file.write(')\n') + returnType = self.typeDescriptor.returnType.recursiveTypeDescriptor() + # Generate the return value code with no downcast instructions + returnType.generateReturnValueWrapper(file, self.typeDescriptor.userManagesMemory, 1, nesting+2) + + def outputInheritedUpcastMethodFooter(self, methodClass, parentClass, file, nesting): + indent(file, nesting+1, '\n') + + +class GlobalValueSpecification: + def __init__(self): + self.name = '' + # We really do not need the descriptor for the value, just + # the getter and setter + # self.typeDescriptor = None + # To be filled in with a GlobalFunctionSpecification + self.getter = None + # To be filled in with a GlobalFunctionSpecification + self.setter = None + + def generateGlobalCode(self, file): + indent(file, 0, '# Global value: ' + self.name + '\n') + if self.getter: + self.getter.generateGlobalCode(file) + if self.setter: + self.setter.generateGlobalCode(file) + indent(file, 0, '\n') + +class MethodArgumentSpecification: + def __init__(self): + self.name = '' + self.typeDescriptor = None + # By default it is not the this pointer + self.isThis = 0 + + def passName(self): + if (self.typeDescriptor.recursiveTypeDescriptor().__class__ == \ + FFITypes.ClassTypeDescriptor): + return self.name + '.this' + else: + return self.name diff --git a/direct/src/ffi/FFITypes.py b/direct/src/ffi/FFITypes.py new file mode 100644 index 0000000000..c9359b18ed --- /dev/null +++ b/direct/src/ffi/FFITypes.py @@ -0,0 +1,767 @@ + +""" +Type Descriptors + +Type Descriptors are used for code generation of C++ types. They +know everything they need to know about themselves to generate code. +They get constructed by and stored in FFIInterrogateDatabase. + +""" + +import sys +import os +import string +import FFIConstants +import FFIOverload + + +from PythonUtil import * + +class BaseTypeDescriptor: + """ + A type descriptor contains everything you need to know about a C++ function, + class, or primitive. + """ + def __init__(self): + # The pythonified name from C++ + self.foreignTypeName = '' + + # The typeIndex for lookup in the typeIndexMap + self.typeIndex = 0 + + # The C++ prototype for this type + self.prototype = '' + + # The C++ comment for this type + self.comment = '' + + # Is this a nested type? + self.isNested = 0 + + # If we are nested, this is the typeDescriptor we are nested in + self.outerType = None + + # The type descriptors for the types we derive from + self.parentTypes = [] + + # atomicType may be one of the following + # AT_not_atomic = 0 + # AT_int = 1 + # AT_float = 2 + # AT_double = 3 + # AT_bool = 4 + # AT_char = 5 + # AT_void = 6 + # AT_string = 7 + # By default this type is not atomic + self.atomicType = 0 + + def isAtomic(self): + return (self.atomicType != 0) + + def generateGlobalCode(self, dir, extensionsDir): + # By default generate no code + pass + def recursiveTypeDescriptor(self): + """ + Attempt to get to the bottom of a type descriptor + Since we are at the bottom when we get here, just return self + """ + return self + def recordOverloadedMethods(self): + # By default do nothing + pass + def generateReturnValueWrapper(self, file, userManagesMemory, + needsDowncast, nesting): + # By default do nothing + pass + def getFullNestedName(self): + """ + If this type is nested, it will return the fully specified name + For example: OuterClass.InnerClass.ReallyInnerClass + """ + if self.isNested: + return self.outerType.getFullNestedName() + '.' + self.foreignTypeName + else: + return self.foreignTypeName + + +class PrimitiveTypeDescriptor(BaseTypeDescriptor): + """ + Primitive type descriptors include int, float, char, etc. + These get mapped to Python types like IntType, FloatType, StringType + """ + def __init__(self): + BaseTypeDescriptor.__init__(self) + + def generateReturnValueWrapper(self, file, userManagesMemory, + needsDowncast, nesting): + """ + Write code to the file that will return a primitive to the caller. + Pretty simple since there is no extra work needed here + """ + indent(file, nesting, 'return returnValue\n') + + + +class EnumTypeDescriptor(PrimitiveTypeDescriptor): + """ + EnumTypeDescriptors represent enums in C++ + """ + def __init__(self): + PrimitiveTypeDescriptor.__init__(self) + # A dictionary of name, value pairs for this enum + self.values = {} + # The enum name is different than the foreignTypeName because + # we record the foreignTypeName as enum (int) + self.enumName = '' + # Specify that we do not have any parent or nested types to make + # the sorting based on inheritance happy. Essentially, we do not + # inherit from anybody or have any nested types + self.parentTypes = [] + self.nestedTypes = [] + + def generateGlobalCode(self, dir, extensionsDir): + """ + Generate enum code for this type. + """ + fileName = self.enumName + '.py' + file = open(os.path.join(dir, fileName), 'w') + indent(file, 0, FFIConstants.generatedHeader) + self.generateCode(file, 0) + + def generateCode(self, file, nesting): + self.outputComment(file, nesting) + self.outputValues(file, nesting) + + + def outputComment(self, file, nesting): + indent(file, nesting, '\n') + indent(file, nesting, '##################################################\n') + indent(file, nesting, '# Enum ' + self.enumName + '\n') + indent(file, nesting, '##################################################\n') + indent(file, nesting, '\n') + + def outputValues(self, file, nesting): + """ + For each entry in the dictionary, output a line for name, value pairs + Example: + off = 0 + on = 1 + """ + for key in self.values.keys(): + indent(file, nesting, key + ' = ' + `self.values[key]` + '\n') + + +class DerivedTypeDescriptor(BaseTypeDescriptor): + """ + DerivedTypeDescriptor is a wrapper around a primitive or class type + For instance const, or pointer to. + """ + def __init__(self): + BaseTypeDescriptor.__init__(self) + self.typeDescriptor = None + + def recursiveTypeDescriptor(self): + """ + Attempt to get to the bottom of a type descriptor by + recursively unravelling typeDescriptors until you get to + a type that is not derived (primitive or class) in which + case the base class will just return self. + """ + return self.typeDescriptor.recursiveTypeDescriptor() + +class PointerTypeDescriptor(DerivedTypeDescriptor): + """ + Points to another type descriptor + """ + def __init__(self): + DerivedTypeDescriptor.__init__(self) + +class ConstTypeDescriptor(DerivedTypeDescriptor): + """ + Const version of another type descriptor + """ + def __init__(self): + DerivedTypeDescriptor.__init__(self) + +class ClassTypeDescriptor(BaseTypeDescriptor): + """ + This describes a C++ class. It holds lists of all its methods too. + It can also generate Python shadow class code for itself. + """ + def __init__(self): + BaseTypeDescriptor.__init__(self) + + # Methods interrogate told us were constructors + self.constructors = [] + + # A method interrogate told us is the destructor + self.destructor = None + + # Methods interrogate told us were instance methods + # Note: the methods without the this pointer get moved into staticMethods + self.instanceMethods = [] + + # Methods interrogate told us were upcast methods + self.upcastMethods = [] + + # Methods interrogate told us were downcast methods + self.downcastMethods = [] + + # Instance methods that had no this pointer are moved into here + self.staticMethods = [] + + # Global methods that take this class as the first parameter are just + # stored with the class because it is more useable that way + self.globalMethods = [] + + # These are dictionaries used to temporarily hold methods for + # overloading while generating code + self.overloadedClassMethods = {} + self.overloadedInstanceMethods = {} + + # Nested typeDescriptors inside this class + self.nestedTypes = [] + + def getExtensionModuleName(self): + """ + Return a filename for the extensions for this class + Example: NodePath extensions would be found in NodePath-extensions.py + """ + return self.foreignTypeName + '-extensions.py' + + def getCModules(self): + """ + Return a list of all the C modules this class references + """ + moduleList = [] + for method in (self.constructors + [self.destructor] + self.instanceMethods + + self.upcastMethods + self.downcastMethods + + self.staticMethods + self.globalMethods): + if method: + if (not (method.typeDescriptor.moduleName in moduleList)): + moduleList.append(method.typeDescriptor.moduleName) + + # Now look at all the methods that we might inherit if we are at + # a multiple inheritance node and get their C modules + if (len(self.parentTypes) >= 2): + for parentType in self.parentTypes: + for method in parentType.instanceMethods: + if (not (method.typeDescriptor.moduleName in moduleList)): + moduleList.append(method.typeDescriptor.moduleName) + for method in parentType.upcastMethods: + if (not (method.typeDescriptor.moduleName in moduleList)): + moduleList.append(method.typeDescriptor.moduleName) + for method in parentType.globalMethods: + if (not (method.typeDescriptor.moduleName in moduleList)): + moduleList.append(method.typeDescriptor.moduleName) + + return moduleList + + + def getReturnTypeModules(self): + """ + Return a list of all the other shadow class modules this + class references. + Be careful about nested types + """ + moduleList = [] + + upcastMethods = [] + if (len(self.parentTypes) >= 2): + for parentType in self.parentTypes: + for method in parentType.instanceMethods: + upcastMethods.append(method) + for method in parentType.upcastMethods: + upcastMethods.append(method) + for method in parentType.globalMethods: + upcastMethods.append(method) + + for method in (self.constructors + [self.destructor] + self.instanceMethods + + self.upcastMethods + self.downcastMethods + + self.staticMethods + self.globalMethods + upcastMethods): + if method: + # Get the real return type (not derived) + returnType = method.typeDescriptor.returnType.recursiveTypeDescriptor() + if (not returnType.isNested): + returnTypeName = returnType.foreignTypeName + # Do not put our own module in the import list + if ((returnTypeName != self.foreignTypeName) and + # Do not put modules already in the list (like a set) + (not (returnTypeName in moduleList))): + # If this is a class (not a primitive), put it on the list + if (returnType.__class__ == ClassTypeDescriptor): + moduleList.append(returnTypeName) + + # Now look at all the arguments + argTypes = method.typeDescriptor.argumentTypes + for argType in argTypes: + # Get the real return type (not derived) + argType = argType.typeDescriptor.recursiveTypeDescriptor() + if (not argType.isNested): + argTypeName = argType.foreignTypeName + # Do not put our own module in the import list + if ((argTypeName != self.foreignTypeName) and + # Do not put modules already in the list (like a set) + (not (argTypeName in moduleList))): + # If this is a class (not a primitive), put it on the list + if (argType.__class__ == ClassTypeDescriptor): + moduleList.append(argTypeName) + + return moduleList + + def recordClassMethod(self, methodSpec): + """ + Record all class methods in a 2 level dictionary so we can go + through them and see which are overloaded + { className : {methodName : [methodSpec, methodSpec, methodSpec]}} + """ + methodList = ifAbsentPut(self.overloadedClassMethods, methodSpec.name, []) + methodList.append(methodSpec) + + def recordInstanceMethod(self, methodSpec): + """ + Record all instance methods in a 2 level dictionary so we can go + through them and see which are overloaded + { className : {methodName : [methodSpec, methodSpec, methodSpec]}} + """ + methodList = ifAbsentPut(self.overloadedInstanceMethods, methodSpec.name, []) + methodList.append(methodSpec) + + def cullOverloadedMethods(self): + """ + Find all the entries that have multiple indexes for the same method name + Get rid of all others. + """ + tmpDict = {} + # For each class + for methodName in self.overloadedClassMethods.keys(): + methodList = self.overloadedClassMethods[methodName] + # See if this method has more than one function index (overloaded) + if (len(methodList) > 1): + tmpDict[methodName] = methodList + # Mark all the method specifications as overloaded + for methodSpec in methodList: + methodSpec.overloaded = 1 + + # Now we are done. Jam the tmpDict into the real one + self.overloadedClassMethods = tmpDict + + # Now do the same for instance methods + tmpDict = {} + # For each class + for methodName in self.overloadedInstanceMethods.keys(): + methodList = self.overloadedInstanceMethods[methodName] + # See if this method has more than one function index (overloaded) + if (len(methodList) > 1): + # Copy over the method list + tmpDict[methodName] = methodList + # Mark all the method specifications as overloaded + for methodSpec in methodList: + methodSpec.overloaded = 1 + + # Now we are done. Jam the tmpDict into the real one + self.overloadedInstanceMethods = tmpDict + + def filterOutStaticMethods(self): + """ + Run through the list of instance methods and filter out the + ones that are static class methods. We can tell this because they + do not have a this pointer in their arg list. Those methods that + are static are then placed in a new staticMethods list and the ones + that are left are stored back in the instanceMethods list. We are + avoiding modifying the instanceMethods list in place while traversing it. + Do not check upcast or downcast methods because we know they are not static. + """ + newInstanceMethods = [] + for method in self.instanceMethods: + if method.isStatic(): + self.staticMethods.append(method) + else: + newInstanceMethods.append(method) + self.instanceMethods = newInstanceMethods + + def recordOverloadedMethods(self): + """ + Record all the methods in dictionaries based on method name + so we can see if they are overloaded + """ + classMethods = self.constructors + self.staticMethods + if self.destructor: + classMethods = classMethods + [self.destructor] + for method in classMethods: + self.recordClassMethod(method) + + instanceMethods = (self.instanceMethods + self.globalMethods + + self.upcastMethods + self.downcastMethods) + for method in instanceMethods: + self.recordInstanceMethod(method) + + def generateOverloadedMethods(self, file, nesting): + """ + Generate code for all the overloaded methods of this class + """ + + if (len(self.overloadedClassMethods.values()) or + len(self.overloadedInstanceMethods.values())): + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Overloaded methods #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + + for methodSpecList in self.overloadedClassMethods.values(): + treeColl = FFIOverload.FFIMethodArgumentTreeCollection(self, methodSpecList) + treeColl.generateCode(file, nesting) + for methodSpecList in self.overloadedInstanceMethods.values(): + treeColl = FFIOverload.FFIMethodArgumentTreeCollection(self, methodSpecList) + treeColl.generateCode(file, nesting) + + def generateGlobalCode(self, dir, extensionsDir): + """ + Generate shadow class code for this type. + We make our own file form our foreignTypeName and put it in the dir + passed in. + """ + fileName = self.foreignTypeName + '.py' + file = open(os.path.join(dir, fileName), 'w') + indent(file, 0, FFIConstants.generatedHeader) + self.outputBaseImports(file) + self.generateCode(file, 0) + + # Copy in any extensions we may have + self.copyExtensions(extensionsDir, file, 0) + self.outputClassFooter(file) + file.close() + + def generateCode(self, file, nesting): + self.recordOverloadedMethods() + self.cullOverloadedMethods() + self.outputImports(file, nesting) + self.outputClassHeader(file, nesting) + self.outputClassComment(file, nesting) + + self.outputNestedTypes(file, nesting) + + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Constructors #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + self.outputBaseConstructor(file, nesting) + if self.constructors: + for method in self.constructors: + method.generateConstructorCode(self, file, nesting) + else: + self.outputEmptyConstructor(file, nesting) + + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Destructor #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + self.outputBaseDestructor(file, nesting) + if self.destructor: + self.destructor.generateDestructorCode(self, file, nesting) + else: + self.outputEmptyDestructor(file, nesting) + + if len(self.staticMethods): + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Static Methods #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + for method in self.staticMethods: + method.generateStaticCode(self, file, nesting) + + if len(self.instanceMethods): + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Instance methods #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + for method in self.instanceMethods: + method.generateMethodCode(self, file, nesting) + + if len(self.upcastMethods): + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Upcast methods #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + for method in self.upcastMethods: + method.generateUpcastMethodCode(self, file, nesting) + + if len(self.downcastMethods): + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Downcast methods #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + for method in self.downcastMethods: + method.generateDowncastMethodCode(self, file, nesting) + + if len(self.globalMethods): + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Global methods #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + for method in self.globalMethods: + method.generateMethodCode(self, file, nesting) + + # At multiple inheritance nodes, copy all the parent methods into + # this class and call them after upcasting us to that class + if (len(self.parentTypes) >= 2): + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Upcast inherited instance method wrappers #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + for parentType in self.parentTypes: + # Copy all the parents instance methods + for method in parentType.instanceMethods: + method.generateInheritedUpcastMethodCode(self, parentType, file, nesting) + # Copy all the parents upcast methods so we transitively pick them up + for method in parentType.upcastMethods: + method.generateInheritedUpcastMethodCode(self, parentType, file, nesting) + # Do not copy the downcast methods + + # At multiple inheritance nodes, copy all the parent methods into + # this class and call them after upcasting us to that class + if (len(self.parentTypes) >= 2): + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Upcast global method wrappers #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + for parentType in self.parentTypes: + for method in parentType.globalMethods: + method.generateInheritedUpcastMethodCode(self, parentType, file, nesting) + + self.generateOverloadedMethods(file, nesting) + + def outputNestedTypes(self, file, nesting): + if (len(self.nestedTypes) > 0): + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Nested Types #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + # Output code in this same file for all our nested types + for nestedType in self.nestedTypes: + nestedType.generateCode(file, nesting+1) + + def copyExtensions(self, extensionsDir, file, nesting): + """ + Copy in the extension file for this class if one exists + If you want to extend a C++ file, create a file in the extensions directory and + this will append that extension file to the generated code file. + """ + extensionFileName = self.getExtensionModuleName() + extensionFilePath = os.path.join(extensionsDir, extensionFileName) + if os.path.exists(extensionFilePath): + FFIConstants.notify.info( 'Found extensions for class: ' + self.foreignTypeName) + extensionFile = open(extensionFilePath) + indent(file, nesting+1, '\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '# Extension methods #\n') + indent(file, nesting+1, '##################################################\n') + indent(file, nesting+1, '\n') + # Copy the contents of the extensions file to the class file verbatim + indent(file, nesting, extensionFile.read()) + else: + # No extensions for this class + pass + + + def outputBaseImports(self, file): + indent(file, 0, 'import ' + FFIConstants.staticModuleName + '\n') + # Everybody imports types for type checking + indent(file, 0, 'import types\n') + indent(file, 0, '\n') + + indent(file, 0, '# Import all the C modules this class uses\n') + for moduleName in self.getCModules(): + indent(file, 0, 'import ' + moduleName + '\n') + indent(file, 0, '\n') + indent(file, 0, 'import FFIExternalObject\n') + + + def outputImports(self, file, nesting): + """ + Generate code that imports the modules we need for this class + """ + + if len(self.parentTypes): + indent(file, nesting, '# Import everybody we inherit from\n') + for parent in self.parentTypes: + indent(file, nesting, 'import ' + parent.foreignTypeName + '\n') + indent(file, nesting, '\n') + + returnTypeModules = self.getReturnTypeModules() + if len(returnTypeModules): + indent(file, nesting, '# Import all the shadow class modules this class uses\n') + for moduleName in returnTypeModules: + indent(file, nesting, 'import ' + moduleName + '\n') + + # an extra line just for spacing + indent(file, nesting, '\n') + + def outputClassComment(self, file, nesting): + """ + Output the class comment to the file + """ + if FFIConstants.wantComments: + if self.comment: + indent(file, nesting+1, ('"' * 3) + '\n') + # To insert tabs into the comment, replace all newlines with a newline+tabs + comment = string.replace(self.comment, + '\n', ('\n' + (' ' * (nesting+1)))) + indent(file, nesting+1, comment) + file.write('\n') + indent(file, nesting+1, ('"' * 3) + '\n\n') + + def outputClassHeader(self, file, nesting): + """ + Output the class definition to the file + """ + + if (self.foreignTypeName == ''): + FFIConstants.notify.warning('Class with no name') + + # If this is the toplevel, we need to delay the generation of this + # class to avoid circular imports, so put the entire class in a function + # that we will call later + if (nesting==0): + indent(file, nesting, '# Delay the definition of this class until all the imports are done\n') + indent(file, nesting, 'def generateClass_' + self.foreignTypeName + '():\n') + # Start the class definition indented a space to account for the function + indent(file, nesting, ' class ' + self.foreignTypeName) + else: + # Start the class definition + indent(file, nesting, 'class ' + self.foreignTypeName) + + # Everybody inherits from FFIExternalObject + file.write('(') + # Also inherit from all of your parentTypes + for i in range(len(self.parentTypes)): + parentTypeName = self.parentTypes[i].foreignTypeName + # assuming the type "Node" is stored in module "Node.py" + # and we have done an "import Node", we need to then + # inherit from Node.Node + file.write(parentTypeName + '.' + parentTypeName) + file.write(', ') + file.write('FFIExternalObject.FFIExternalObject):\n') + # Store the class C modules for the class so they do not + # get garbage collected before we do + indent(file, nesting+1, '__CModules__ = [') + for moduleName in self.getCModules(): + file.write(moduleName + ',') + file.write(']\n') + + + def outputClassFooter(self, file): + indent(file, 0, " # When this class gets defined, put it in this module's namespace\n") + indent(file, 0, " globals()['" + self.foreignTypeName + "'] = " + self.foreignTypeName + '\n') + + def outputBaseConstructor(self, file, nesting): + """ + Output the __init__ constructor for this class. + There is special login if you pass in None to the constructor, you + will not get an actual C object with memory, you will just get the + shadow class shell object. This is useful for functions that want + to return this type that already have a this pointer and just need + to construct a shadow object to contain it. + """ + + indent(file, nesting+1, 'def __init__(self, *_args):\n') + indent(file, nesting+2, '# Initialize the super class\n') + indent(file, nesting+2, 'FFIExternalObject.FFIExternalObject.__init__(self)\n') + indent(file, nesting+2, '# If you want an empty shadow object, pass in None\n') + indent(file, nesting+2, 'if ((len(_args) == 1) and (_args[0] == None)):\n') + indent(file, nesting+3, 'return\n') + indent(file, nesting+2, '# Otherwise, call the C constructor\n') + indent(file, nesting+2, 'apply(self.constructor, _args)\n') + indent(file, nesting+2, '\n') + + def outputEmptyConstructor(self, file, nesting): + # If there is no C++ constructor, we just output this + # empty one instead + indent(file, nesting+1, 'def constructor(self):\n') + indent(file, nesting+2, 'pass\n') + + def outputBaseDestructor(self, file, nesting): + # This destructor overwrites the builtin Python destructor + # using the __del__ method. This will get called whenever a + # Python object is garbage collected. We are going to overwrite + # it with special cleanup for Panda. + indent(file, nesting+1, 'def __del__(self):\n') + + # Reference counting is now handled in the C++ code + # indent(file, nesting+2, 'if isinstance(self, ReferenceCount):\n') + # indent(file, nesting+3, 'self.unref()\n') + # indent(file, nesting+3, 'if (self.getCount() == 0):\n') + # indent(file, nesting+4, 'self.destructor()\n') + + # If the scripting language owns the memory for this object, + # we need to call the C++ destructor when Python frees the + # shadow object, but only if the userManagesMemory flag is set. + # Also make sure we are not destructing a null pointer + indent(file, nesting+2, 'if (self.userManagesMemory and (self.this != 0)):\n') + indent(file, nesting+3, 'self.destructor()\n') + + def outputEmptyDestructor(self, file, nesting): + # If there is no C++ destructor, we just output this + # empty one instead + indent(file, nesting+1, 'def destructor(self):\n') + indent(file, nesting+2, 'pass\n') + + def generateReturnValueWrapper(self, file, userManagesMemory, + needsDowncast, nesting): + """ + Generate code that creates a shadow object of this type + then sets the this pointer and returns the object. We call the + class destructor with None as the only parameter to get an + empty shadow object. + """ + indent(file, nesting, 'returnObject = ') + # Do not put Class.Class if this file is the file that defines Class + if (os.path.basename(file.name)[:-3] == self.foreignTypeName): + file.write(self.foreignTypeName) + else: + file.write(self.foreignTypeName + '.' + self.foreignTypeName) + file.write('(None)\n') + indent(file, nesting, 'returnObject.this = returnValue\n') + if userManagesMemory: + indent(file, nesting, 'returnObject.userManagesMemory = 1\n') + if needsDowncast: + indent(file, nesting, 'downcastObject = returnObject.setPointer()\n') + indent(file, nesting, 'return downcastObject\n') + else: + indent(file, nesting, 'return returnObject\n') + + + +class FunctionTypeDescriptor(BaseTypeDescriptor): + """ + A C++ function type. It knows its returnType, arguments, etc. + """ + def __init__(self): + BaseTypeDescriptor.__init__(self) + self.returnType = None + self.argumentTypes = [] + self.userManagesMemory = 0 + self.isVirtual = 0 + self.moduleName = '' + self.wrapperName = '' + self.returnValueDestructor = None + def thislessArgTypes(self): + """ + It is often useful to know the list of arguments excluding the + this parameter (if there was one) + """ + return filter(lambda type: (not type.isThis), self.argumentTypes) + diff --git a/direct/src/ffi/Sources.pp b/direct/src/ffi/Sources.pp new file mode 100644 index 0000000000..a03ea8c336 --- /dev/null +++ b/direct/src/ffi/Sources.pp @@ -0,0 +1,3 @@ +// For now, since we are not installing Python files, this file can +// remain empty. + diff --git a/direct/src/ffi/generatePythonCode b/direct/src/ffi/generatePythonCode new file mode 100644 index 0000000000..53ce939bc2 --- /dev/null +++ b/direct/src/ffi/generatePythonCode @@ -0,0 +1,107 @@ +#!/usr/local/bin/python + + +import getopt +import sys +import os +import FFIConstants + +# Define a help string for the user +helpString =""" +generatePythonCode [opts] -i libtool libcode1 libcode2 ... + +Generates Python code for the C++ libraries listed. + +Example: + generatePythonCode -v -d $DIRECT/lib/py -e $DIRECT/src/all/python -i libdtool libpandaexpress libpanda libdirect + +Options: + -h print this message + -v verbose + -d dir directory to write output code + -e dir directory to pull extension code from + -i lib interrogate library + -O no C++ comments or assertion statements +""" + +# Initialize variables +outputDir = '' +extensionsDir = '' +interrogateLib = '' +codeLibs = [] + + +# Extract the args the user passed in +try: + opts, pargs = getopt.getopt(sys.argv[1:], 'hvOd:e:i:') +except Exception, e: + # User passed in a bad option, print the error and the help, then exit + print e + print helpString + sys.exit() + +if len(opts)==0: + print helpString + sys.exit() + + +# Store the option values into our variables +for opt in opts: + flag, value = opt + if (flag == '-h'): + print helpString + sys.exit() + elif (flag == '-v'): + FFIConstants.notify.setVerbose(1) + elif (flag == '-d'): + outputDir = value + elif (flag == '-e'): + extensionsDir = value + elif (flag == '-i'): + interrogateLib = value + elif (flag == '-O'): + FFIConstants.wantComments = 0 + FFIConstants.wantTypeChecking = 0 + else: + FFIConstants.notify.error('illegal option: ' + flag) + +# Store the program arguments into the codeLibs +codeLibs = pargs + +# Now do some error checking and verbose output +if (not interrogateLib): + FFIConstants.notify.error('You must specify an interrogate library (-i lib)') +else: + FFIConstants.notify.info('Setting interrogate library to: ' + interrogateLib) + FFIConstants.InterrogateModuleName = interrogateLib + +if (not outputDir): + FFIConstants.notify.info('Setting output directory to current directory') + outputDir = '.' +elif (not os.path.exists(outputDir)): + FFIConstants.notify.error('Directory does not exists: ' + outputDir) +else: + FFIConstants.notify.info('Setting output directory to: ' + outputDir) + + +if (not extensionsDir): + FFIConstants.notify.info('Setting extensions directory to current directory') + extensionsDir = '.' +elif (not os.path.exists(extensionsDir)): + FFIConstants.notify.error('Directory does not exists: ' + extensionsDir) +else: + FFIConstants.notify.info('Setting extensions directory to: ' + extensionsDir) + + +if (not codeLibs): + FFIConstants.notify.error('You must specify one or more libraries to generate code from') +else: + FFIConstants.notify.info('Generating code for: ' + `codeLibs`) + FFIConstants.CodeModuleNameList = codeLibs + +# Ok, now we can start generating code +import FFIInterrogateDatabase +db = FFIInterrogateDatabase.FFIInterrogateDatabase() +db.updateBindings() +db.generateCode(outputDir, extensionsDir) + diff --git a/direct/src/showbase/AudioManagerGlobal.py b/direct/src/showbase/AudioManagerGlobal.py new file mode 100644 index 0000000000..817e5512f3 --- /dev/null +++ b/direct/src/showbase/AudioManagerGlobal.py @@ -0,0 +1,4 @@ +"""AudioManagerGlobal module: contains the global audio manager""" +import AudioManager + +audioMgr = AudioManager.AudioManager() diff --git a/direct/src/showbase/DirectNotify.py b/direct/src/showbase/DirectNotify.py new file mode 100644 index 0000000000..24d4f77bb3 --- /dev/null +++ b/direct/src/showbase/DirectNotify.py @@ -0,0 +1,65 @@ + +"""DirectNotify module: this module contains the DirectNotify class""" + +import Notifier +import Logger + +class DirectNotify: + """DirectNotify class: this class contains methods for creating + mulitple notify categories via a dictionary of Notifiers.""" + + def __init__(self): + """__init__(self) + DirectNotify class keeps a dictionary of Notfiers""" + self.__categories = { } + # create a default log file + self.logger = Logger.Logger() + + def __str__(self): + """__str__(self) + Print handling routine""" + return "DirectNotify categories: %s" % (self.__categories) + + #getters and setters + def getCategories(self): + """getCategories(self) + Return list of category dictionary keys""" + return (self.__categories.keys()) + + def getCategory(self, categoryName): + """getCategory(self, string) + Return the category with given name if present, None otherwise""" + return(self.__categories.get(categoryName, None)) + + def addCategory(self, categoryName, category): + """addCategory(self, Notifier) + Add a given Notifier with given name to the category dictionary + and return 0 if not present, else return 0. """ + if (self.__categories.has_key(categoryName)): + print "Warning: DirectNotify: category '%s' already exists" % \ + (categoryName) + return(0) + else: + self.__categories[categoryName] = category + return(1) + + def newCategory(self, categoryName): + """newCategory(self, string) + Make a new notify category named categoryName. Return new category + if no such category exists, else return existing category""" + if (not self.__categories.has_key(categoryName)): + self.__categories[categoryName] = Notifier.Notifier(categoryName) + else: + print "Warning: DirectNotify: category '%s' already exists" % \ + (categoryName) + return(self.getCategory(categoryName)) + + + +#global DirectNotify for public access +directNotify = DirectNotify() + + + + + diff --git a/direct/src/showbase/DirectNotifyGlobal.py b/direct/src/showbase/DirectNotifyGlobal.py new file mode 100644 index 0000000000..f2513ab91c --- /dev/null +++ b/direct/src/showbase/DirectNotifyGlobal.py @@ -0,0 +1,6 @@ +"""instantiate global DirectNotfiy used in Direct""" + +import DirectNotify + +directNotify = DirectNotify.DirectNotify() + diff --git a/direct/src/showbase/DirectObject.py b/direct/src/showbase/DirectObject.py new file mode 100644 index 0000000000..b73f9a53fa --- /dev/null +++ b/direct/src/showbase/DirectObject.py @@ -0,0 +1,26 @@ + +from MessengerGlobal import * +from DirectNotifyGlobal import * + +class DirectObject: + """ + This is the class that all Direct/SAL classes should inherit from + """ + # Event Handling + + # object.accept('mouse', object.handleMouse) + # object.accept('mouse', 'handleMouse') + # object.accept('mouse', 'handleMouse', [1,2]) + + def accept(self, event, method, extraArgs=[]): + messenger.accept(event, self, method, extraArgs, 1) + def acceptOnce(self, event, method, extraArgs=[]): + messenger.accept(event, self, method, extraArgs, 0) + def ignore(self, event): + messenger.ignore(event, self) + def isAccepting(self, event): + return messenger.isAccepting(event, self) + def isIgnoring(self, event): + return messenger.isIgnoring(event, self) + + diff --git a/direct/src/showbase/EventManager.py b/direct/src/showbase/EventManager.py new file mode 100644 index 0000000000..e23dedbeb5 --- /dev/null +++ b/direct/src/showbase/EventManager.py @@ -0,0 +1,87 @@ + +from PandaModules import * +from MessengerGlobal import * +from TaskManagerGlobal import * +from DirectNotifyGlobal import * + +class EventManager: + + notify = None + + def __init__(self): + """ + Create a C++ event queue and handler + """ + + # Make a notify category for this class (unless there already is one) + if (EventManager.notify == None): + EventManager.notify = directNotify.newCategory("EventManager") + + self.eventQueue = EventQueue.getGlobalEventQueue() + self.eventHandler = EventHandler(self.eventQueue) + + def eventLoop(self, state): + """ + Process all the events on the C++ event queue + """ + while (not self.eventQueue.isQueueEmpty()): + event = self.eventQueue.dequeueEvent() + self.processEvent(event) + return Task.cont + + def parseEventParameter(self, eventParameter): + """ + Extract the actual data from the eventParameter + """ + if (eventParameter.isInt()): + return eventParameter.getIntValue() + elif (eventParameter.isDouble()): + return eventParameter.getDoubleValue() + elif (eventParameter.isString()): + return eventParameter.getStringValue() + # Must be some user defined type, return the ptr + # which will be downcast to that type + else: + return eventParameter.getPtr() + + + + def processEvent(self, event): + """ + Process a C++ event + """ + # If the event has a name, throw a Python event with the Pythonified name + if event.hasName(): + # Get the event name + eventName = event.getName() + + numParameters = event.getNumParameters() + paramList = [] + for i in range(numParameters): + eventParameter = event.getParameter(i) + eventParameterData = self.parseEventParameter(eventParameter) + paramList.append(eventParameterData) + + EventManager.notify.debug('received C++ event named: ' + eventName + + ' parameters: ' + `paramList`) + + + # Send the event, we used to send it with the event + # name as a parameter, but now you can use extraArgs for that + if paramList: + messenger.send(eventName, paramList) + else: + messenger.send(eventName) + + # Also send the event down into C++ land + self.eventHandler.dispatchEvent(event) + + # An unnamed event from C++ is probably a bad thing + else: + EventManager.notify.warning('unnamed event in processEvent') + + def restart(self): + taskMgr.spawnTaskNamed(Task.Task(self.eventLoop), 'eventManager') + + def shutdown(self): + taskMgr.removeTasksNamed('eventManager') diff --git a/direct/src/showbase/EventManagerGlobal.py b/direct/src/showbase/EventManagerGlobal.py new file mode 100644 index 0000000000..28931246de --- /dev/null +++ b/direct/src/showbase/EventManagerGlobal.py @@ -0,0 +1,3 @@ +import EventManager + +eventMgr = EventManager.EventManager() diff --git a/direct/src/showbase/FSM.py b/direct/src/showbase/FSM.py new file mode 100644 index 0000000000..4d23eff5db --- /dev/null +++ b/direct/src/showbase/FSM.py @@ -0,0 +1,140 @@ +"""Finite State Machine module: contains the FSM class""" + +from DirectObject import * + +class FSM(DirectObject): + """FSM class: Finite State Machine class""" + + # create FSM DirectNotify category + notify = directNotify.newCategory("FSM") + + # special methods + + def __init__(self, name, states=[], initialStateName=None, + finalStateName=None): + """__init__(self, string, State[], string, string) + + FSM constructor: takes name, list of states, initial state and + final state as: + + fsm = FSM.FSM('stopLight', + [ State.State('red', enterRed, exitRed, ['green']), + State.State('yellow', enterYellow, exitYellow, ['red']), + State.State('green', enterGreen, exitGreen, ['yellow']) ], + 'red', + 'red') + + """ + + self.setName(name) + self.setStates(states) + self.setInitialState(initialStateName) + self.setFinalState(finalStateName) + + + #enter the initial state + self.__currentState = self.__initialState + self.__enter(self.__initialState) + + def __str__(self): + """__str__(self)""" + return "FSM: name = %s \n states = %s \n initial = %s \n final = %s \n current = %s" % (self.__name, self.__states, self.__initialState, self.__finalState, self.__currentState) + + + #setters and getters + + def getName(self): + """getName(self)""" + return(self.__name) + + def setName(self, name): + """setName(self, string)""" + self.__name = name + + def getStates(self): + """getStates(self)""" + return(self.__states) + + def setStates(self, states): + """setStates(self, State[])""" + self.__states = states + + def getInitialState(self): + """getInitialState(self)""" + return(self.__initialState) + + def setInitialState(self, initialStateName): + """setInitialState(self, string)""" + self.__initialState = self.getStateNamed(initialStateName) + + def getFinalState(self): + """getFinalState(self)""" + return(self.__finalState) + + def setFinalState(self, finalStateName): + """setFinalState(self, string)""" + self.__finalState = self.getStateNamed(finalStateName) + + def getCurrentState(self): + """getCurrentState(self)""" + return(self.__currentState) + + + # lookup funcs + + def getStateNamed(self, stateName): + """getStateNamed(self, string) + Return the state with given name if found, issue warning otherwise""" + for state in self.__states: + if (state.getName() == stateName): + return state + FSM.notify.warning("getStateNamed: no such state") + + + # basic FSM functionality + + def __exitCurrent(self): + """__exitCurrent(self) + Exit the current state""" + FSM.notify.info("exiting %s" % self.__currentState.getName()) + self.__currentState.exit() + self.__currentState = None + + def __enter(self, aState): + """__enter(self, State) + Enter a given state, if it exists""" + if (aState in self.__states): + self.__currentState = aState + aState.enter() + FSM.notify.info("entering %s" % aState.getName()) + else: + FSM.notify.error("enter: no such state") + + def __transition(self, aState): + """__transition(self, State) + Exit currentState and enter given one""" + self.__exitCurrent() + self.__enter(aState) + + def request(self, aStateName): + """request(self, string) + Attempt transition from currentState to given one. + Return true is transition exists to given state, + false otherwise""" + if (aStateName in self.__currentState.getTransitions()): + self.__transition(self.getStateNamed(aStateName)) + return 1 + else: + FSM.notify.info("no transition exists to %s" % aStateName) + return 0 + + + + + + + + + + + diff --git a/direct/src/showbase/LerpBlendHelpers.py b/direct/src/showbase/LerpBlendHelpers.py new file mode 100644 index 0000000000..d67b48ca68 --- /dev/null +++ b/direct/src/showbase/LerpBlendHelpers.py @@ -0,0 +1,14 @@ +"""LerpBlendHelpers module: contains LerpBlendHelpers class""" + +from PandaModules import * +class LerpBlendHelpers: + + """global lerp blend types for lerp function""" + + easeIn = EaseInBlendType() + + easeOut = EaseOutBlendType() + + easeInOut = EaseInOutBlendType() + + noBlend = NoBlendType() diff --git a/direct/src/showbase/Loader.py b/direct/src/showbase/Loader.py new file mode 100644 index 0000000000..d28c10f5b8 --- /dev/null +++ b/direct/src/showbase/Loader.py @@ -0,0 +1,96 @@ +"""Loader module: contains the Loader class""" + +from PandaModules import * +from DirectNotifyGlobal import * + + +class Loader: + + """Loader class: contains method to load models, sounds and code""" + + notify = directNotify.newCategory("Loader") + + # special methods + def __init__(self, base): + """__init__(self) + Loader constructor""" + self.__base = base + self.__loader = PandaLoader() + self.__texturePool = TexturePool() + self.__modelPool = ModelPool() + self.__audioPool = AudioPool() + + # model loading funcs + def loadModel(self, modelPath): + """loadModel(self, string) + Attempt to load a model from given file path, return + a nodepath to the model if successful or None otherwise.""" + Loader.notify.info("Loading model: %s" % (modelPath) ) + node = self.__loader.loadSync(Filename(modelPath)) + if (node != None): + nodePath = self.__base.hidden.attachNewNode(node) + else: + nodePath = None + return nodePath + + def loadModelOnce(self, modelPath): + """loadModelOnce(self, string) + Attempt to load a model from modelPool, if not present + then attempt to load it from disk. Return a nodepath to + the model if successful or None otherwise""" + Loader.notify.info("Loading model once: %s" % (modelPath)) + node = self.__modelPool.loadModel(modelPath) + if (node != None): + nodePath = self.__base.hidden.attachNewNode(node) + else: + nodePath = None + return nodePath + + def loadModelCopy(self, modelPath): + """loadModelCopy(self, string) + Attempt to load a model from modelPool, if not present + then attempt to load it from disk. Return a nodepath to + a copy of the model if successful or None otherwise""" + Loader.notify.info("Loading model copy: %s" % (modelPath)) + # utilize load once goodness + nodePath = self.loadModelOnce(modelPath) + if (nodePath != None): + return (nodePath.copyTo(self.__base.hidden)) + else: + return None + + # texture loading funcs + def loadTexture(self, texturePath): + """loadTexture(self, string) + Attempt to load a texture from the given file path using + TexturePool class. Returns None if not found""" + Loader.notify.info("Loading texture: %s" % (texturePath) ) + texture = self.__texturePool.loadTexture(Filename(texturePath)) + return texture + + # sound loading funcs + def loadSample(self, samplePath): + """loadSample(self, string) + Attempt to load a sound from the given file path using + Cary's sound class. Returns None if not found""" + Loader.notify.info("Loading sound: %s" % (samplePath) ) + sound = self.__audioPool.loadSample(samplePath) + return sound + + def loadMusic(self, musicPath): + """loadMusic(self, string) + Attempt to load music from the given file path using + Cary's sound class. Returns None if not found""" + Loader.notify.info("Loading music: %s" % (musicPath) ) + music = self.__audioPool.loadMusic(musicPath) + return music + + + + + + + + + + diff --git a/direct/src/showbase/Logger.py b/direct/src/showbase/Logger.py new file mode 100644 index 0000000000..817404c1f7 --- /dev/null +++ b/direct/src/showbase/Logger.py @@ -0,0 +1,93 @@ +"""Logger module: contains the logger class which creates and writes + data to log files on disk""" + +import sys +import time +import math + +class Logger: + + """Logger class: """ + + # built-ins + + def __init__(self, fileName="log"): + """__init__(self) + Logger constructor""" + self.__timeStamp = 1 + self.__startTime = 0.0 + self.__logFile = None + self.__logFileName = fileName + + + # setters and getters + + def setTimeStamp(self, bool): + """setTimeStamp(self, int) + Toggle time stamp printing with log entries on and off""" + self.__timeStamp = bool + + def getTimeStamp(self): + """getTimeStamp(self) + Return whether or not we are printing time stamps with log entries""" + return(self.__timeStamp) + + + # logging control + + def resetStartTime(self): + """resetStartTime() + Reset the start time of the log file for time stamps""" + self.__startTime = time.time() + + def log(self, entryString): + """log(self, string) + Print the given string to the log file""" + if (self.__logFile == None): + self.__openLogFile() + if (self.__timeStamp): + self.__logFile.write(self.__getTimeStamp()) + self.__logFile.write(entryString + '\n') + + + # logging functions + + def __openLogFile(self): + """__openLogFile(self) + Open a file for logging error/warning messages""" + self.resetStartTime() + t = time.localtime(self.__startTime) + st = time.strftime("%m.%d.%Y-%H:%M:%S", t) + logFileName = self.__logFileName + "." + st + self.__logFile = open(logFileName, "w") + + def __closeLogFile(self): + """__closeLogFile(self) + Close the error/warning output file""" + if (self.__logFile != None): + self.__logFile.close() + + def __getTimeStamp(self): + """__getTimeStamp(self) + Return the offset between current time and log file startTime""" + t = time.time() + dt = t - self.__startTime + if (dt >= 86400): + days = int(math.floor(dt/86400)) + dt = dt%86400 + else: + days = 0 + if (dt >= 3600): + hours = int(math.floor(dt/3600)) + dt = dt%3600 + else: + hours = 0 + if (dt >= 60): + minutes = int(math.floor(dt/60)) + dt = dt%60 + else: + minutes = 0 + seconds = int(math.ceil(dt)) + return("%02d:%02d:%02d:%02d: " % (days, hours, minutes, seconds) ) + + diff --git a/direct/src/showbase/LoggerGlobal.py b/direct/src/showbase/LoggerGlobal.py new file mode 100644 index 0000000000..87a89e446d --- /dev/null +++ b/direct/src/showbase/LoggerGlobal.py @@ -0,0 +1,5 @@ +"""instantiate global Logger object""" + +import Logger + +defaultLogger = Logger.Logger() diff --git a/direct/src/showbase/Messenger.py b/direct/src/showbase/Messenger.py new file mode 100644 index 0000000000..313ec756e0 --- /dev/null +++ b/direct/src/showbase/Messenger.py @@ -0,0 +1,126 @@ + +from PythonUtil import * +from DirectNotifyGlobal import * + + +class Messenger: + + notify = None + + def __init__(self): + """ __init__(self) + One dictionary does it all. It has the following structure: + {event1 : {object1: [method, extraArgs, persistent], + object2: [method, extraArgs, persistent]}, + event2 : {object1: [method, extraArgs, persistent], + object2: [method, extraArgs, persistent]}} + + Or, for an example with more real data: + {'mouseDown' : {avatar : [avatar.jump, (2.0), 1]}} + """ + self.dict = {} + + if (Messenger.notify == None): + Messenger.notify = directNotify.newCategory("Messenger") + + def accept(self, event, object, method, extraArgs=[], persistent=1): + """ accept(self, string, DirectObject, Function, List, Boolean) + + Make this object accept this event. When the event is + sent (using Messenger.send or from C++), method will be executed, + optionally passing in extraArgs. + + If the persistent flag is set, it will continue to respond + to this event, otherwise it will respond only once. + """ + + Messenger.notify.debug('object: ' + `object` + + '\n accept: ' + `event` + + '\n method: ' + `method` + + '\n extraArgs: ' + `extraArgs` + + '\n persistent: ' + `persistent`) + + acceptorDict = ifAbsentPut(self.dict, event, {}) + acceptorDict[object] = [method, extraArgs, persistent] + + def ignore(self, event, object): + """ ignore(self, string, DirectObject) + Make this object no longer respond to this event. + It is safe to call even if it was not alread + """ + + Messenger.notify.debug(`object` + '\n ignore: ' + `event`) + + if self.dict.has_key(event): + # Find the dictionary of all the objects accepting this event + acceptorDict = self.dict[event] + # If this object is there, delete it from the dictionary + if acceptorDict.has_key(object): + del acceptorDict[object] + # If this dictionary is now empty, remove the event + # entry from the Messenger alltogether + if (len(acceptorDict) == 0): + del self.dict[event] + + def isAccepting(self, event, object): + """ isAccepting(self, string, DirectOject) + Is this object accepting this event? + """ + if self.dict.has_key(event): + if self.dict[event].has_key(object): + # Found it, return true + return 1 + + # If we looked in both dictionaries and made it here + # that object must not be accepting that event. + return 0 + + def isIgnoring(self, event, object): + """ isIgnorning(self, string, DirectObject) + Is this object ignoring this event? + """ + return (not self.isAccepting(event, object)) + + def send(self, event, sentArgs=[]): + """ send(self, string, [arg1, arg2,...]) + Send this event, optionally passing in arguments + """ + + Messenger.notify.debug('sent event: ' + event + ' sentArgs: ' + `sentArgs`) + + if self.dict.has_key(event): + acceptorDict = self.dict[event] + for object in acceptorDict.keys(): + method, extraArgs, persistent = acceptorDict[object] + apply(method, (extraArgs + sentArgs)) + # If this object was only accepting this event once, + # remove it from the dictionary + if not persistent: + del acceptorDict[object] + # If this dictionary is now empty, remove the event + # entry from the Messenger alltogether + if (len(acceptorDict) == 0): + del self.dict[event] + + def clear(self): + """clear(self) + Start fresh with a clear dict + """ + self.dict.clear() + + def __repr__(self): + """__repr__(self) + Print out the table in a readable format + """ + str = 'Messenger\n' + str = str + '='*50 + '\n' + for event in self.dict.keys(): + acceptorDict = self.dict[event] + str = str + event + '\n' + for object in acceptorDict.keys(): + method, extraArgs, persistent = acceptorDict[object] + str = str + '\t' + `object` + '\n\t' + `method` + '\n\t' + `extraArgs` + ' ' + `persistent` + '\n' + str = str + '='*50 + '\n' + return str + + diff --git a/direct/src/showbase/MessengerGlobal.py b/direct/src/showbase/MessengerGlobal.py new file mode 100644 index 0000000000..588d38254c --- /dev/null +++ b/direct/src/showbase/MessengerGlobal.py @@ -0,0 +1,5 @@ +"""instantiate global Messenger object""" + +import Messenger + +messenger = Messenger.Messenger() diff --git a/direct/src/showbase/Notifier.py b/direct/src/showbase/Notifier.py new file mode 100644 index 0000000000..d2016c5ffd --- /dev/null +++ b/direct/src/showbase/Notifier.py @@ -0,0 +1,127 @@ +"""Notifier module: contains methods for handling information output + for the programmer/user""" + +from LoggerGlobal import * + +class Notifier: + + def __init__(self, name, logger=None): + """__init__(self, string, Logger=None) + Create a new instance of the Notifier class with a given name + and an optional Logger class for piping output to. If no logger + specified, use the global default""" + self.__name = name + + if (logger==None): + self.__logger = defaultLogger + else: + self.__logger = logger + + self.__verbose = 0 + self.__warning = 1 + self.__debug = 0 + self.__logging = 0 + + def __str__(self): + """__str__(self) + Print handling routine""" + return "%s: verbose = %d, warning = %d, debug = %d, logging = %d" % \ + (self.__name, self.__verbose, self.__warning, self.__debug, self.__logging) + + + # error funcs + def error(self, errorString, exception=StandardError): + """error(self, string, Exception=StandardError) + Raise an exception with given string and optional type: + Exception: error""" + self.__log(str(exception) + ": " + self.__name + ": " + errorString) + raise exception(errorString) + + + # warning funcs + def warning(self, warningString): + """warning(self, string) + Issue the warning message if warn flag is on""" + if (self.__warning): + str = "Warning: " + self.__name + ": " + warningString + self.__log(str) + print(str) + + def setWarning(self, bool): + """setWarning(self, int) + Enable/Disable the printing of warning messages""" + self.__warning = bool + + def getWarning(self): + """getWarning(self) + Return whether the printing of warning messages is on or off""" + return(self.__warning) + + + + # debug funcs + def debug(self, debugString): + """debug(self, string) + Issue the debug message if debug flag is on""" + if (self.__debug): + str = "Debug: " + self.__name + ": " + debugString + self.__log(str) + print(str) + + def setDebug(self, bool): + """setDebug(self, int) + Enable/Disable the printing of debug messages""" + self.__debug = bool + + def getDebug(self): + """getDebug(self) + Return whether the printing of debug messages is on or off""" + return(self.__debug) + + + + # info funcs + def info(self, infoString): + """info(self, string) + Print the given informational string, if verbose flag is on""" + if (self.__verbose): + str = "Info: " + self.__name + ": " + infoString + self.__log(str) + print(str) + + def getVerbose(self): + """getVerbose(self) + Return whether the printing of info messages is on or off""" + return(self.__verbose) + + def setVerbose(self, bool): + """setVerbose(self, int) + Enable/Disable informational message printing""" + self.__verbose = bool + + + # log funcs + def __log(self, logEntry): + """__log(self, string) + Determine whether to send informational message to the logger""" + if (self.__logging): + self.__logger.log(logEntry) + + def getLogging(self): + """getLogging(self) + Return 1 if logging enabled, 0 otherwise""" + return (self.__logging) + + def setLogging(self, bool): + """setLogging(self, int) + Set the logging flag to int (1=on, 0=off)""" + self.__logging = bool + + + + + + + + + diff --git a/direct/src/showbase/OnscreenText.py b/direct/src/showbase/OnscreenText.py new file mode 100644 index 0000000000..695095c55a --- /dev/null +++ b/direct/src/showbase/OnscreenText.py @@ -0,0 +1,55 @@ +"""OnscreenText module: contains the OnscreenText class""" + +from PandaObject import * + + + +class OnscreenText(PandaObject, ShowBase.NodePath): + + Font = loader.loadModelOnce("fonts/ttf-comic").node() + + def __init__(self, string, x=0.0, y=0.0): + """__init__(self, string, float=0.0, float=0.0) + Make a text node from string, put it into the 2d sg and place + it at x, y in screen space + """ + # become one with our NodePath-ness + NodePath.__init__(self) + + # make a text node + textNode = ShowBase.TextNode() + textNode.setBillboard(0) + textNode.setTextColor(0.0, 0.0, 0.0, 1.0) + textNode.setCardColor(1.0, 1.0, 1.0, 1.0) + textNode.setCardAsMargin(0.1, 0.1, 0.1, 0.1) + textNode.setFrameColor(0.0, 0.0, 0.0, 1.0) + textNode.setFrameAsMargin(0.1, 0.1, 0.1, 0.1) + textNode.setFont(OnscreenText.Font) + textNode.setText(string) + + # put the text node into the 2d scene graph + textNodePath = render2d.attachNewNode(textNode) + + # we ARE this node path + self.assign(textNodePath) + + # position ourselves + self.setXY(x, y) + + # assume 4:3 aspect ratio + self.setScale( 0.069, 1.0, 0.069) + + + def setText(self, string): + """setText(self, string) + Set the text of the onscreen text + """ + self.node().setText(string) + + + def setXY(self, x, y): + """setPos(self, float, float) + Position the onscreen text in 2d screen space + """ + # render2d has x across and z up + self.setPos(x, 0.0, y) diff --git a/direct/src/showbase/PandaObject.py b/direct/src/showbase/PandaObject.py new file mode 100644 index 0000000000..56dfdef89f --- /dev/null +++ b/direct/src/showbase/PandaObject.py @@ -0,0 +1,10 @@ + +from DirectObject import * +from PandaModules import * +from ShowBaseGlobal import * + +class PandaObject(DirectObject): + """ + This is the class that all Panda/Show classes should inherit from + """ + pass diff --git a/direct/src/showbase/PythonUtil.py b/direct/src/showbase/PythonUtil.py new file mode 100644 index 0000000000..1e880549d8 --- /dev/null +++ b/direct/src/showbase/PythonUtil.py @@ -0,0 +1,22 @@ + +def ifAbsentPut(dict, key, newValue): + """ + If dict has key, return the value, otherwise insert the newValue and return it + """ + if dict.has_key(key): + return dict[key] + else: + dict[key] = newValue + return newValue + + +def indent(stream, numIndents, str): + """ + Write str to stream with numIndents in front it it + """ + #indentString = '\t' + # To match emacs, instead of a tab character we will use 4 spaces + indentString = ' ' + stream.write(indentString * numIndents + str) + + diff --git a/direct/src/showbase/ShowBase.py b/direct/src/showbase/ShowBase.py new file mode 100644 index 0000000000..25804363f6 --- /dev/null +++ b/direct/src/showbase/ShowBase.py @@ -0,0 +1,140 @@ + +from PandaModules import * +from DirectNotifyGlobal import * +from MessengerGlobal import * +from TaskManagerGlobal import * +from EventManagerGlobal import * +from AudioManagerGlobal import * +# This should be on a dconfig variable +from TkGlobal import * +import Task +import EventManager + +class ShowBase: + + notify = None + + def __init__(self): + + import Loader + + self.initialState = NodeAttributes() + self.renderTop = NodePath(NamedNode('renderTop')) + self.render = self.renderTop.attachNewNode('render') + self.hidden = NodePath(NamedNode('hidden')) + self.camera = self.render.attachNewNode('camera') + self.dataRoot = NodePath(NamedNode('dataRoot'), DataRelation.getClassType()) + self.dataUnused = NodePath(NamedNode('dataUnused'), DataRelation.getClassType()) + + self.pipe = makeGraphicsPipe() + self.win = self.pipe.makeGraphicsWindow(self.renderTop.node(), + self.camera.node(), + self.dataRoot.node(), + self.initialState) + + self.render2d = NodePath(self.win.setupPanda2d()) + self.cam = self.camera.find('**/+Camera') + self.mak = self.dataRoot.attachNewNode(MouseAndKeyboard(self.win, 0, 'mak')) + self.trackball = self.dataUnused.attachNewNode(Trackball('trackball')) + self.drive = self.dataUnused.attachNewNode(DriveInterface('drive')) + self.mouse2cam = self.dataUnused.attachNewNode(Transform2SG('mouse2cam')) + self.mouse2cam.node().setArc(self.camera.getBottomArc()) + self.useDrive() + + self.buttonThrower = self.mak.attachNewNode(ButtonThrower()) + + if (ShowBase.notify == None): + ShowBase.notify = directNotify.newCategory("ShowBase") + + self.loader = Loader.Loader(self) + + self.eventMgr = eventMgr + + self.messenger = messenger + + self.taskMgr = taskMgr + + self.audioMgr = audioMgr + + self.wantTk = 1 + self.createRootPanel() + + self.restart() + + self.wantSound = 1 + + self.wantMusic = 1 + + def createRootPanel(self): + if self.wantTk: + self.tkroot = Pmw.initialise() + else: + self.tkroot = None + + def igloop(self, state): + self.win.update() + if (self.wantSound): + self.audioMgr.update() + return Task.cont + + def restart(self): + #self.shutdown() + self.taskMgr.spawnTaskNamed(Task.Task(self.igloop), 'igloop') + self.eventMgr.restart() + + def shutdown(self): + self.taskMgr.removeTasksNamed('igloop') + self.eventMgr.shutdown() + + def toggleBackface(self): + self.initialState.toggleBackface() + + def toggleTexture(self): + self.initialState.toggleTexture() + + def toggleWireframe(self): + self.initialState.toggleWireframe() + + def disableMouse(self): + self.drive.reparentTo(self.dataUnused) + self.trackball.reparentTo(self.dataUnused) + self.mouse2cam.reparentTo(self.dataUnused) + self.mouseInterface = None + + def setMouseOnArc(self, newArc): + self.mouse2cam.node().setArc(newArc) + + def useDrive(self): + """ + Toggle mouse action to drive mode + """ + # Get rid of the trackball + self.trackball.reparentTo(self.dataUnused) + # Update the mouseInterface to point to the drive + self.mouseInterface = self.drive + self.drive.node().reset() + self.drive.reparentTo(self.mak) + # Hookup the drive to the camera + self.mouse2cam.reparentTo(self.drive) + # Set the height to a good eyeheight + self.drive.node().setZ(4.0) + + def useTrackball(self): + """ + Toggle mouse action to trackball mode + """ + # Get rid of the drive + self.drive.reparentTo(self.dataUnused) + # Update the mouseInterface to point to the trackball + self.mouseInterface = self.trackball + # Hookup the trackball to the camera + self.trackball.reparentTo(self.mak) + self.mouse2cam.reparentTo(self.trackball) + + def run(self): + self.taskMgr.run() + +if __name__ == '__main__': + base = ShowBase() + base.run() + diff --git a/direct/src/showbase/ShowBaseGlobal.py b/direct/src/showbase/ShowBaseGlobal.py new file mode 100644 index 0000000000..9a0bddd6a6 --- /dev/null +++ b/direct/src/showbase/ShowBaseGlobal.py @@ -0,0 +1,16 @@ +"""instantiate global ShowBase object""" + +import ShowBase + +base = ShowBase.ShowBase() + +# Make some global aliases for convenience +render2d = base.render2d +render = base.render +hidden = base.hidden +camera = base.camera +loader = base.loader +messenger = base.messenger +taskMgr = base.taskMgr +run = base.run +tkroot = base.tkroot diff --git a/direct/src/showbase/Sources.pp b/direct/src/showbase/Sources.pp new file mode 100644 index 0000000000..d0f2f785fd --- /dev/null +++ b/direct/src/showbase/Sources.pp @@ -0,0 +1,14 @@ +#begin lib_target + #define TARGET showbase + #define LOCAL_LIBS \ + pandatoolbase + #define OTHER_LIBS \ + linmath:c putil:c express:c panda:m dtool + + #define SOURCES \ + showBase.cxx showBase.h + + #define IGATESCAN all +#end lib_target + +#define INSTALL_SCRIPTS ppython diff --git a/direct/src/showbase/State.py b/direct/src/showbase/State.py new file mode 100644 index 0000000000..9e42054f4e --- /dev/null +++ b/direct/src/showbase/State.py @@ -0,0 +1,136 @@ + +"""State module: contains State class""" + +from DirectObject import * + + +class State(DirectObject): + + """State class: """ + + def __init__(self, name, enterFunc=None, exitFunc=None, transitions=[]): + """__init__(self, string, func, func, string[]) + State constructor: takes name, enter func, exit func, and + a list of states it can transition to.""" + self.setName(name) + self.setEnterFunc(enterFunc) + self.setExitFunc(exitFunc) + self.setTransitions(transitions) + self.__FSMList = None + + + # setters and getters + + def getName(self): + """getName(self)""" + return(self.__name) + + def setName(self, stateName): + """setName(self, string)""" + self.__name = stateName + + def getEnterFunc(self): + """getEnterFunc(self)""" + return(self.__enterFunc) + + def setEnterFunc(self, stateEnterFunc): + """setEnterFunc(self, func)""" + self.__enterFunc = stateEnterFunc + + def getExitFunc(self): + """getExitFunc(self)""" + return(self.__exitFunc) + + def setExitFunc(self, stateExitFunc): + """setExitFunc(self, func)""" + self.__exitFunc = stateExitFunc + + def getTransitions(self): + """getTransitions(self)""" + return(self.__transitions) + + def setTransitions(self, stateTransitions): + """setTransitions(self, string[])""" + self.__transitions = stateTransitions + + + # support for HFSMs + + def getChildren(self): + """getChildren(self) + Return the list of child FSMs""" + return(self.__FSMList) + + def setChildren(self, FSMList): + """setChildren(self, FSM[]) + Set the children to given list of FSMs""" + self.__FSMList = FSMList + + def addChild(self, FSM): + """addChild(self, FSM) + Add the given FSM to list of child FSMs""" + if (self.__FSMList == None): + self.__FSMList = [FSM] + else: + self.__FSMList.append(FSM) + + def hasChildren(self): + """hasChildren(self) + Return true if state has child FSMs""" + return(self.__FSMList != None) + + def __enterChildren(self): + """__enterChildren(self) + Enter all child FSMs""" + if self.hasChildren(): + for fsm in self.__FSMList: + fsm.request((fsm.getInitialState()).getName()) + + def __exitChildren(self): + """__exitChildren(self) + Exit all child FSMs""" + if self.hasChildren(): + for fsm in self.__FSMList: + fsm.request((fsm.getFinalState()).getName()) + + + # basic State functionality + + def enter(self): + """enter(self) + Call the enter function for this state""" + if (self.__enterFunc != None): + apply(self.__enterFunc) + + #enter child FSMs + self.__enterChildren() + + def exit(self): + """exit(self) + Call the exit function for this state""" + #first exit child FSMs + self.__exitChildren() + + #call exit function if it exists + if (self.__exitFunc != None): + apply(self.__exitFunc) + + def __str__(self): + """__str__(self)""" + return "State: name = %s, enter = %s, exit = %s, trans = %s, children = %s" %\ + (self.__name, self.__enterFunc, self.__exitFunc, self.__transitions, self.__FSMList) + + + + + + + + + + + + + + + diff --git a/direct/src/showbase/Task.py b/direct/src/showbase/Task.py new file mode 100644 index 0000000000..605ca1762c --- /dev/null +++ b/direct/src/showbase/Task.py @@ -0,0 +1,335 @@ + +from PandaModules import * +from DirectNotify import * +from PythonUtil import * + + +exit = -1 +done = 0 +cont = 1 + + +def getTimeFrame(): + # WARNING: If you are testing tasks without an igloop, + # you must manually tick the clock + + # Ask for the time last frame + t = ClockObject.getGlobalClock().getTime() + + # Set the clock to have last frame's time in case we were + # Paused at the prompt for a long time + ClockObject.getGlobalClock().setTime(t) + + # Get the new frame count + f = ClockObject.getGlobalClock().getFrameCount() + + return t, f + + +class Task: + def __init__(self, callback): + self.__call__ = callback + + def setStartTimeFrame(self, startTime, startFrame): + self.starttime = startTime + self.startframe = startFrame + + def setCurrentTimeFrame(self, currentTime, currentFrame): + # Calculate and store this task's time (relative to when it started) + self.time = currentTime - self.starttime + self.frame = currentFrame - self.startframe + +def doLater(delayTime, task, taskName): + task.name = taskName + # make a sequence out of the delay and the task + seq = sequence(pause(delayTime), task) + return seq + +def pause(delayTime): + def func(self): + if (self.time < self.delayTime): + return cont + else: + # Time is up, return done + TaskManager.notify.debug('pause done: ' + self.name) + return done + task = Task(func) + task.name = 'pause' + task.delayTime = delayTime + return task + + +def release(): + def func(self): + # A release is immediately done + TaskManager.notify.debug('release done: ' + self.name) + return done + task = Task(func) + task.name = 'release' + return task + + +def sequence(*taskList): + return make_sequence(taskList) + + +def make_sequence(taskList): + def func(self): + # If we got to the end of the list, this sequence is done + if (self.index >= len(self.taskList)): + TaskManager.notify.debug('sequence done: ' + self.name) + return done + else: + task = self.taskList[self.index] + # If this is a new task, set it's start time and frame + if (self.index > self.prevIndex): + task.setStartTimeFrame(self.time, self.frame) + self.prevIndex = self.index + + # Calculate this task's time since it started + task.setCurrentTimeFrame(self.time, self.frame) + + # Execute the current task + ret = task(task) + + # Check the return value from the task + # If this current task wants to continue, + # come back to it next frame + if (ret == cont): + return cont + + # If this task is done, increment the index so that next frame + # we will start executing the next task on the list + elif (ret == done): + self.index = self.index + 1 + return cont + + # If this task wants to exit, the sequence exits + elif (ret == exit): + return exit + + task = Task(func) + task.name = 'sequence' + task.taskList = taskList + task.prevIndex = -1 + task.index = 0 + return task + +def makeSpawner(task, taskName, taskMgr): + def func(self): + self.taskMgr.spawnTaskNamed(self.task, self.taskName) + return done + newTask = Task(func) + newTask.name = taskName + "-spawner" + newTask.task = task + newTask.taskName = taskName + newTask.taskMgr = taskMgr + return newTask + + +def makeSequenceFromTimeline(timelineList, taskMgr): + timeline = [] + lastPause = 0 + sortedList = list(timelineList) + sortedList.sort() + for triple in sortedList: + t = triple[0] - lastPause + lastPause = triple[0] + task = triple[1] + taskName = triple[2] + timeline.append(pause(t)) + timeline.append(makeSpawner(task, taskName, taskMgr)) + return make_sequence(timeline) + + +def timeline(*timelineList): + def func(self): + # Step our sub task manager (returns the number of tasks remaining) + lenTaskList = self.taskMgr.step() + + # The sequence start time is the same as our start time + self.sequence.time = self.time + self.sequence.frame = self.frame + + if (not self.sequenceDone): + # Execute the sequence for this frame + seqRet = self.sequence(self.sequence) + # See if sequence is done + if (seqRet == done): + self.sequenceDone = 1 + # See if the timeline is done + if (lenTaskList == 0): + TaskManager.notify.debug('timeline done: ' + self.name) + return done + else: + return cont + else: + return cont + else: + return cont + task = Task(func) + task.name = 'timeline' + task.taskMgr = TaskManager() + task.sequence = makeSequenceFromTimeline(timelineList, task.taskMgr) + task.sequenceDone = 0 + return task + + +class TaskManager: + + notify = None + + def __init__(self): + self.running = 0 + self.taskList = [] + self.currentTime, self.currentFrame = getTimeFrame() + if (TaskManager.notify == None): + TaskManager.notify = directNotify.newCategory("TaskManager") + #TaskManager.notify.setDebug(1) + + + def spawnMethodNamed(self, func, name): + task = Task(func) + self.spawnTaskNamed(task, name) + + def spawnTaskNamed(self, task, name): + TaskManager.notify.debug('spawning task named: ' + name) + task.name = name + task.setStartTimeFrame(self.currentTime, self.currentFrame) + self.taskList.append(task) + return task + + def removeAllTasks(self): + # Make a shallow copy so we do not modify the list in place + taskListCopy = self.taskList[:] + for task in taskListCopy: + self.removeTask(task) + + def removeTask(self, task): + TaskManager.notify.debug('removing task: ' + `task`) + try: + self.taskList.remove(task) + except: + pass + # TODO: upon death + + def removeTasksNamed(self, taskName): + removedTasks = [] + TaskManager.notify.debug('removing tasks named: ' + taskName) + + # Find the tasks that match by name and make a list of them + for task in self.taskList: + if (task.name == taskName): + removedTasks.append(task) + + # Now iterate through the tasks we need to remove and remove them + for task in removedTasks: + self.removeTask(task) + + # Return the number of tasks removed + return len(removedTasks) + + def step(self): + self.currentTime, self.currentFrame = getTimeFrame() + for task in self.taskList: + task.setCurrentTimeFrame(self.currentTime, self.currentFrame) + # Run the task and check the return value + ret = task(task) + if (ret == cont): + continue + elif (ret == done): + self.removeTask(task) + elif (ret == exit): + self.removeTask(task) + else: + raise 'invalid task state' + return len(self.taskList) + + def run(self): + self.running = 1 + while self.running: + try: + self.step() + except KeyboardInterrupt: + self.stop() + + def stop(self): + # Set a flag so we will stop before beginning next frame + self.running = 0 + + def __repr__(self): + str = '' + str = str + 'taskList\n' + str = str + '--------------------\n' + for task in self.taskList: + str = str + ' ' + task.name + '\n' + return str + + +""" +import Task +from ShowBaseGlobal import * # to get taskMgr, and run() + +# sequence + +def seq1(self): + print 'seq1' + return Task.done +def seq2(self): + print 'seq2' + return Task.done + +t = Task.sequence(Task.pause(1.0), Task.Task(seq1), Task.release(), + Task.pause(3.0), Task.Task(seq2)) +taskMgr.spawnTaskNamed(t, 'sequence') +run() + +# timeline + +def keyframe1(self): + print 'keyframe1' + return Task.done +def keyframe2(self): + print 'keyframe2' + return Task.done +def keyframe3(self): + print 'keyframe3' + return Task.done + +testtl = Task.timeline( + (0.5, Task.Task(keyframe1), 'key1'), + (0.6, Task.Task(keyframe2), 'key2'), + (0.7, Task.Task(keyframe3), 'key3') + ) + +t = taskMgr.spawnTaskNamed(testtl, 'timeline') +run() + +# do later - returns a sequence + +def foo(self): + print 'later...' + return Task.done + +seq = Task.doLater(3.0, Task.Task(foo), 'fooLater') +t = taskMgr.spawnTaskNamed(seq, 'doLater-fooLater') +run() + +# tasks with state + +someValue = 1 + +def func(self): + if (self.someValue > 10): + print 'true!' + return Task.done + else: + self.someValue = self.someValue + 1 + return Task.cont + +task = Task.Task(func) +# set task state here +task.someValue = someValue +t = taskMgr.spawnTaskNamed(task, 'funcTask') +run() +""" diff --git a/direct/src/showbase/TaskManagerGlobal.py b/direct/src/showbase/TaskManagerGlobal.py new file mode 100644 index 0000000000..ec20e1e418 --- /dev/null +++ b/direct/src/showbase/TaskManagerGlobal.py @@ -0,0 +1,4 @@ +"""TaskManagerGlobal module: contains the global task manager""" +import Task + +taskMgr = Task.TaskManager() diff --git a/direct/src/showbase/TkGlobal.py b/direct/src/showbase/TkGlobal.py new file mode 100644 index 0000000000..8920eb4926 --- /dev/null +++ b/direct/src/showbase/TkGlobal.py @@ -0,0 +1,21 @@ + +from Tkinter import * +import Pmw + +import Task + +def tkloop(self): + # Do all the tkinter events waiting on this frame + # dooneevent will return 0 if there are no more events + # waiting or 1 if there are still more. + # DONT_WAIT tells tkinter not to block waiting for events + while tkinter.dooneevent(tkinter.DONT_WAIT): + pass + # Run forever + return Task.cont + +# Get the taskMgr +from TaskManagerGlobal import * + +# Spawn this task +taskMgr.spawnTaskNamed(Task.Task(tkloop), "tkloop") diff --git a/direct/src/showbase/ppython b/direct/src/showbase/ppython new file mode 100755 index 0000000000..db45c99f52 --- /dev/null +++ b/direct/src/showbase/ppython @@ -0,0 +1,36 @@ +#! /bin/sh + +if [ -z "$CYGWIN_ROOT" ]; then + # Add our LD_LIBRARY_PATH to the python path so we can pull in panda modules + PYTHONPATH="$DIRECT"/src/all/python:"$DIRECT"/lib/py:"$LD_LIBRARY_PATH" + export PYTHONPATH + + # Run unbuffered output, with any options passed in + exec python -u $* + exit 1 +fi + +# Under Windows/Cygwin, we have to do this whole thing a little differently. + +# First, we have to mangle the LD_LIBRARY_PATH into a series of Windows +# paths separated by semicolons. +PYTHONPATH= +for path in `echo $LD_LIBRARY_PATH | sed 'y/:/ /'`; do + PYTHONPATH=$PYTHONPATH\;`cygpath -w $path` +done + +# And now prefix that with the python source directories. +PYTHONPATH=`cygpath -w $DIRECT/src/all/python`\;`cygpath -w $DIRECT/lib/py`$PYTHONPATH +export PYTHONPATH + +# We can't run with -u under windows for some reason. But we do need to +# make a distinction between python_d and python. We'll accept the user +# parameter -d to indicate we should run python_d. + +if [ x"$1" = x"-d" ]; then + shift 1 + exec python_d $* +else + exec python $* +fi + diff --git a/direct/src/showbase/showBase.cxx b/direct/src/showbase/showBase.cxx new file mode 100644 index 0000000000..c0f5d6635e --- /dev/null +++ b/direct/src/showbase/showBase.cxx @@ -0,0 +1,262 @@ +// Filename: showBase.cxx +// Created by: shochet (02Feb00) +// +//////////////////////////////////////////////////////////////////// + +#include "showBase.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static CollisionTraverser *collision_traverser = NULL; + +void render_frame(GraphicsPipe *pipe, + NodeAttributes &initial_state) { + int num_windows = pipe->get_num_windows(); + for (int w = 0; w < num_windows; w++) { + GraphicsWindow *win = pipe->get_window(w); + win->get_gsg()->render_frame(initial_state); + } + ClockObject::get_global_clock()->tick(); + throw_event("NewFrame"); +} + +// to be used with new display callback system +class DisplayCallback : public GraphicsWindow::Callback { +public: + DisplayCallback(GraphicsPipe *pipe, Node *render, NodeAttributes *initial_state) : + _pipe(pipe), + _render(render), + _initial_state(initial_state), + _app_traverser(RenderRelation::get_class_type()) { } + + virtual void draw(bool) { + _app_traverser.traverse(_render); + render_frame(_pipe, *_initial_state); + } + + PT(GraphicsPipe) _pipe; + PT(Node) _render; + NodeAttributes *_initial_state; + AppTraverser _app_traverser; +}; + + + +// to be used with new display callback system +class IdleCallback : public GraphicsWindow::Callback { +public: + IdleCallback(Node *render, Node *data_root) { + _render = render; + _data_root = data_root; + } + + virtual void idle(void) { + // Initiate the data traversal, to send device data down its + // respective pipelines. + traverse_data_graph(_data_root); + if (collision_traverser != (CollisionTraverser *)NULL) { + collision_traverser->traverse(_render); + } + } + + PT(Node) _render; + PT(Node) _data_root; +}; + + +PT(GraphicsPipe) make_graphics_pipe() { + PT(GraphicsPipe) main_pipe; + + // load display modules + GraphicsPipe::resolve_modules(); + + nout << "Known pipe types:" << endl; + GraphicsPipe::_factory.write_types(nout, 2); + + // Create a window + main_pipe = GraphicsPipe::_factory. + make_instance(InteractiveGraphicsPipe::get_class_type()); + + if (main_pipe == (GraphicsPipe*)0L) { + nout << "No interactive pipe is available! Check your Configrc!\n"; + return NULL; + } + + nout << "Opened a '" << main_pipe->get_type().get_name() + << "' interactive graphics pipe." << endl; + return main_pipe; +} + +PT(GraphicsWindow) make_graphics_window(GraphicsPipe *pipe, + NamedNode *render, + NamedNode *camera, + NamedNode *data_root, + NodeAttributes &initial_state) { + PT(GraphicsWindow) main_win; + ChanCfgOverrides override; + + // Turn on backface culling + CullFaceAttribute *cfa = new CullFaceAttribute; + cfa->set_mode(CullFaceProperty::M_cull_clockwise); + initial_state.set_attribute(CullFaceTransition::get_class_type(), cfa); + DepthTestAttribute *dta = new DepthTestAttribute; + initial_state.set_attribute(DepthTestTransition::get_class_type(), dta); + DepthWriteAttribute *dwa = new DepthWriteAttribute; + initial_state.set_attribute(DepthWriteTransition::get_class_type(), dwa); + + override.setField(ChanCfgOverrides::Mask, + ((unsigned int)(W_DOUBLE|W_DEPTH|W_MULTISAMPLE))); + + main_win = ChanConfig(pipe, "single", camera, render, override); + assert(main_win != (GraphicsWindow*)0L); + + DisplayCallback *dcb = new DisplayCallback(pipe, render, &initial_state); + IdleCallback *icb = new IdleCallback(render, data_root); + + // Set draw and idle callbacks + main_win->set_draw_callback(dcb); + main_win->set_idle_callback(icb); + + return main_win; +} + +// Create a scene graph, associated with the indicated window, that +// can contain 2-d geometry and will be rendered on top of the +// existing 3-d window. Returns the top node of the scene graph. +NodePath +setup_panda_2d(PT(GraphicsWindow) win) { + PT(Node) render2d_top; + + render2d_top = new NamedNode("render2d_top"); + Node *render2d = new NamedNode("render2d"); + RenderRelation *render2d_arc = new RenderRelation(render2d_top, render2d); + + // Set up some overrides to turn off certain properties which we + // probably won't need for 2-d objects. + render2d_arc->set_transition(new DepthTestTransition(DepthTestProperty::M_none), 1); + render2d_arc->set_transition(new DepthWriteTransition(DepthWriteTransition::off()), 1); + render2d_arc->set_transition(new LightTransition(LightTransition::all_off()), 1); + render2d_arc->set_transition(new MaterialTransition(MaterialTransition::off()), 1); + render2d_arc->set_transition(new CullFaceTransition(CullFaceProperty::M_cull_none), 1); + + // Create a 2-d camera. + Camera *cam2d = new Camera("cam2d"); + new RenderRelation(render2d, cam2d); + cam2d->set_scene(render2d_top); + + Frustumf frustum2d; + frustum2d.make_ortho(-1000,1000); + cam2d->set_projection(OrthoProjection(frustum2d)); + + // Now create a new layer. + GraphicsChannel *chan = win->get_channel(0); + nassertr(chan != (GraphicsChannel *)NULL, NodePath()); + + GraphicsLayer *layer = chan->make_layer(); + nassertr(layer != (GraphicsLayer *)NULL, NodePath()); + + DisplayRegion *dr = layer->make_display_region(); + nassertr(dr != (DisplayRegion *)NULL, NodePath()); + dr->set_camera(cam2d); + + return NodePath(render2d_arc); +} + +// Enable the collision traversal using a particular traverser. +void set_collision_traverser(CollisionTraverser *traverser) { + collision_traverser = traverser; +} +// Stop the collision traversal. +void clear_collision_traverser() { + collision_traverser = NULL; +} + + +void toggle_wireframe(NodeAttributes &initial_state) { + static bool wireframe_mode = false; + + wireframe_mode = !wireframe_mode; + if (!wireframe_mode) { + // Set the normal, filled mode on the render arc. + RenderModeAttribute *rma = new RenderModeAttribute; + rma->set_mode(RenderModeProperty::M_filled); + CullFaceAttribute *cfa = new CullFaceAttribute; + cfa->set_mode(CullFaceProperty::M_cull_clockwise); + initial_state.set_attribute(RenderModeTransition::get_class_type(), rma); + initial_state.set_attribute(CullFaceTransition::get_class_type(), cfa); + + } else { + // Set the initial state up for wireframe mode. + RenderModeAttribute *rma = new RenderModeAttribute; + rma->set_mode(RenderModeProperty::M_wireframe); + CullFaceAttribute *cfa = new CullFaceAttribute; + cfa->set_mode(CullFaceProperty::M_cull_none); + initial_state.set_attribute(RenderModeTransition::get_class_type(), rma); + initial_state.set_attribute(CullFaceTransition::get_class_type(), cfa); + } +} + + +void toggle_backface(NodeAttributes &initial_state) { + static bool backface_mode = false; + + // Toggle the state variable + backface_mode = !backface_mode; + + if (backface_mode) { + // Turn backface culling off + CullFaceAttribute *cfa = new CullFaceAttribute; + cfa->set_mode(CullFaceProperty::M_cull_none); + initial_state.set_attribute(CullFaceTransition::get_class_type(), cfa); + } else { + // Turn backface culling on + CullFaceAttribute *cfa = new CullFaceAttribute; + cfa->set_mode(CullFaceProperty::M_cull_clockwise); + initial_state.set_attribute(CullFaceTransition::get_class_type(), cfa); + } +} + + +void toggle_texture(NodeAttributes &initial_state) { + static bool textures_enabled = true; + + textures_enabled = !textures_enabled; + if (textures_enabled) { + // Remove the override from the initial state. + initial_state.clear_attribute(TextureTransition::get_class_type()); + } else { + // Set an override on the initial state to disable texturing. + TextureAttribute *ta = new TextureAttribute; + ta->set_priority(100); + initial_state.set_attribute(TextureTransition::get_class_type(), ta); + } +} diff --git a/direct/src/showbase/showBase.h b/direct/src/showbase/showBase.h new file mode 100644 index 0000000000..d20d4f8dcc --- /dev/null +++ b/direct/src/showbase/showBase.h @@ -0,0 +1,39 @@ +// Filename: showBase.h +// Created by: shochet (02Feb00) +// +//////////////////////////////////////////////////////////////////// + +#ifndef SHOWBASE_H +#define SHOWBASE_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +class CollisionTraverser; + +EXPCL_DIRECT PT(GraphicsPipe) make_graphics_pipe(); +EXPCL_DIRECT PT(GraphicsWindow) + make_graphics_window(GraphicsPipe *pipe, + NamedNode *render, + NamedNode *camera, + NamedNode *data_root, + NodeAttributes &initial_state + ); + +EXPCL_DIRECT NodePath setup_panda_2d(PT(GraphicsWindow) win); + +EXPCL_DIRECT void set_collision_traverser(CollisionTraverser *traverser); +EXPCL_DIRECT void clear_collision_traverser(); + +EXPCL_DIRECT void toggle_wireframe(NodeAttributes &initial_state); +EXPCL_DIRECT void toggle_texture(NodeAttributes &initial_state); +EXPCL_DIRECT void toggle_backface(NodeAttributes &initial_state); + +#endif