Questions about Calculated Properties efficiency and "overriding"

1) Property “overriding”

I have a prototype called BasicUnit which is the “foundation” of any piece which needs to be placed on the board.
It handles some actions triggered by the map “end of the movement” key command, and it has a Calculated Property called FinalDestination made like this:

(LocationName.contains(“Battlefield”)) ?             “Play”                                            :
(LocationName==“DiscardArea”        )  ? (Owner!=0 ? “Discard”    :             “DoNothing”  )         :
(LocationName==“P1 Dashboard”       )  ? (Owner==0 ? “AssignToP1” : (Owner==1 ? “DoNothing”:“GoBack”) ):
(LocationName==“P2 Dashboard”       )  ? (Owner==0 ? “AssignToP2” : (Owner==2 ? “DoNothing”:“GoBack”) ):
“GoBack”

Finally, there’s some Trigger Action traits where FinalDestination is the condition to trigger when the map KeyCommand is fired.

Now, some piece (or some other prototype which includes BasicUnit) may want to add more tests to FinalDestination, and my approach has been to “override” the property, writing a new one with more different results.

So far it works, but i’m worried if it is a bad pratice, if this pratice results in FinalDestination being calculated more times than what’s needed, and wondering how the traits (and prototype) stacking works on properties with the same name.

Could someone teach me and answer to my concerns?

2) Calculated Property “series”

I want to show some little icons when a unit gains some temporary trait from a series of condition; as example: if the unit is active (a couple of Dynamic Properties must have the right value), is an Infantry (a Marker) and isn’t engaged (a Dynamic Property) with an enemy, it can CounterAttack (this is just an example, a lot of what i need requires a longer “serie” of conditions).

