• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[Snippet] [Needs work] Advent

It's like Event, but with the best API and functionality.

JASS:
library Advent requires LinkedListModule /*

    ==========================================================================
    Advent v3.1.0.1
    --------------------------------------------------------------------------
    Easy, light, dynamic & complete event registration

    ==========================================================================
    Credits:
    --------------------------------------------------------------------------
    Created by Bribe
    Idea by Jesus4Lyf and Nestharus
    Many API additions thanks to Dirac
    
    ==========================================================================
    Requirements:
    --------------------------------------------------------------------------
    LinkedListModule by Dirac: hiveworkshop.com/forums/showthread.php?t=206552
    
    ==========================================================================
    Intro:
    --------------------------------------------------------------------------
    Advent allows you to create an Advent, add code to its execution queue,
    remove certain code from its queue, fire all added codes, fire just one
    single code and even destroy the Advent should its task be finished.

    I created this library for some key reasons. For one, the existing Event
    libraries have quite verbose API and their cross compatibility has quite
    large problems. Nestharus wanted a fast solution and Jesus4Lyf wanted a
    fully-dynamic solution.

    Why not have both?

    Advent combines tight code with complex functionality while not adding any
    unnecessary performance overhead. If you know your code will run at any
    point in the game, you can add permanent code that runs more quickly. If
    you need dynamic code, you can take advantage of the AdventReg type which
    allows you to do so.

    You can also destroy Advents which completely destroys any & all code reg-
    istered to them. I imagine this would only be used for some really complex
    systems; it's mostly here for completeness.

    ==========================================================================
    API Guide:
    --------------------------------------------------------------------------
    function CreateAdvent
        takes nothing
            returns Advent

        Create a new Advent instance. You must create it before anything else.

        * Advent adv = Advent.create()

    function DestroyAdvent
        takes Advent adv
            returns nothing

        Perhaps this is here just for completeness but it effectively allows
        the system to be completely dynamic.

        * call adv.destroy()

    function RegisterAdvent
        takes Advent adv, code c, boolean permanent
            returns AdventReg

        Register some code to be run when the Advent is fired. If you want to
        be able to unregister the code at some point, set the permanent* flag
        to false. If false, the function returns an AdventReg that is used to
        identify it.

        * AdventReg adreg = adv.registerEx(code) //Dynamic
        * call adv.register(code)                //Permanent

    function UnregisterAdventReg
        takes AdventReg node
            returns nothing

        Unregister an AdventReg from an Advent.

        * call adreg.unregister()

    function FireAdvent
        takes Advent adv
            returns nothing

        Run all code that was registered to the Advent.

        * call adv.fire()

    function FireAdventData
        takes Advent adv, integer data
            returns nothing

        Run all code that was registered to the Advent with an extra parameter
        to pass a data integer. This is useful for certain systems to support
        a single integer as a piece of data.

        * call adv.fireData(data)

    function GetAdventData
        takes nothing
            returns integer

        Returns the data specified by FireAdventData.

        * set data = Advent.data

    function GetAdventReg
        takes nothing
            returns AdventReg

        Returns the registry of the AdventReg that is currently firing. It is
        pointless to use this if you aren't using dynamic Advent registries.

        * set data = Advent.reg.data
            - Yeah AdventReg has a data member now, thanks to Dirac.

    function FireCode
        takes code c, integer data
            returns nothing

        Call the code directly. This is useful if you are late registering an
        Advent and you want this code to fire anyway, without firing the full
        Advent. This function also helps to keep Advent.data readonly because
        that is a very sensitive variable.

        * Advent.fireCode(code, data)
*/
    globals
        private force f
        private trigger array trig
    endglobals

    private module Init
        private static method onInit takes nothing returns nothing
            set f = bj_FORCE_PLAYER[0]
        endmethod
    endmodule
    
    private struct List extends array
        implement LinkedList
        implement Init
    endstruct
    
    struct Advent extends array
        
        //Data attached to the currently-firing Advent.
        readonly static integer data = 0

        //The AdventReg which is currently firing.
        readonly static AdventReg reg = 0
        
        static method create takes nothing returns thistype
            return List.createNode()
        endmethod
        
        method destroy takes nothing returns nothing
            local List node = List(this).next
            if not List(this).head then
                loop
                    call DestroyTrigger(trig[node])
                    set trig[node] = null
                    exitwhen node == this
                    set node = node.next
                endloop
                call List(this).flushNode()
            debug else
                debug call BJDebugMsg("[Advent] Attempt to destroy invalid instance: " + I2S(this))
            endif
        endmethod

        method register takes code c returns nothing
            if trig[this] == null then
                set trig[this] = CreateTrigger()
            endif
            call TriggerAddCondition(trig[this], Filter(c))
        endmethod

        method registerEx takes code c returns AdventReg
            local AdventReg node = List.allocate()
            call List(this).insertNode(node)
            set trig[node] = CreateTrigger()
            call TriggerAddCondition(trig[node], Filter(c))
            return node
        endmethod
        
        method fire takes nothing returns nothing
            local integer r = .reg
            set .reg = List(this).next
            loop
                call TriggerEvaluate(trig[.reg])
                exitwhen .reg == this
                set .reg = List(.reg).next
            endloop
            set .reg = r
        endmethod
        
        method fireData takes integer whichData returns nothing
            local integer pdat = .data
            set .data = whichData
            call this.fire()
            set .data = pdat
        endmethod
        
        static method fireCode takes code c, integer whichData returns nothing
            local integer pdat = .data
            set .data = whichData
            call ForForce(f, c)
            set .data = pdat
        endmethod

    endstruct

    struct AdventReg extends array

        //For attaching data to registries, for example a struct instance.
        integer data

        method unregister takes nothing returns nothing
            if trig[this] != null and not List(this).head then
                call DestroyTrigger(trig[this])
                set trig[this] = null
                call List(this).removeNode()
                call List(this).deallocate()
            debug else
                debug call BJDebugMsg("[Advent] Attempt to unregister invalid AdventReg: " + I2S(this))
            endif
        endmethod

    endstruct

    function CreateAdvent takes nothing returns Advent
        return Advent.create()
    endfunction

    function DestroyAdvent takes Advent adv returns nothing
        call adv.destroy()
    endfunction

    function FireAdvent takes Advent adv returns nothing
        call adv.fire()
    endfunction

    function FireAdventData takes Advent adv, integer data returns nothing
        call adv.fireData(data)
    endfunction

    function FireCode takes code c, integer data returns nothing
        call Advent.fireCode(c, data)
    endfunction

    function RegisterAdvent takes Advent adv, code c, boolean permanent returns AdventReg
        if permanent then
            call adv.register(c)
            return 0
        endif
        return adv.registerEx(c)
    endfunction

    function UnregisterAdvent takes AdventReg reg returns nothing
        call reg.unregister()
    endfunction

    function GetAdventData takes nothing returns integer
        return Advent.data
    endfunction

    function GetAdventReg takes nothing returns AdventReg
        return Advent.reg
    endfunction

