- Joined
- Jul 10, 2007
- Messages
- 6,306
The big thing many of us were concerned about back in the day was correctly support recursive events. Two correct approaches were devised for this.
Events have always been global and they have always had one stage. For a DDS, a damage event would run all code registered to it whenever any unit was damaged. For a unit indexer, a deindex event would run for any indexed unit that was deindexed. It was up to the user to determine whether or not to run their own code using if-statements.
This would sort of simulate local events (the deindex event is tied only to units that pass the if-statement in the index event).
The other thing was handling data that may be destroyed during the course of an event. For example, consider a unit that is removed in the middle of a damage event. If another unit is created, then the data is overwritten and the damage event becomes corrupt. AIDS devised locks. When an index was locked, it would not be recycled until it was unlocked. Later, someone decided to use a queue for recycling instead of a stack. This meant that locks were no longer needed as the recycled index wouldn't be reused for a while (in almost all cases).
The only current weakness seems to be handling local events. Furthermore, consider events that have multiple stages.
Let's consider a unit index/deindex event. Stage 1 would be index. Stage 2 would be deindex. We can consider these to be Start and Stop.
Suppose a resource B requires data from a resource A. If registering normally, the events would look like this.
Index A
Index B
Deindex A
Deindex B
The problem with the above is that the data B relies on is destroyed before B ever gets run.
What happens when we have local events, using if-statements? Well, everything is global, so they just go in with the global events and will go after whatever data the require.
Index Simulated Local C
Index A
Index B
Deindex A
Deindex B
Deindex Simulated Local C (will run whether indexed or not)
So there are two challenges. The first challenge is to make it so that events run in the correct order.
Index A
Index B
Deindex B
Deindex A
The second challenge is to get local events going (meaning that deindex events are unit-specific).
The final result would be this
Global Index A
Global Index B
Global Index C
Local Deindex C
Global Deindex B
Global Deindex A
And would go in 3 stages
Global Index
Local Deindex
Global Deindex
Why does local go before global? A local event is, for all intents and purposes, registered *after* a global event. Everything is in reverse order, so the local event ends up at the top. Global events also can't rely on local event data because that data may or may not exist, meaning that local events should always be registered after global events.
Global Start
Local Start
Local End (registration is in reverse order here)
Global End
The only way to achieve local events is to create 1 trigger for every single unit for every single event.
Each unit would then have 1 trigger on it for a unit indexer
Local Trigger End
The system itself would have 2 global triggers
Global Trigger Start
Global Trigger End
The events would fire as follows
On Index:
GlobalTrigger Start
On Deindex:
Local Trigger End
Global Trigger End
For a DDS, 4 evaluations per event
GlobalTriggStart
LocalTriggerStart
LocalTriggerEnd
GlobalTriggerEnd
However, the end triggers will still run code on them in the incorrect order! A trigger does not run in reverse! This means that the end events would have to be lists of code that would each have to be evaluated one by one...
GlobalTriggStart
LocalTriggerStart
LocalListEnd (loop through this in reverse)
GlobalListEnd (loop through this in reverse)
Now we're talking a ton of overhead.
So what if instead of using trigger conditions (TriggerAddCondition) or managing a list ourselves, we used a boolean expression to store the data? We can store it in reverse.
expression = Or(last, first)
The problem with this is that a boolean expression is rather slow when chained together
expression = Or(7, Or(6, Or(5, Or(4, Or(3, Or(2, 1))))))
However, when balanced out, it's actually faster than evaluating a trigger that relied on TriggerAddCondition
expression = Or(Or(Or(7, 6), Or(5, 4)), Or(Or(3, 2), 1))
In conclusion, there are several things to worry about when writing events
1. Data
2. Recursion Support
3. Data Destruction (locks from AIDS, consider a queue recycler)
4. Global vs Local Events (local shouldn't run for everything!)
5. Start vs End Events (Start runs forward, End runs backwards)
6. Multi-Stage Events (Start/End would be 2 stages) (how many stages might a combat system have in it?)
An example of correct order (but not local events) using Trigger
JASS:
/*
* stack approach
*/
globals
integer array stack
integer index = 0
endglobals
function FireEvent takes integer dat returns nothing
set index = index + 1
set stack[index] = dat
//Fire Event
set index = index - 1
endfunction
/*
* locals approach
*/
globals
integer eventDat = 0
endglobals
function FireEvent2 takes integer dat returns nothing
local integer prev = eventDat
set eventDat = dat
//Fire Event
set eventDat = prev
endfunction
Events have always been global and they have always had one stage. For a DDS, a damage event would run all code registered to it whenever any unit was damaged. For a unit indexer, a deindex event would run for any indexed unit that was deindexed. It was up to the user to determine whether or not to run their own code using if-statements.
JASS:
//when a unit is indexed
globals
boolean array flag
endglobals
function Index takes nothing returns nothing
if (condition) then
set flag[GetUnitUserData(indexedUnit)] = true
// code
endif
endfunction
function Deindex takes nothing returns nothing
if (flag[GetUnitUserData(deindexedUnit)]) then
set flag[GetUnitUserData(deindexedUnit)] = false
// code
endif
endfunction
This would sort of simulate local events (the deindex event is tied only to units that pass the if-statement in the index event).
The other thing was handling data that may be destroyed during the course of an event. For example, consider a unit that is removed in the middle of a damage event. If another unit is created, then the data is overwritten and the damage event becomes corrupt. AIDS devised locks. When an index was locked, it would not be recycled until it was unlocked. Later, someone decided to use a queue for recycling instead of a stack. This meant that locks were no longer needed as the recycled index wouldn't be reused for a while (in almost all cases).
The only current weakness seems to be handling local events. Furthermore, consider events that have multiple stages.
Let's consider a unit index/deindex event. Stage 1 would be index. Stage 2 would be deindex. We can consider these to be Start and Stop.
Suppose a resource B requires data from a resource A. If registering normally, the events would look like this.
Index A
Index B
Deindex A
Deindex B
The problem with the above is that the data B relies on is destroyed before B ever gets run.
What happens when we have local events, using if-statements? Well, everything is global, so they just go in with the global events and will go after whatever data the require.
Index Simulated Local C
Index A
Index B
Deindex A
Deindex B
Deindex Simulated Local C (will run whether indexed or not)
So there are two challenges. The first challenge is to make it so that events run in the correct order.
Index A
Index B
Deindex B
Deindex A
The second challenge is to get local events going (meaning that deindex events are unit-specific).
The final result would be this
Global Index A
Global Index B
Global Index C
Local Deindex C
Global Deindex B
Global Deindex A
And would go in 3 stages
Global Index
Local Deindex
Global Deindex
Why does local go before global? A local event is, for all intents and purposes, registered *after* a global event. Everything is in reverse order, so the local event ends up at the top. Global events also can't rely on local event data because that data may or may not exist, meaning that local events should always be registered after global events.
Global Start
Local Start
Local End (registration is in reverse order here)
Global End
The only way to achieve local events is to create 1 trigger for every single unit for every single event.
Each unit would then have 1 trigger on it for a unit indexer
Local Trigger End
The system itself would have 2 global triggers
Global Trigger Start
Global Trigger End
The events would fire as follows
On Index:
GlobalTrigger Start
On Deindex:
Local Trigger End
Global Trigger End
For a DDS, 4 evaluations per event
GlobalTriggStart
LocalTriggerStart
LocalTriggerEnd
GlobalTriggerEnd
However, the end triggers will still run code on them in the incorrect order! A trigger does not run in reverse! This means that the end events would have to be lists of code that would each have to be evaluated one by one...
GlobalTriggStart
LocalTriggerStart
LocalListEnd (loop through this in reverse)
GlobalListEnd (loop through this in reverse)
Now we're talking a ton of overhead.
So what if instead of using trigger conditions (TriggerAddCondition) or managing a list ourselves, we used a boolean expression to store the data? We can store it in reverse.
expression = Or(last, first)
The problem with this is that a boolean expression is rather slow when chained together
expression = Or(7, Or(6, Or(5, Or(4, Or(3, Or(2, 1))))))
However, when balanced out, it's actually faster than evaluating a trigger that relied on TriggerAddCondition
expression = Or(Or(Or(7, 6), Or(5, 4)), Or(Or(3, 2), 1))
In conclusion, there are several things to worry about when writing events
1. Data
2. Recursion Support
3. Data Destruction (locks from AIDS, consider a queue recycler)
4. Global vs Local Events (local shouldn't run for everything!)
5. Start vs End Events (Start runs forward, End runs backwards)
6. Multi-Stage Events (Start/End would be 2 stages) (how many stages might a combat system have in it?)
An example of correct order (but not local events) using Trigger
JASS:
/*
* Here, the entry point of a system for some event is A
*
* B depends on the data of A
*/
library A uses Trigger
struct A extends array
static Trigger event
static Trigger start
static Trigger end
static integer data = 0
private static method s takes nothing returns boolean
set data = 100
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "A Start -> " + I2S(data))
return false
endmethod
private static method e takes nothing returns boolean
set data = 0
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "A End -> " + I2S(data))
return false
endmethod
private static method onInit takes nothing returns nothing
set event = Trigger.create(false)
set start = Trigger.create(false)
set end = Trigger.create(true)
call start.register(Condition(function thistype.s))
call end.register(Condition(function thistype.e))
call event.reference(start)
call event.reference(end)
endmethod
endstruct
endlibrary
library B uses A
/*
* can do sub events if desired with triggers in B
*/
struct B extends array
static integer data = 0
private static method s takes nothing returns boolean
set data = A.data*2
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "B Start -> " + I2S(A.data) + ", " + I2S(data))
return false
endmethod
private static method e takes nothing returns boolean
set data = 0
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "B End -> " + I2S(A.data) + ", " + I2S(data))
return false
endmethod
private static method onInit takes nothing returns nothing
call A.start.register(Condition(function thistype.s))
call A.end.register(Condition(function thistype.e))
endmethod
endstruct
endlibrary
library User uses A, B
struct User extends array
static integer data = 0
private static method s takes nothing returns boolean
set data = B.data + A.data
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "User Start -> " + I2S(A.data) + ", " + I2S(B.data) + ", " + I2S(data))
return false
endmethod
private static method e takes nothing returns boolean
set data = 0
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60000, "User End -> " + I2S(A.data) + ", " + I2S(B.data) + ", " + I2S(data))
return false
endmethod
private static method onInit takes nothing returns nothing
call A.start.register(Condition(function thistype.s))
call A.end.register(Condition(function thistype.e))
endmethod
endstruct
endlibrary
struct Run extends array
private static method onInit takes nothing returns nothing
call A.event.fire()
endmethod
endstruct
Attachments
Last edited: