- Joined
- Jul 10, 2007
- Messages
- 6,306
I discovered something rather interesting about triggers.
Given a set of boolean expressions, that set of boolean expressions is a list and each boolean expression is a tree. Therefore, TriggerAddCondition will add to the trigger's list one tree.
When the trigger is evaluated, every boolean expression in the trigger's list will be evaluated, and by being evaluated, the trees will be walked.
The more balanced the trees are, the less overhead there is.
I performed 3 tests
Test 1: many single boolean expression trigger conditions
Result: 10 fps
Test 2: 1 trigger condition with a very unbalanced boolean expression tree built via
Or(expr, func)
Or(expr, func) //etc
Result: 5 fps (similar to trigger actions)
Test 3: 1 trigger condition with a perfectly balanced boolean expression tree built similarly to a heap
Result: 15 fps
This allows for a few things. Firstly, because there is only 1 trigger condition in the trigger's list, that trigger condition may be removed at any point without breaking the trigger's evaluation. TriggerRemoveCondition was unsafe as if the trigger condition being removed was the one being currently evaluated, the trigger wouldn't evaluate next trigger condition, it would simply terminate. Timers and so forth had to be used, which gave bad behavior. Allowing the use of TriggerRemoveCondition gives correct behavior and reduces overhead.
Next, a single boolean expression allows triggers to be mixed. Consider priorities, like in PriorityEvent. Suppose that you have one PriorityEvent that is global for everything in the map and another PriorityEvent that is specific to 1 unit in the map. If you can't mix them and they have this format:
Global Priorities: 0, 5, 10
Local Priorities: 0, 5, 10
Then it would run in this order
G0, G5, G10, L0, L5, L10
Where G is Global and L is Local
By being able to mix triggers, it will correctly run in this order
L0, G0, L5, G5, L10, G10
But wait? If the entire trigger is just 1 boolean expression, won't the same problem remain? If they are put together via Condition(localExpression, globalExpression), the result is just
L0, L5, L10, G0, G5, G10
By storing a list of trees, each list being a priority, and each tree being a boolean expression, then iterating over the local list and the global list will correctly merge the trees.
Local List:
Priority 0: Boolean Expression Tree
Priority 5: Boolean Expression Tree
Priority 10: Boolean Expression Tree
Global List:
Priority 0: Boolean Expression Tree
Priority 5: Boolean Expression Tree
Priority 10: Boolean Expression Tree
From here, iterate over the local tree first
The final result, after applying the above, is
L0, G0, L5, G5, L10, G10
You may notice that the tree will probably not be perfectly balanced, but each subtree that makes up the larger tree will be perfectly balanced, so there is a significant performance gain.
Given a set of boolean expressions, that set of boolean expressions is a list and each boolean expression is a tree. Therefore, TriggerAddCondition will add to the trigger's list one tree.
When the trigger is evaluated, every boolean expression in the trigger's list will be evaluated, and by being evaluated, the trees will be walked.
The more balanced the trees are, the less overhead there is.
I performed 3 tests
Test 1: many single boolean expression trigger conditions
Result: 10 fps
Test 2: 1 trigger condition with a very unbalanced boolean expression tree built via
Or(expr, func)
Or(expr, func) //etc
Result: 5 fps (similar to trigger actions)
Test 3: 1 trigger condition with a perfectly balanced boolean expression tree built similarly to a heap
Result: 15 fps
This allows for a few things. Firstly, because there is only 1 trigger condition in the trigger's list, that trigger condition may be removed at any point without breaking the trigger's evaluation. TriggerRemoveCondition was unsafe as if the trigger condition being removed was the one being currently evaluated, the trigger wouldn't evaluate next trigger condition, it would simply terminate. Timers and so forth had to be used, which gave bad behavior. Allowing the use of TriggerRemoveCondition gives correct behavior and reduces overhead.
Next, a single boolean expression allows triggers to be mixed. Consider priorities, like in PriorityEvent. Suppose that you have one PriorityEvent that is global for everything in the map and another PriorityEvent that is specific to 1 unit in the map. If you can't mix them and they have this format:
Global Priorities: 0, 5, 10
Local Priorities: 0, 5, 10
Then it would run in this order
G0, G5, G10, L0, L5, L10
Where G is Global and L is Local
By being able to mix triggers, it will correctly run in this order
L0, G0, L5, G5, L10, G10
But wait? If the entire trigger is just 1 boolean expression, won't the same problem remain? If they are put together via Condition(localExpression, globalExpression), the result is just
L0, L5, L10, G0, G5, G10
By storing a list of trees, each list being a priority, and each tree being a boolean expression, then iterating over the local list and the global list will correctly merge the trees.
Local List:
Priority 0: Boolean Expression Tree
Priority 5: Boolean Expression Tree
Priority 10: Boolean Expression Tree
Global List:
Priority 0: Boolean Expression Tree
Priority 5: Boolean Expression Tree
Priority 10: Boolean Expression Tree
From here, iterate over the local tree first
JASS:
loop
exitwhen globalNode == 0
loop
exitwhen localNode.priority > globalNode.priority
set expr = Or(expr, localNode.expr)
endloop
set expr = Or(expr, globalNode.expr)
set globalNode = globalNode .next
endloop
The final result, after applying the above, is
L0, G0, L5, G5, L10, G10
You may notice that the tree will probably not be perfectly balanced, but each subtree that makes up the larger tree will be perfectly balanced, so there is a significant performance gain.