My first istinct is to create “basic” Calculated Properties like IsActive, IsEngaged, IsInfantry, and then use these in all the other “HighLevel” Calculated Properties that needs to use them (so, the CanCounterAttack property would be (IsActive && IsInfantry && !IsEngaged)?1:0

What i’m asking for is how much “inefficient” this method could be in comparison to avoid the “basic” properties and just use the same expressions over and over in every “HighLevel” property i need.

From the maineinance point of view i would like so much to use the “basic” ones: when i change something i just have to alter one property instead of modify all the prototypes/pieces affected by the change, but i’m unable to evaluate if, and how much, this will impact performances.

Thanks

To answer this point only: Every time Vassal needs to evaluate a Beanshell expression, it has to spawn a Beanshell shell (or interpreter). That requires a fair bit of setting up global variables (a.k.a. Properties), the context in which the interpreter is run (e.g., Piece, Zone, Board, Map, or Module), so there’s some definite overhead in that.

Suppose you want to evaluate expression A, B, and C, with execution times tA, tB, and tC, respectively. Suppose the overhead of setting up the interpreter is tO.

  • If the expressions are evaluated in separate shells, then the time to execute the whole thing would be

    Tseparate = (tA+tO)+(tA+tO)+ (tA+tO)=3 tO+ tA+ tB+tC

  • If the expressions are evaluated in a single shell, then the time would be

    Tsingle = tO+ tA+ tB+tC

Thus, the difference is 2 tO. If tOtA, then penalty for executing in separate shells is vanishing. if tOtA then penalty will be high.

Of course, if all of the times involved are small, or those expressions are evaluated a few times, then the overall time to execute perhaps matters less.

In short, yes there’s a penalty to break up Beanshell expressions in separate expressions. Whether the penalty is high depends on

  • how long it takes to execute the individual expressions to relative how long it takes to execute he overhead.
  • whether the expressions are evaluated often or not.

It is hard to give any hard’n’fast rules or even estimates of this. The best you can do is try to implement both ways and then measure the difference in execution time.

You may find that the penalty is non-vanishing, in which case you would have to weight the cost against the easy of maintenance.

If I had a chain with Tseparate=3s and Tsingle=2s, then I would seriously consider to use the single shell implementation. On the other hand, if I had Tseparate=1s and Tsingle=0.7s, I would let the ease of maintenance weigh a lot higher when choosing the implementation.

My 2¢

Yours,
Christian

2 Likes

My understanding, which isn’t based on a code review but practise, is based on Trait Ordering and YOU.

First off, it may be worth mentioning how prototype definition are used in a piece. Suppose a piece p has the traits

  1. BasicTrait p
  2. CalculatedTrait B
  3. PrototypeTrait A

and A prototype as traits

  1. BasicTrait ø (yes, prototypes do have a - empty - BasicTrait)
  2. MarkTrait C
  3. CalculatedTrait B

When the piece p is read into Vassal, the prototype traits are unrolled into the pieces’ trait stack, so when Vassal has the GamePiece

  1. BasicTrait p
  2. CalculatedTrait B (from p)
  3. MarkTrait C
  4. CalculatedTrait B (from A)

Next, remember that traits are evaluated bottom up - except TriggerTrait and ReportTrait which are only evaluated after the first pass, and in reverse order - from the top to the bottom.

Side-bar The way that traits are implemented in the code is as a Decorator pattern, with the inner-most decorator being the BasicTrait. In the above unrolled p game piece, trait 4 decorates trait 3, which decorates trait 2, which then finally decorates trait 1. In that sense, normal trait evaluation goes from the outer-most decorator trait towards the inner most trait, while TriggerTrait and ReportTrait are evaluated from the inner-most trait to the outer-most decorator.

Now coming back to the question: calculated more times than what’s needed? In the above example, the 2nd and 4th trait (CalculatedTrait B) will likely both be evaluated, but the one from the p piece will override the Property B set by the one from prototype A.

It could be, and I haven’t looked at the code so someone may correct me, that what happens during construction of the piece, Vassal registers a call-back on property B so that when that property is referenced, that call-back is evaluated. In that case, the call-back set by piece p will simply override the one from prototype A, and thus only the p CalculatedTrait will ever be evaluated. And it could be similar for MarkTrait or traits with a Key command interface.

If the latter is the case, there should be no performance penalty incurred by the practise of overridding traits from prototypes.

My gut feeling is that what Vassal does is something like the former option, but I haven’t checked. One should really look at the source code to figure it out.

Yours,
Christian

Wow, thank you for this detailed answer!

What if one put a sleep(ms) call in each Calculated trait? Say 2 seconds in the prototype and 1 second in the piece using it? Should we see how much sleep time is happening …. unless the sleep call get “threaded” or manipulated or mixed up by the Vassal process.

What do you think?

I’m not sure that will work because Vassal expects a single expression that evaluates to a value, and I’m not sure if the embedded interpreter chokes on a ; to separate expressions.

Another way could be to make a common TriggerTrait that trigger unique TriggerTraits which in turn does nothing, but to which you have attached ReportTraits, e.g.,

  • Prototype A
    • TriggerTrait
      • key: Ctrl-A
      • actionkeys: actionA
    • TriggerTrait:
      • key: actionA
    • ReportTrait
      • keys: actionA
      • report: Action in A
  • Piece p
    • TriggerTrait
      • key: Ctrl-A
      • actionkeys: actionp
    • TriggerTrait:
      • key: actionp
    • ReportTrait
      • keys: actionp
      • report: Action in p
    • Prototype A

The question is then if you see one or both of

Action in A
Action in p

BTW, please let us know the outcome of your investigations - i think it will be of general interest.

Yours,
Christian

Ok, i’ve built a tiny module to test this all (here’s a dropbox link if you want to try it).

This is the result:

Ignore the various “Top” and “Bottom” reports which are there just to help orienting myself in the whole topic; the relevant parts are the “I’m …” report, which shows the “overriding” calculated property.

Here’s the Prototype:

CalculatedTraitA: WhoAmI + ": " + MyNumber + " is " + ((MyNumber%2)==1 ?“odd”:“even”)

Here’s how the 4 piece are built:

CalculatedTraitA: "I’m " + BasicName + ": " + MyNumber + “=1”; Sleep(1000)

CalculatedTraitA: "I’m " + BasicName + ": " + MyNumber + “=2”; Sleep(2000)

CalculatedTraitA: "I’m " + BasicName + ": " + MyNumber + “=3”; Sleep(3000)

CalculatedTraitA: "I’m " + BasicName + ": " + MyNumber + “=4”; Sleep(4000)

So:

Seems to me that Calculated traits instead are working from top to bottom: the only pieces showing the “overrided value” are P2 and P4, where the Prototype is on top of the Calculated trait.

This SEEMS confirmed by the Sleep() instruction (which works) that I see happening only on those two pieces.

From the Sleep() instruction, seems to me that the Calculated traits are being evaluated three times: 2 times “inside” the pieces, and 1 for the Prototype (so, for P2 and P4 you see three “pauses”, for P1 and P3 none). I’ve tried adding a Sleep()in the prototype but the result were a bit funky and I haven’t understood what was happening.

What do you think?

Thank you for the analysis. I haven’t looked at your module yet, so here’s some initial comments only.

CalculatedTrait should only be evaluated when actually referenced. That is, in your ReportTrait you reference the piece property CalculatedTraitA and the expressions are not evaluated until the ReportTrait executes.

When the pieces see the GKC_ReportAll command

  • P1:
    • Piece Report - ActionPiece executed - evaluate prototype CalculatedTraitA
    • Prototype Report - Action A executed - evaluate prototype CalculatedTraitA
  • P2:
    • Prototype Report - Action A executed - evaluate piece CalculatedTraitA
    • Piece Report - ActionPiece executed - evaluate piece CalculatedTraitA
  • Similar for P3 and P4

In case of P2 and P4, the piece definition of CalculatedTraitA overrides the same from the prototype because the prototype is more inner than the CalculatedTrait, and thus that property definition is seen before the prototype property definition (and searching for properties ends on first match).

Also note the difference in when the ReportTraits are executed in P2 and P4. In P2, the prototype TriggerTrait is more inner than in P4, so the corresponding ReportTraits are executed earlier (ordering is reversed).

That is, the ordering of traits mentioned in the documentation still holds, with the caveat that more outer property definitions trumph more inner definitions.

Yours,
Christian