MUI
From WarCraft3
What is MUI?
MUI stands for Multi-Unit Instance-ability, that is, whether or not code can be run with multiple units at the same time without bugging. Contrary to popular belief, there isn't really any specific way of defining a code as 'MUI'. Instead, code is deemed not-MUI when it bugs with multiple instances of the code running, usually caused from variables being overwritten. When it is said that a script or trigger is MUI, it means that multiple instances can be running at a time with no bugs. Any code that is executed instantly is automatically MUI.
Detailed Examples
When trigger fires it is called a trigger instance.
This is a simple trigger which is not MUI (written in GUI):
Timed Special Effect
Events
Player - Player 1 (Red) skips a cinematic sequence
Conditions
Actions
Special Effect - Create a special effect at (Center of (Playable map area)) using units\human\Footman\Footman.mdl
Wait 10.00 game-time seconds
Special Effect - Destroy (Last created special effect)
'Last created special effect' is a Blizzard made variable which can store only one value at any given time. Each instance is forced to use the same variable. Conflicts here usually occur because variable resources are limited while there is no fire limit for instances.
A solution is to use a timer in conjunction with a group of variable arrays. It gives a total of 8191 (2^13 - 1, the max size of an array) properly working instances.
Here is an example of a trigger using a timer with a group of variable arrays to achieve MUI:
Actions
Set instanceIndex = (instanceIndex + 1)
Special Effect - Create a special effect at (Center of (Playable map area)) using units\human\Footman\Footman.mdl
Set instanceSpecialEffect[instanceIndex] = (Last created special effect)
First things first, an instance assigned a unique integer. The effect will be stored within an effect array using that unique integer. The duration the effect will last requires storage as well.
Actions
Set instanceIndex = (instanceIndex + 1)
Special Effect - Create a special effect at (Center of (Playable map area)) using units\human\Footman\Footman.mdl
Set instanceSpecialEffect[instanceIndex] = (Last created special effect)
Set instanceWait[instanceIndex] = 10.00
Using such indexing methods are called parallel arrays (global arrays). Lastly, timer expiration needs to be set. One timer can handle all instances, but it is important that you know the exact waiting time.
Actions
Set instanceIndex = (instanceIndex + 1)
Special Effect - Create a special effect at (Center of (Playable map area)) using units\human\Footman\Footman.mdl
Set instanceSpecialEffect[instanceIndex] = (Last created special effect)
Set instanceWait[instanceIndex] = 10.00
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
instanceIndex Equal to 1
Then - Actions
Countdown Timer - Start userCreatedTimer as a One-shot timer that will expire in 10.00 seconds
Skip remaining actions
Else - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
(Remaining time for userCreatedTimer) Greater than instanceWait[instanceIndex]
Then - Actions
Countdown Timer - Pause userCreatedTimer
Countdown Timer - Start userCreatedTimer as a One-shot timer that will expire in instanceWait[instanceIndex] seconds
Else – Actions
When the timer expires, we loop through all instances. If an instance has reached its lifespan, we finish the spell. If there are still instances left, we restart the timer.
Destroy Timer Special Effect
Events
Time - userCreatedTimer expires
Conditions
Actions
For each (Integer instanceLoopIndex) from 1 to instanceIndex, do (Actions)
Loop - Actions
Set instanceWait[instanceLoopIndex] = (instanceWait[instanceLoopIndex] - (Elapsed time for userCreatedTimer))
This is variable loop with subtracting from currently handled instance duration elapsed time (instanceLoopIndex variable). If wait variable becomes zero or less its time to end instance. This means destroying special effect and doing variable recycling. By variable recycling last instance data (instanceIndex) is moved to current instance (instanceLoopIndex) and last index freed (by lowering instance max count by one):
Actions
For each (Integer instanceLoopIndex) from 1 to instanceIndex, do (Actions)
Loop - Actions
Set instanceWait[instanceLoopIndex] = (instanceWait[instanceLoopIndex] - (Elapsed time for userCreatedTimer))
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
instanceWait[instanceLoopIndex] Greater than 0.00
instanceWait[instanceLoopIndex] Less than instanceNewWait
Then - Actions
Set instanceNewWait = instanceWait[instanceLoopIndex]
Else – Actions
This If/Then/Else action picks out shortest wait time which is used later for new expiration time. Another If/Then/Else action checks does instance needs destroying and when it is true last instance is replaced with it and max instances decreased by one. Loop needs to check once more same index because now it holds new instance:
Actions
For each (Integer instanceLoopIndex) from 1 to instanceIndex, do (Actions)
Loop - Actions
Set instanceWait[instanceLoopIndex] = (instanceWait[instanceLoopIndex] - (Elapsed time for userCreatedTimer))
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
instanceWait[instanceLoopIndex] Greater than 0.00
instanceWait[instanceLoopIndex] Less than instanceNewWait
Then - Actions
Set instanceNewWait = instanceWait[instanceLoopIndex]
Else - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
instanceWait[instanceLoopIndex] Less than or equal to 0.00
Then - Actions
Special Effect - Destroy instanceSpecialEffect[instanceLoopIndex]
Set instanceSpecialEffect[instanceLoopIndex] = instanceSpecialEffect[instanceIndex]
Set instanceWait[instanceLoopIndex] = instanceWait[instanceIndex]
Set instanceLoopIndex = (instanceLoopIndex - 1)
Set instanceIndex = (instanceIndex - 1)
Else – Actions
Good practice is not to nest If/Then/Else actions. Try to avoid adding new If/Then/Else action to another If/Then/Else 'Else' actions. It makes code easier to understand.
When loop has ended expired instances and there are still more instances left which need to wait timer is started with shortest wait time. Before not done, but required is to set 'instanceNewWait' before loop start to value, that value could be default duration or bigger value. It is needed for new wait time comparison:
Destroy Timer Special Effect
Events
Time - userCreatedTimer expires
Conditions
Actions
Set instanceNewWait = 10.00
For each (Integer instanceLoopIndex) from 1 to instanceIndex, do (Actions)
Loop - Actions
Set instanceWait[instanceLoopIndex] = (instanceWait[instanceLoopIndex] - (Elapsed time for userCreatedTimer))
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
instanceWait[instanceLoopIndex] Greater than 0.00
instanceWait[instanceLoopIndex] Less than instanceNewWait
Then - Actions
Set instanceNewWait = instanceWait[instanceLoopIndex]
Else - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
instanceWait[instanceLoopIndex] Less than or equal to 0.00
Then - Actions
Special Effect - Destroy instanceSpecialEffect[instanceLoopIndex]
Set instanceSpecialEffect[instanceLoopIndex] = instanceSpecialEffect[instanceIndex]
Set instanceWait[instanceLoopIndex] = instanceWait[instanceIndex]
Set instanceLoopIndex = (instanceLoopIndex - 1)
Set instanceIndex = (instanceIndex - 1)
Else - Actions
If (All Conditions are True) then do (Then Actions) else do (Else Actions)
If - Conditions
instanceIndex Greater than 0
Then - Actions
Countdown Timer - Start userCreatedTimer as a One-shot timer that will expire in instanceNewWait seconds
Else – Actions
MUI in GUI can also be achieved by using hashtables as an attachment method.