Announcement Announcement Module
Collapse
No announcement yet.
Newbie: AOP wrapping and argument numbers Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Newbie: AOP wrapping and argument numbers

    Hi there,
    I'm trying to wrap an object, and I'm having some trouble.

    Code:
            
    c = ProxyFactoryComponent(target = Character(Circle(Constants.radius), brain), interceptors = tagAdvisor)
    c.setPosition(vec.randomVec1(gs.worldDim, c.getPosition(), tmp))
    I'm using the WrappingInterceptor as detailed in the documentation, but changed slightly (as I just want to know it's wrapping, no need to mess with the output)

    Code:
        def invoke(self, invocation):
            print "Caught a tag change"
            results = invocation.proceed()
            return results
    When I wrap the Character class, Python starts complaining:

    Code:
    Traceback (most recent call last):
      File "./tag.py", line 141, in <module>
        main()
      File "./tag.py", line 121, in main
        setupCharacters(gs, gui, kb)
      File "./tag.py", line 79, in setupCharacters
        c.setPosition(vec.randomVec1(gs.worldDim, c.getPosition(), tmp))
      File "/Library/Python/2.5/site-packages/springpython/aop/__init__.py", line 186, in dispatch
        return invocation.__getattr__(self.method_name)(*args, **kwargs)
      File "/Library/Python/2.5/site-packages/springpython/aop/__init__.py", line 77, in __call__
        return self.proceed()
      File "/Library/Python/2.5/site-packages/springpython/aop/__init__.py", line 63, in proceed
        return interceptor.invoke(self)
      File "/Library/Python/2.5/site-packages/springpython/aop/__init__.py", line 140, in invoke
        return getattr(invocation.instance, invocation.method_name)(invocation.args)
    TypeError: getPosition() takes exactly 1 argument (2 given)
    The wrapper is sending two arguments, but the nuts and bolts of Python I'm not too familiar with, so I don't know what's happening here.

    getPosition in the Character class is defined as:

    Code:
        def getPosition(self):
            return self.shape.position
    I managed to work through the tutorial code fine, but I'm not getting very far here. I added a debug to __init.py__, and couldn't see anything wrong:
    Code:
                self.logger.debug("No match, bypassing advice, going straight to targetClass.")
                
                print str(invocation.instance) + ", " + str(invocation.method_name) + " , " + str(invocation.args)
                return getattr(invocation.instance, invocation.method_name)(invocation.args)
    outputted

    Code:
    <character.Character instance at 0x2c30a58>, getPosition , ()
    which seems perfectly fine.

    Any ideas would be really appreciated!

    EDIT:
    I'm sort of getting closer, but at the same time sort of not
    Code:
    <character.Character instance at 0x2c30a30>, getPosition , () so invocation array length is 0
    <character.Character instance at 0x2c30a30>, getPosition , (array([ 328.20498978,  184.92346538]),) so invocation array length is 1
    So Spring Python is trying to call getPosition() with some arguments, but no where in the code is getPosition called with any arguments. I'm doing something wrong with SP, or SP doesn't like the way the code I'm trying to wrap around is written. The code is from John Funge's AI4Games (can't post the URL because forum anti-spam won't let me), so you can download it and see. I then tried adding optional arguments to the methods it is getting upset with the length of, and then something else was being cast into the wrong type. Weird, weird stuff

    This is driving me pretty nuts now! Would really appreciate help when someone gets a chance
    Last edited by Lewisham; Nov 3rd, 2008, 08:21 PM.

  • #2
    You have uncovered a very interesting problem in Spring Python's AOP module

    Thanks for finding this issue! Good news: I replicated your problem and found a temporary workaround without altering Spring Python itself.

    I checked out ai4games from subversion (r.428), and then installed PyOpenGL and pygame in order to run these things. I had to make a couple of patches, but I finally got it running. I have the patches included at the bottom of this thread. I also opened SESPRINGPYTHONPY-77 to track this fix.

    I then turned on debugging to see what Spring Python was doing, and I started adding more print statements to see what was happening. Sometimes, the very proxy-ish nature of AOP can get in your way when trying to debug that very code.

    Anyway, I seemed to discover a pattern where if you are calling an intercepted method, the arguments should not contain intercepting calls themselves. It seems Spring Python's AOP doesn't handle that yet.

    The best example is this line from tag.py's setupCharacters:
    Code:
    c.setPosition(vec.randomVec1(gs.worldDim, c.getPosition(), tmp))
    In this case, setPosition will be intercepted, however it blows up. However when I pull the c.getPosition() call out of into a local variable, everything works.

    Code:
    pos = c.getPosition()
    c.setPosition(vec.randomVec1(gs.worldDim, pos, tmp))
    It appeared that in one way or another, the AOP segments arguments from one of these calls was leaking into another. I don't precisely have my finger on it, but if a method that had arguments leaked into a method without arguments, it would break when calling the target method.

    P.S. That is a neat little app! I haven't opened it up to see what it is doing, but it looks like it calculates mass, velocity, vectors, etc. to show from random, free-motion system.

    brain.py
    Code:
    ### Eclipse Workspace Patch 1.0
    #P ai4games
    Index: tag/brain.py
    ===================================================================
    --- tag/brain.py	(revision 428)
    +++ tag/brain.py	(working copy)
    @@ -63,7 +63,7 @@
     class BrainWander(Brain):
     
         def calcAction(self):
    -        vec.normalize(vec.random(self.action.direction), self.action.direction)
    +        vec.normalize(vec.randomVec(self.action.direction), self.action.direction)
             self.action.speed = util.clamp(util.uniform(), 0.25, 1.0)
     
     class BrainPeriodic(Brain):
    simulator.py
    Code:
    ### Eclipse Workspace Patch 1.0
    #P ai4games
    Index: tag/simulator.py
    ===================================================================
    --- tag/simulator.py	(revision 428)
    +++ tag/simulator.py	(working copy)
    @@ -32,7 +32,8 @@
                 # calculate required force from the acceleration
                 force = vec.clampMaxLength(vec.scale(acceleration, c.mass, self.n), c.maxForce, self.t)
                 # re-calculate acceleration for new (possibly) clamped force
    -            c.setVelocity(vec.clampMaxLength(c.getVelocity() + vec.scale(force, deltaT/c.mass, self.n), c.maxSpeed, self.t))
    +            v = c.getVelocity()
    +            c.setVelocity(vec.clampMaxLength(v + vec.scale(force, deltaT/c.mass, self.n), c.maxSpeed, self.t))
     
         def resolveCollisions(self):
             e = 0.75              # coefficient of restitution
    @@ -111,7 +112,9 @@
     
         def updateGameState(self, deltaT):
             for c in self.gs.characters:
    -            c.setPosition(vec.wrap(c.getPosition() + vec.scale(c.getVelocity(), deltaT, self.t), self.gs.worldDim, self.n))
    +            pos = c.getPosition()
    +            v = c.getVelocity()
    +            c.setPosition(vec.wrap(pos + vec.scale(v, deltaT, self.t), self.gs.worldDim, self.n))
     
         def forward(self, deltaT):
             self.calcActions()
    tag.py
    Code:
    ### Eclipse Workspace Patch 1.0
    #P ai4games
    Index: tag/tag.py
    ===================================================================
    --- tag/tag.py	(revision 428)
    +++ tag/tag.py	(working copy)
    @@ -16,6 +16,10 @@
     
     import pygame
     
    +import logging
    +from springpython.aop import ProxyFactoryComponent
    +from springpython.aop import MethodInterceptor
    +
     class Constants:
         pass
     
    @@ -57,6 +61,12 @@
                         minPeriod,
                         maxPeriod)))
     
    +class WrappingInterceptor(MethodInterceptor):
    +    def invoke(self, invocation):
    +        print "Caught a tag change while calling %s %s %s" % (invocation.method_name, str(invocation.args), invocation.kwargs)
    +        results = invocation.proceed()
    +        return results
    +
     def setupCharacters(gs, gui, kb):
         rendererNPC = RendererCharacter(Constants.npcColorName, Constants.flashColorName)
         rendererPC = RendererCharacter(Constants.pcColorName, Constants.flashColorName)
    @@ -71,8 +81,15 @@
                 brain = BrainPC(percepts, kb)
             else:
                 brain = setupNPCBrain("wander", percepts)
    -        c = Character(Circle(Constants.radius), brain)
    -        c.setPosition(vec.randomVec1(gs.worldDim, c.getPosition(), tmp))
    +            
    +            
    +        tagAdvisor = WrappingInterceptor()
    +        
    +        c = ProxyFactoryComponent(target = Character(Circle(Constants.radius), brain),
    +                                  interceptors = tagAdvisor)
    +        pos = c.getPosition()
    +        c.setPosition(vec.randomVec1(gs.worldDim, pos, tmp))
    +        
             if i == 0:
                 c.setRenderer(rendererPC)
             else:
    @@ -128,6 +145,15 @@
         gui.destroyWindow()
     
     if __name__ == "__main__":
    +    logger = logging.getLogger("springpython")
    +    loggingLevel = logging.INFO
    +    logger.setLevel(loggingLevel)
    +    ch = logging.StreamHandler()
    +    ch.setLevel(loggingLevel)
    +    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") 
    +    ch.setFormatter(formatter)
    +    logger.addHandler(ch)
    +
         main()
     
     # TODO:
    vec.py
    Code:
    ### Eclipse Workspace Patch 1.0
    #P ai4games
    Index: tag/vec.py
    ===================================================================
    --- tag/vec.py	(revision 428)
    +++ tag/vec.py	(working copy)
    @@ -44,10 +44,8 @@
         return w
     
     def randomVec(u):
    -    # random.uniform(low=-1.0, high=1.0, size=u.shape, u)
    +    return random.random(u.shape)
     
    -    return u
    -
     def zeroize(u):
         return set(0.0, u)

    Comment


    • #3
      Originally posted by gregturn View Post
      Thanks for finding this issue! Good news: I replicated your problem and found a temporary workaround without altering Spring Python itself.
      Greg,
      You are a superstar! I'll try out the patches later (it's a little early and I haven't drunk any coffee yet!) but I can't wait to do so.

      ai4games is exactly as you say it is; it's supposed to be a good sandbox for trying out certain AI behaviors like steering; decision-making and things like that. The guy who writes it, John Funge, teaches a Game AI class at UC Santa Cruz, and we're looking into how we could make some cleaner code using cross-cutting concerns.

      Thanks ever so much Greg, you went way beyond the call of duty. I really, really appreciate it.

      Comment


      • #4
        Hey Greg,
        Thanks for the patched code. It did help a lot, but unfortunately I still can't use RegexpMethodPointcutAdvisor, as it exhibits the same behavior even on the patched code. This is a bit of a downer, but I can work around it
        Last edited by Lewisham; Nov 5th, 2008, 09:52 PM.

        Comment


        • #5
          Guess what...there was another bug in there.

          I really appreciate your usage of Spring Python AOP, mostly because you exposed two key bugs that I was able to code automated tests for and get fixed this morning.

          I can't merge these changes to the trunk yet because I'm waiting on the build team to update the CI server. However, I included a patch you can apply to src/springpython/aop/__init__.py that should get things going. NOTE: This patch fixes both the original problem you found, along with the RegexpMethodPointcut problem you just reported.

          Code:
          Index: src/springpython/aop/__init__.py
          ===================================================================
          --- src/springpython/aop/__init__.py	(revision 356)
          +++ src/springpython/aop/__init__.py	(working copy)
          @@ -16,6 +16,7 @@
              NOTE: This module contains parts of PyContainer written by Rafal Sniezynski.
              They have been adapted to work outside the container.
           """
          +import copy
           import logging
           import re
           import types
          @@ -46,7 +47,7 @@
                   self.method_name = method_name
                   self.args = args
                   self.kwargs = kwargs
          -        self.intercept_stack = interceptors
          +        self.intercept_stack = copy.copy(interceptors)
                   self.intercept_stack.append(FinalInterceptor()) 
                   self.logger = logging.getLogger("springpython.aop.MethodInvocation")
           
          @@ -137,7 +138,7 @@
                       return invocation.proceed()
                   else:
                       self.logger.debug("No match, bypassing advice, going straight to targetClass.")
          -            return getattr(invocation.instance, invocation.method_name)(invocation.args)
          +            return getattr(invocation.instance, invocation.method_name)(*invocation.args, **invocation.kwargs)
           
               def __setattr__(self, name, value):
                   """If "advice", make sure it is a list. Anything else, pass through to simple assignment.
          @@ -178,13 +179,6 @@
                       self.interceptors = [interceptors]
                   self.logger = logging.getLogger("springpython.aop.AopProxy")
           
          -    def dispatch(self, *args, **kwargs):
          -        """This method is returned to the caller through __getattr__, to emulate all function calls being sent to the 
          -        target object. This allow this object to serve as a proxying agent for the target object."""
          -        self.logger.debug("Calling AopProxy.%s(%s)" % (self.method_name, args))
          -        invocation = MethodInvocation(self.target, self.method_name, args, kwargs, self.interceptors)
          -        return invocation.__getattr__(self.method_name)(*args, **kwargs)
          -
               def __getattr__(self, name):
                   """If any of the parameters are local objects, they are immediately retrieved. Callables cause the dispatch method
                   to be return, which forwards callables through the interceptor stack. Target attributes are retrieved directly from
          @@ -194,9 +188,20 @@
                   else:
                       attr = getattr(self.target, name)
                       if not callable(attr):
          -               return attr
          -            self.method_name = name
          -            return self.dispatch
          +                return attr
          +            
          +            def dispatch(*args, **kwargs):
          +                """This method is returned to the caller emulating the function call being sent to the 
          +                target object. This services as a proxying agent for the target object."""
          +                invocation = MethodInvocation(self.target, name, args, kwargs, self.interceptors)
          +                ##############################################################################
          +                # NOTE:
          +                # getattr(invocation, name) doesn't work here, because __str__ will print
          +                # the MethodInvocation's string, instead of trigger the interceptor stack.
          +                ##############################################################################
          +                return invocation.__getattr__(name)(*args, **kwargs)  
          +
          +            return dispatch
                   
           class ProxyFactory(object):
               """This object helps to build AopProxy objects programmatically. It allows configuring advice and target objects.
          With this patch, I was able to revert some changes to ai4games, so that NOW the only patches I have to it are:
          Code:
          ### Eclipse Workspace Patch 1.0
          #P ai4games
          Index: tag/brain.py
          ===================================================================
          --- tag/brain.py	(revision 428)
          +++ tag/brain.py	(working copy)
          @@ -63,7 +63,7 @@
           class BrainWander(Brain):
           
               def calcAction(self):
          -        vec.normalize(vec.random(self.action.direction), self.action.direction)
          +        vec.normalize(vec.randomVec(self.action.direction), self.action.direction)
                   self.action.speed = util.clamp(util.uniform(), 0.25, 1.0)
           
           class BrainPeriodic(Brain):
          and
          Code:
          ### Eclipse Workspace Patch 1.0
          #P ai4games
          Index: tag/vec.py
          ===================================================================
          --- tag/vec.py	(revision 428)
          +++ tag/vec.py	(working copy)
          @@ -44,10 +44,8 @@
               return w
           
           def randomVec(u):
          -    # random.uniform(low=-1.0, high=1.0, size=u.shape, u)
          +    return random.random(u.shape)
           
          -    return u
          -
           def zeroize(u):
               return set(0.0, u)
          and finally
          Code:
          ### Eclipse Workspace Patch 1.0
          #P ai4games
          Index: tag/tag.py
          ===================================================================
          --- tag/tag.py	(revision 428)
          +++ tag/tag.py	(working copy)
          @@ -16,6 +16,11 @@
           
           import pygame
           
          +import logging
          +from springpython.aop import RegexpMethodPointcutAdvisor
          +from springpython.aop import ProxyFactoryComponent
          +from springpython.aop import MethodInterceptor
          +
           class Constants:
               pass
           
          @@ -57,6 +62,12 @@
                               minPeriod,
                               maxPeriod)))
           
          +class WrappingInterceptor(MethodInterceptor):
          +    def invoke(self, invocation):
          +        print "Caught a tag change while calling %s %s %s" % (invocation.method_name, str(invocation.args), invocation.kwargs)
          +        results = invocation.proceed()
          +        return results
          +
           def setupCharacters(gs, gui, kb):
               rendererNPC = RendererCharacter(Constants.npcColorName, Constants.flashColorName)
               rendererPC = RendererCharacter(Constants.pcColorName, Constants.flashColorName)
          @@ -71,8 +82,16 @@
                       brain = BrainPC(percepts, kb)
                   else:
                       brain = setupNPCBrain("wander", percepts)
          -        c = Character(Circle(Constants.radius), brain)
          +            
          +            
          +        tagAdvisor = WrappingInterceptor()
          +        pointcutAdvisor = RegexpMethodPointcutAdvisor(advice = [tagAdvisor],
          +                                                      patterns = [".*getPosition"])
          +
          +        c = ProxyFactoryComponent(target = Character(Circle(Constants.radius), brain),
          +                                  interceptors = pointcutAdvisor)
                   c.setPosition(vec.randomVec1(gs.worldDim, c.getPosition(), tmp))
          +        
                   if i == 0:
                       c.setRenderer(rendererPC)
                   else:
          @@ -128,6 +147,15 @@
               gui.destroyWindow()
           
           if __name__ == "__main__":
          +    logger = logging.getLogger("springpython")
          +    loggingLevel = logging.INFO
          +    logger.setLevel(loggingLevel)
          +    ch = logging.StreamHandler()
          +    ch.setLevel(loggingLevel)
          +    formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") 
          +    ch.setFormatter(formatter)
          +    logger.addHandler(ch)
          +
               main()
           
           # TODO:
          Hope that gets you going!

          Again, thanks for finding these issues.

          Comment


          • #6
            Bonus news: I got the patch for this into 0.8.0's release, which went out yesterday.

            http://forum.springframework.org/showthread.php?t=63130

            Comment


            • #7
              Awesome! I'll get to work and let you know how it goes! Thanks so much for working so hard on this, I really appreciate it.

              EDIT: Fantastic, it works brilliantly. Thank you so much. It looks like ai4games Rev 428 is actually broken and you seem to have patched it all by yourself! I rolled back and manually patched Rev 427, and it works amazingly. I'll be releasing the code as/when it's finished so you can take a look. Maybe (if I didn't make it too horribly broken) you could use it as a tutorial on your site. It would be great to see more people try the awesome AOP bits of Spring Python. It's far more elegant than any other Python AOP I've tried.
              Last edited by Lewisham; Nov 10th, 2008, 01:39 AM.

              Comment

              Working...
              X