Page 1 of 1

[solved] Rollback/Custom Class/Function call

Posted: Sat Aug 06, 2016 4:01 pm
by Ragnarok does VN
Hi everyone!

While I am new to Ren'Py specifically and Python generally I am used to object orientated programming, so naturally I applied my knowledge to a little project.

Everything compiles fine, but the rollback feature, which I really want to use, creates some undesireable outcomes.
At a base level, variable values aren't rolled back.

The 'exact' code, that causes my distress is:

Code: Select all

$ story.relationship_circle.face.negative.minor()
#...
chars.circle "sometext"
When I roll back to it, the custom function simply gets called again, but the increment

Code: Select all

def minor(self):
    self._minor += 1
isn't reverted. Essentially each time I scroll up/down the function gets called again and won't be reverted

The whole construct is initialised as

Code: Select all

define chars.circle = Character("a name", kind=nvl, color="#ffaaaa") #bonus initialisation for completeness
# ...
define story.relationship_circle = AvoidableMeter()
I think I am simply not aware of the limits the rollback feature implies to coding, so please enlighten me.
EDIT: I am also able to reproduce the behaviour on each call of a method of a Meter instance, e.i. each time "I step" to the function call, it will get performed, not matter if basic continue, rollback or rollforward

On a side notice, the function call and the say statement above are separated by a call and an if-else construct, although I don't think that it affects the situation.

The relevant class definition is this:

Code: Select all

init -100 python:
    
    class Meter:
        def __init__(self):
            self._trivial = 0
            self._minor = 0
            self._major = 0
            self._key = 0
            self._critical = 0
            self._keyevents = []
            self._criticalevents = []
        
        def trivial(self):
            self._trivial += 1
            
        def minor(self):
            self._minor += 1
            
        def major(self):
            self._major += 1
            
        def key(self, eventname):
            self._key += 1
            self._keyevents.append(eventname)
            
        def critical(self, eventname):
            self._critical += 1
            self._criticalevents.append(eventname)
    
    class FacettableMeter:
        def __init__(self):
            self.positive = Meter()
            self.negative = Meter()
            
    class AvoidableMeter:
        def __init__(self):
            self.avoid = FacettableMeter()
            self.face = FacettableMeter()

Re: Rollback/Custom Class/Function call - undiserable outcom

Posted: Sat Aug 06, 2016 10:11 pm
by trooper6
Your Meter class should inherit from the object class. I am currently in a theatre lobby during intermission, so I can't quote code at you, but if you look at my post in this thread, it will show you how to do that: viewtopic.php?f=8&t=39256&p=420729&hili ... ry#p420729

Re: Rollback/Custom Class/Function call - undiserable outcom

Posted: Sun Aug 07, 2016 9:30 am
by Ragnarok does VN
So I tried what you suggested, which means each of the Meter classes directly inherits from object now.

It didn't fix the problem.

I also tried to increment the attribute directly, like suggested in the linked thread. It looks like this now:

Code: Select all

$ story.relationship_circle.avoid.negative._trivial += 1
That didn't fix it either.
Then I tried to let just the "Meter" class inherit from object, not the other ones, the literal suggestion. Which changed nothing like expected. I reverted it to let all xMeter classes inherit from object.

I then suspected nvl-mode to be the culprit, but it turned out the bug also persists in adv.

I tried the regular save feature, but it seems no values of the whole construct get saved.

Also, maybe I was wrong with thinking that the "call [label]"-"return" statement tree wasn't a problem, here is an overview:

Code: Select all

## relevant content of script.rpy

label start:
    
    # ... some other call we always return from ...
    
    call line_introduction
    
    $ result = str(story.relationship_circle)
    narrator "story.relationship_circle = [result]" ## Tag: result (for this discussion)

    # ... some more str(story.x) ...

    return

################################
## relevant content of line_introduction.rpy

label line_introduction:

    # ... some other call we always return from ...

    call line_introduction_credit_question

    return

# ... some code ...