endlibrary
 
Last edited:
"No" to getting rid of the dot. It helps distinguish globals from locals.

"No" to the "dynamic" word. It's really a pain to type "registerDynamic" and looks stupid as well, compared to "registerTemp". Although I'd be up for any other synonyms because I don't like "Temp" either.

@Dirac, what is the point of setting data when the event is created? Can you show me an example where that is useful?
 
I use "this." for non-static and ommision of the "this" is incredibly wasteful.

As you can see I always write "this." when it's dynamic.

Having "thistype." is just superfluous.

My code is meant to be intelligible not verbose just for the sake of it.

Edit:

Changed ".register" to ".registerStatic" and changed ".registerTemp" to ".register".
 
It's a 100% waste for this to incorporate triggers into its API. Why should I force the user to do all that crap? Functions passed to conditions can return nothing and not crash Macs. Blizzard did a fix.

A better way to add/remove trigger conditions? Hmm I suppose you are right that I could (should) be just using one trigger with multiple conditions even for the dynamic stuff because then I don't have to destroy triggers for every dynamic node.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
A better way to add/remove trigger conditions? Hmm I suppose you are right that I could (should) be just using one trigger with multiple conditions even for the dynamic stuff because then I don't have to destroy triggers for every dynamic node.

Don't forget that you have to remove the conditions on a timer ; )


Essentially, this will just turn into a clone of my Event Dynamic... so you can just let me put it back up if you want >.>.
 
I've decided not to do it your way because of multiple recursion things.

  • Wouldn't be possible to remove the condition from a currently-firing event.
  • It would require several additional arrays to prevent the rare recursion bug where "unregister" is called, the timer starts, then the ".destroy" method is called on the Advent before the timer expires, and some instances could have been overwritten in that time which would require Advent locks as well.
  • Overall it is not inconceivable that an ".unregister" could be called from an Advent's ".fire" method because recursion can make anything happen, however if I would do it I'd have to add some security checks to the ".fire" method which would slow it down a bit.
  • In any case, Anitarf ran some benchmarks that said evaluating a trigger with multiple conditions or evaluating multiple triggers doesn't have much difference in speed.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
So i was right about trigger conditions ? (just to know and lazy for making a test myself).
I mean they don't have to be removed when you destroy a trigger (no memory leak) ?

It's sure that the loop is in a different scale overhead, much less than a TriggerEvaluate which opens a new thread.
And since each trigger conditions opens a new thread anyway, that's why the difference speed is quite not noticeable.

I don't know how Nestharus used a better method, but the one i have in head is with an ugly API.
You must use an extra function exactly one time in each code "fired".
This function would be inlined and is just an integer incrementing, in order to know in which condition we are.

Basically it's one trigger with an extra condition (added first) which handle the conditions removing, in the next tick, with a linked list.

I say that, because i'm interested if someone has a better idea.
 
I rather like the current setup it has, you can add code while the event is firing and even remove it from that phase.

And if you look at the code it's quite fast anyway. Obviously I'd rather have a ForceEnumPlayerCounted that actually worked, because that would eliminate having to have a trigger per dynamic code, but w/e we can't have everything.

If I remember correctly trigger conditions do not have to be removed, not even with TriggerClearConditions. Simply DestroyTrigger on its own is enough for those.

TriggerClearConditions is only useful if you want to keep the trigger but not its conditions. Well I could implement that kind of trigger recycling here but I don't really believe in recycling for this cause, because the RAM overhead from many idle triggers would be more than I'd want to deal with.
 
Level 6
Joined
Oct 23, 2011
Messages
182
What was updated? =P

*
set adventReg.data = this

local thistype this = advent.reg.data

Does this work? I'm not too familiar with the new functions
Looks really useful though
 
Last edited:
Level 6
Joined
Oct 23, 2011
Messages
182
There's a spell struct involving damage-absorbing shield.

I was going to make an adventReg when the spell was cast, and attach the struct to the adventReg so when it's fired, I can do stuff with the spell instance

I'm assuming adventReg is used for stuff like this.. very easy to make dynamic stuff =)
 
Level 6
Joined
Oct 23, 2011
Messages
182
JASS:
    globals
        private constant integer KILLS = 12
        
        private Table Kills
    endglobals

    private function OnKill takes nothing returns nothing
        local Stat      s = GetAdventData()
        local integer   k
        
        if (Creep.data.unitId == 'nanw') then
            set k = Kills[s] + 1
            
            if (k >= KILLS) then                
                call DisplayQuestText(s, "\n|c00ffff80Complete!")
                call UnregisterAdvent(GetAdventReg())
            else
                set Kills[s] = k
            endif
        endif
    endfunction

    private struct Creep extends array

        readonly integer unitId
        private trigger trg

        readonly static thistype data

        private method index takes nothing returns nothing
            set .unitId=GetUnitTypeId(GetIndexedUnit())
            set .trg=CreateTrigger()

            call TriggerRegisterUnitEvent(.trg,GetIndexedUnit(),EVENT_UNIT_DEATH)
            call TriggerAddCondition(.trg,Condition(function thistype.onDeath))
        endmethod

        private static method onDeath takes nothing returns boolean
            local thistype this = thistype[GetTriggerUnit()]

            if Stat[GetKillingUnit()].allocated then
                set .data = this
                call Stat[GetKillingUnit()].onKill.fireData(GetUnitId(GetKillingUnit()))
            endif
        endmethod
        implement UnitIndexStruct
    endstruct

    private function Start takes nothing returns nothing
        call Stat[GetTriggerUnit()].onKill.registerEx(function OnKill)
    endfunction

    private struct Stat extends array

         readonly Advent onKill

         private method index takes nothing returns nothing
            set .onKill = Advent.create()
         endmethod
         implement UnitIndexStruct
    endstruct




Here it is..
After a few calls of UnregisterAdvent it seems like some other unit's onKill Advent is fired
(And strangely the adventreg is registered on the other unit's advent as well..)
Sometimes, advent fires a million times before all regs are unregistered

I dont really know what's happening.. it's buggy and i've been unable to debug it

Few questions
1. Are functions registered on advent supposed to return booleans?
2. why is the function called UnregisterAdvent when it is mentioned as UnregisterAdventReg in the API?
 
Level 16
Joined
Aug 7, 2009
Messages
1,403
It's also causing weird, heavy lags; my screen is freezing for seconds when an advent is fired. I haven't been able to figure out why, but it works perfectly with an older version.

The most strange thing in it is the fact that I'm using static advent registers, whci is actually a simple trigger. Therefore I think the problem is in the fire method.
 
Level 6
Joined
Oct 23, 2011
Messages
182
It's also causing weird, heavy lags; my screen is freezing for seconds when an advent is fired. I haven't been able to figure out why, but it works perfectly with an older version.

The most strange thing in it is the fact that I'm using static advent registers, whci is actually a simple trigger. Therefore I think the problem is in the fire method.

My map was lagging a bit with the current version as well
 
JASS:
    globals
        private constant integer KILLS = 12
        
        private Table Kills
    endglobals

    private function OnKill takes nothing returns nothing
        local Stat      s = GetAdventData()
        local integer   k
        
        if (Creep.data.unitId == 'nanw') then
            set k = Kills[s] + 1
            
            if (k >= KILLS) then                
                call DisplayQuestText(s, "\n|c00ffff80Complete!")
                call UnregisterAdvent(GetAdventReg())
            else
                set Kills[s] = k
            endif
        endif
    endfunction

    private struct Creep extends array

        readonly integer unitId
        private trigger trg

        readonly static thistype data

        private method index takes nothing returns nothing
            set .unitId=GetUnitTypeId(GetIndexedUnit())
            set .trg=CreateTrigger()

            call TriggerRegisterUnitEvent(.trg,GetIndexedUnit(),EVENT_UNIT_DEATH)
            call TriggerAddCondition(.trg,Condition(function thistype.onDeath))
        endmethod

        private static method onDeath takes nothing returns boolean
            local thistype this = thistype[GetTriggerUnit()]

            if Stat[GetKillingUnit()].allocated then
                set .data = this
                call Stat[GetKillingUnit()].onKill.fireData(GetUnitId(GetKillingUnit()))
            endif
        endmethod
        implement UnitIndexStruct
    endstruct

    private function Start takes nothing returns nothing
        call Stat[GetTriggerUnit()].onKill.registerEx(function OnKill)
    endfunction

    private struct Stat extends array

         readonly Advent onKill

         private method index takes nothing returns nothing
            set .onKill = Advent.create()
         endmethod
         implement UnitIndexStruct
    endstruct




Here it is..
After a few calls of UnregisterAdvent it seems like some other unit's onKill Advent is fired
(And strangely the adventreg is registered on the other unit's advent as well..)
Sometimes, advent fires a million times before all regs are unregistered

I dont really know what's happening.. it's buggy and i've been unable to debug it

Few questions
1. Are functions registered on advent supposed to return booleans?
2. why is the function called UnregisterAdvent when it is mentioned as UnregisterAdventReg in the API?

What is calling the "start" private function?
 
Level 6
Joined
Oct 23, 2011
Messages
182
Yes. I've tested it a bunch of times.

I put a debug message in the fire method. I'll see if multiple debug messages show up consecutively.

If my assumption above is correct then my game would probably perma-freeze due to infinite loop in the fire method if I don't unregister the advent registries (which is sad)

I'll also try saving adventReg to a Table and unregister it after loading it instead of using GetAdventReg()

*edit : I've tested it with this

JASS:
        method fire takes nothing returns nothing
            set .reg = next[this]
            debug call BJDebugMsg("Advent firing")
            loop
                call TriggerEvaluate(trigs[.reg])
                debug call BJDebugMsg("Registry firing")
                exitwhen .reg == this
                set .reg = next[.reg]
            endloop
        endmethod

I have two advents : onReceiveAttack and onKill
I have one permanent registry on onReceiveAttack advent (that deals damage to kill the attacker instantly)
I register a temporary one on onKill advent on receiving a quest.

When I receive attack and fire onReceiveAttack, onKill advent will fire as well. this is where the bug occurs (i see bazillions of registry firing messages. they show up until i unregister the temporary registry on onKill)

I tried to make it clear as I can. As far as I see, doing what I wrote above seems to always trigger the bug
 
Last edited:
Level 6
Joined
Jun 20, 2011
Messages
249
What kStiyl suggests is bad news, i'll have to check if there's an error with my linked list, unless of course he's doing something wrong somewhere else

EDIT: LinkedList works wonders, i'll have to read bribe's code to find a flaw
 
Level 6
Joined
Oct 23, 2011
Messages
182
Kstiyl, you are using an older version od advent which is why you have that problem. Copy the new version.

I think 3.1.0.0 has its own bugs separate from 3.0.0.0.

[Advent] Attempt to destroy invalid instances
^ shows

Its also feels laggy (fps unusually low)

This never happened to me while testing the map for a few good hours with 3.0.0.0.
I'm afraid to use this as I have no idea what effect would it have on gameplay

The code causing troubles is below:

JASS:
    struct Creep extends array

        readonly integer unitId
        
        private trigger trg
        private Advent  onAttackEvent
        
        private static method test takes nothing returns boolean
            return false
        endmethod
        
        private method index takes nothing returns nothing
            set .unitId         = GetUnitTypeId(GetIndexedUnit())
            set .onAttackEvent  = Advent.create()
            set .trg            = CreateTrigger()
            
            call TriggerRegisterUnitEvent(.trg, GetIndexedUnit(), EVENT_UNIT_DEATH)
            call TriggerAddCondition(.trg, Condition(function thistype.onDeath))
            
            call .onAttackEvent.register(function thistype.test)
        endmethod
        
        private method deindex takes nothing returns nothing
            call .onAttackEvent.destroy()
            call DestroyTrigger(.trg)

            set .trg = null
        endmethod

        private static method Respawn takes nothing returns nothing
            local thistype this = ReleaseTimer(GetExpiredTimer())

            call CreateUnit(Player(12), .unitId, 0, 0, 0)
            call UnitIndex(this).unlock()
        endmethod
        
        private static method onDeath takes nothing returns boolean
            local thistype this = thistype(GetUnitId(GetTriggerUnit()))
            
            call UnitIndex(this).lock()
            call TimerStart(NewTimerEx(this), 60, false, function thistype.Respawn)
            
            return false
        endmethod

        implement UnitIndexStruct
    endstruct

Probably something wrong with my code?
Maybe destroying an advent right after I create one causes problem?
Maybe UnitIndexer bugs with Advent? lol
 
Last edited:
Level 6
Joined
Jun 20, 2011
Messages
249
Ofcourse it's going to be laggy!
There's a Trigger Evaluation inside a loop.
It would be better if you handled the boolexpr, created a dynamic trigger, added all the boolexprs to the trigger, and did the TriggerEvaluation at the end.

Although this sounds like a very cool solution it would make the retrieval of data an impossible task. And looping through trigger evaluations shouldn't cause lag if it's not performed periodically

EDIT: Also this.
JASS:
        method fire takes nothing returns nothing
            local integer m
            set .reg = List(this)
            loop
                set m = List(.reg).next
                call TriggerEvaluate(trig[.reg])
                set .reg = m
                exitwhen .reg == this
            endloop
        endmethod
 
Level 6
Joined
Jun 20, 2011
Messages
249
Read the documentation
JASS:
library Advent requires LinkedListModule
    
    globals
        private constant boolean RECYCLE_TRIGGERS = false
    endglobals
    
    struct Advent extends array
        
        //Everything provided by this module is public, if you don't want the user to
        //mess with it just don't mention it in the documentation, its silly to go over
        //lots of unnecesary lines of code to prevent the user from doing something no-one
        //would do anyways.
        implement LinkedList
        
        readonly static thistype reg = 0
        
        private thistype adventdata
        private trigger  trig
        
        //Best way to retrieve data, attaches it to an instance, delivered globally.
        //The "fireData" method is no longer useful if the user can attach data to it's advent.
        static method operator data takes nothing returns thistype
            return reg.adventdata
        endmethod
        method operator data= takes thistype v returns nothing
            set adventdata=v
        endmethod
        
        static method create takes nothing returns thistype
            local thistype this = createNode()
            if trig == null then
                set trig = CreateTrigger()
            else
                call TriggerClearConditions(trig)
            endif
            return this
        endmethod
        
        method destroy takes nothing returns nothing
            static if not RECYCLE_TRIGGERS then
                call DestroyTrigger(trig)
                set trig = null
            endif
            call this.flushNode()
        endmethod

        method register takes code c returns nothing
            call TriggerAddCondition(trig, Filter(c))
        endmethod
        
        //Very simple
        method unregister takes nothing returns nothing
            call TriggerClearConditions(trig)
        endmethod
        
        //Instead of messy registerEx i'm giving the user the capacity of joining two
        //advents together or even merge advent groups together.
        static method join takes thistype A, thistype B returns nothing
            set A.next.prev = B.prev
            set B.prev.next = A.next
            set A.next = B
            set B.prev = A
        endmethod
        
        //If the user wants to remove the advent from the group it's currently in.
        //Does nothing if the advent hasn't been joined.
        method unpin takes nothing returns nothing
            call this.removeNode()
        endmethod
        
        //Very clean way of firing the advent, notice how this:
        //  -Fires the whole group, doesn't matter if you're firing the "head" or not,
        //  there are no heads.
        //  -Fires the "this" trigger first, doesn't leave it for last like currently
        //  it is.
        //  -The reg variable isn't a member of other struct.
        //  -Stores the previous "reg" data inside a local variable to allow the user to
        //  fire advents within advents.
        method fire takes nothing returns nothing
            local integer m = reg
            set reg = this
            loop
                call TriggerEvaluate(reg.trig)
                set reg = reg.next
                exitwhen reg == this
            endloop
            set reg = m
        endmethod

    endstruct

    function CreateAdvent takes nothing returns Advent
        return Advent.create()
    endfunction

    function DestroyAdvent takes Advent adv returns nothing
        call adv.destroy()
    endfunction

    function FireAdvent takes Advent adv returns nothing
        call adv.fire()
    endfunction

    function RegisterAdvent takes Advent adv, code c returns nothing
        call adv.register(c)
    endfunction

    function UnregisterAdvent takes Advent adv returns nothing
        call adv.unregister()
    endfunction

    function GetAdventData takes nothing returns integer
        return Advent.data
    endfunction

endlibrary
 
Last edited:
^ You know, it would be better to call TriggerClearConditions when you deallocate an instance.

And I'd recommend creating the trigger whenever you create an instance (checking if trig[this] is null first ofcourse)
It's better because it will let register inline (TriggerAddCondition is already a slow function, let's not add an extra function call to it)
create can't inline but register can, so you might as well do it ;P (You aren't going to save handles, I mean who would create an Advent and not use it?)
 
Level 6
Joined
Oct 23, 2011
Messages
182
for anyone who needs a fixed version of 3.0.0.0... I think replacing the fire method with this solved my problems ^^

JASS:
        method fire takes nothing returns nothing
            local integer m
            set .reg = this
            loop
                set m = next[.reg]
                set .reg = m
                call TriggerEvaluate(trigs[.reg])
                set .reg = m
                exitwhen .reg == this
            endloop
        endmethod
 
Top