YesNoOk
avatar

Organizing for efficiency (CNS) (Read 994 times)

Started by Just No Point, October 07, 2015, 02:31:32 pm
Share this topic:
Organizing for efficiency (CNS)
#1  October 07, 2015, 02:31:32 pm
  • ******
    • www.justnopoint.com/

  • Online
Expression handling does not tax modern computers, so readability of your code is more important than micro-optimization of expressions. However, following certain good practices will increase efficiency without harming clarity.

MUGEN evaluates condition-type triggers for a state controller in the following order: First it evaluates triggeralls, from top to bottom. If any of the triggeralls evaluates to 0, the rest of the triggers are skipped and evaluation proceeds to the next controller. If all the triggeralls evaluate to nonzero, then the engine starts to evaluate trigger1's, from top to bottom. If any of these evaluate to 0, then evaluation skips to the first trigger2, and so on. If all the triggers in a block (besides triggerall) evaluate to nonzero, then the state controller parameters are evaluated and the controller is triggered.

In other words, the logical evaluation of triggers is short-circuited. In C-like notation, this setup might be denoted

Code:
triggerall,1 && triggerall,2 && ... && ((trigger1,1 && trigger1,2
  && ...) || (trigger2,1 && trigger2,2 && ...) || ... )
where (e.g.) trigger1,2 denotes the second trigger1 line; trigger2,1 denotes the first trigger2 line; etc. The logical evaluation of this trigger group would then be short-circuited as in C.

Because of this system, considerable efficiency gains can be attained by organizing expressions so that the condition-type triggers are as simple, and as few in number, as possible. The bulk of the "work" can be offloaded to the state controller parameters, which are only evaluated once at trigger time, instead of every frame that the player is in the state. For instance,

Code:
[State -1]
type = ChangeState
trigger1 = command = "a"
trigger1 = power < 1000
value = 3000

[State -1]
type = ChangeState
trigger1 = command = "a"
trigger1 = power >= 1000
value = 3001

[State -1]
type = ChangeState
trigger1 = command = "a"
trigger1 = power >= 2000
value = 3002
could be more compactly expressed as

Code:
[State -1]
type = ChangeState
trigger1 = command = "a"
value = 3000 + (power >= 1000) + (power >= 2000)
You can also help the engine out by placing triggeralls that are most likely to be false at the top of the triggerall block. Similarly, the trigger1 block should be the most likely block to trigger, but within the trigger1 block itself, the triggers that are most likely to evaluate to 0 should be placed highest. For state controllers with many triggers containing duplicated conditions, it may be better to break the controllers up into two separate blocks, each with its own set of triggeralls.

If you have a complex condition which is being used as the trigger condition for many consecutive state controllers, you may choose to save the value of the condition in a variable, then use that variable as a trigger to the subsequent controllers. For instance,

Code:
trigger1 = (command="abc" && command!="holddown" && power>=1000) ||
           (command="abc" && command!="holddown" && var(5)) ||
           ((command != "abc" || command = "holddown") && power>=2000)
could be written as (assuming var(0) is available):

Code:
trigger1 = (var(0):=(command="abc" && command !="holddown") && power>=
         1000) || (var(0) && var(5)) || (!var(0) && power>=2000)
Here, you must balance the readability gain of factoring out common subexpressions against the readability loss of using the := operator.