label line_introduction_credit_question:

    # ... some dialog ....

    if trigger.credit_granted:
        # ... other WIP call, currently not used
    else:
        call line_introduction_credit_not_granted
    
    return

# ... some code ...

label line_introduction_credit_not_granted:

    # ... some dialog ....
    
    menu:
        chars.line "question"
        
        "option 1":
            nvl clear
            
            chars.line "say option 1" ## tag: option 1 (for this discussion)
            $ story.relationship_circle.avoid.negative.trivial() ## tag: option 1 fcall A
            $ story.relationship_circle.face.negative.minor() ## tag: option 1 fcall B
            
            call line_introduction_for_better_or_worse(positivereaction=False)
        
        "option 2":
            nvl clear
            
            chars.line "say option 2" ## tag: option 2
            $ story.relationship_circle.face.negative.minor() ## tag: option 2 fcall
            
            call line_introduction_for_better_or_worse(positivereaction=False)
        
        "option 3":
            nvl clear
             
            chars.line "say option 3" ## tag: option 3
            $ story.relationship_circle.avoid.negative.trivial() ## tag: option 3 fcall
            
            call line_introduction_for_better_or_worse(positivereaction=True)
          
    return

label line_introduction_for_better_or_worse(positivereaction=True):
    if positivereaction:
        chars.circle "some positive reaction" ## tag: reaction positive
    else:
        chars.circle "some negative reaction" ## tag: reaction negative
    
    return
The behaviour is as follows:
  • If I rollback from result to reaction positive, then rollforward to result again, story.relationship_circle.avoid.negative._trivial is incremented once, likely by option 3 fcall
  • If I rollback from result to reaction positive, then rollback to option3 result and then rollforward 2 times to result again, story.relationship_circle.avoid.negative._trivial is incremented twice, again by 2 option 3 fcalls
  • The other paths show the same behaviour, as expected
For completeness, here are the full xMeter classes:

Code: Select all

init -100 python:
    
    class Meter(object):
        def __init__(self):
            self._trivial = 0
            self._minor = 0
            self._major = 0
            self._key = 0
            self._critical = 0
            self._keyevents = []
            self._criticalevents = []
            
        def __str__(self):
            return "Meter{{trv="+str(self._trivial)+", mnr="+str(self._minor)+", mjr="+str(self._major)+", key="+str(self._key)+", crt="+str(self._critical)+"}}"
        
        def trivial(self):
            self._trivial += 1
            
        def minor(self):
            self._minor += 1
            
        def major(self):
            self._major += 1
            
        def key(self, eventname):
            self._key += 1
            self._keyevents.append(eventname)
            
        def critical(self, eventname):
            self._critical += 1
            self._criticalevents.append(eventname)
        
        def result(self):
            return self._trivial * 1 + self._minor * 3 + self._major * 7 + self._key * 15 + self._critical * 31
    
    class FacettableMeter(object):
        def __init__(self):
            self.positive = Meter()
            self.negative = Meter()
            
        def __str__(self):
            return "FacettableMeter{{pos="+str(self.positive)+", neg="+str(self.negative)+"}}"
        
        def result(self):
            return self.positive.result()-self.negative.result()
            
    class AvoidableMeter(object):
        def __init__(self):
            self.avoid = FacettableMeter()
            self.face = FacettableMeter()
            
        def __str__(self):
            return "AvoidableMeter{{fce="+str(self.face)+", avd="+str(self.avoid)+"}}"
            
        def result(self):
            return self.face.result()-self.avoid.result()

Re: Rollback/Custom Class/Function call - undiserable outcom

Posted: Sun Aug 07, 2016 10:27 am
by PyTom
If you want an object to be rolled-back, you have to created it using a default statement, not a define statement.

Define runs at init time, and objects reachable from variables that have been created at init time are not rolled back. Default runs at game-start time, and hence those objects participate in rollback.

Re: Rollback/Custom Class/Function call - undiserable outcom

Posted: Sun Aug 07, 2016 11:13 am
by Ragnarok does VN
Thanks a lot! It runs perfectly now.