Snippet Event

Jesus4Lyf

Good Idea™
Reaction score
397
Event​
Version 1.04​

Requirements:
- Jass NewGen

JASS:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//  ~~    Event     ~~    By Jesus4Lyf    ~~    Version 1.04    ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//  What is Event?
//         - Event simulates Warcraft III events. They can be created,
//           registered for, fired and also destroyed.
//         - Event, therefore, can also be used like a trigger "group".
//         - This was created when there was an influx of event style systems 
//           emerging that could really benefit from a standardised custom
//           events snippet. Many users were trying to achieve the same thing
//           and making the same kind of errors. This snippet aims to solve that.
//
//  Functions:
//         - Event.create()       --> Creates a new Event.
//         - .destroy()           --> Destroys an Event.
//         - .fire()              --> Fires all triggers which have been
//                                    registered on this Event.
//         - .register(trigger)   --> Registers another trigger on this Event.
//         - .unregister(trigger) --> Unregisters a trigger from this Event.
//
//  Details:
//         - Event is extremely efficient and lightweight.
//         - It is safe to use with dynamic triggers.
//         - Internally, it is just a linked list. Very simple.
//
//  How to import:
//         - Create a trigger named Event.
//         - Convert it to custom text and replace the whole trigger text with this.
//
//  Thanks:
//         - Builder Bob for the trigger destroy detection method.
//         - Azlier for inspiring this by ripping off my dodgier code.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library Event
    ///////////////
    // EventRegs //
    ////////////////////////////////////////////////////////////////////////////
    // For reading this far, you can learn one thing more.
    // Unlike normal Warcraft III events, you can attach to Event registries.
    // 
    // Event Registries are registrations of one trigger on one event.
    // These cannot be created or destroyed, just attached to.
    //
    // It is VERY efficient for loading and saving data.
    // 
    //  Functions:
    //         - set eventReg.data = someStruct --> Store data.
    //         - eventReg.data                  --> Retreive data.
    //         - Event.getTriggeringEventReg()  --> Get the triggering EventReg.
    //         - eventReg.destroy()             --> Undo this registration.
    // 
    private keyword destroyNode
    struct EventReg extends array
        integer data
        method clear takes nothing returns nothing
            set this.data=0
        endmethod
        method destroy takes nothing returns nothing
            call Event(this).destroyNode()
        endmethod
    endstruct
    
    private module Stack
        static thistype top=0
        static method increment takes nothing returns nothing
            set thistype.top=thistype(thistype.top+1)
        endmethod
        static method decrement takes nothing returns nothing
            set thistype.top=thistype(thistype.top-1)
        endmethod
    endmodule
    
    private struct EventStack extends array
        implement Stack
        Event current
    endstruct
    
    struct Event
        private trigger trig
        private thistype next
        private thistype prev
        
        static method getTriggeringEventReg takes nothing returns EventReg
            return EventStack.top.current
        endmethod
        
        static method create takes nothing returns Event
            local Event this=Event.allocate()
            set this.next=this
            set this.prev=this
            return this
        endmethod
        
        private static trigger currentTrigger
        method fire takes nothing returns nothing
            local thistype curr=this.next
            call EventStack.increment()
            loop
                exitwhen curr==this
                set thistype.currentTrigger=curr.trig
                if IsTriggerEnabled(thistype.currentTrigger) then
                    set EventStack.top.current=curr
                    if TriggerEvaluate(thistype.currentTrigger) then
                        call TriggerExecute(thistype.currentTrigger)
                    endif
                else
                    call EnableTrigger(thistype.currentTrigger) // Was trigger destroyed?
                    if IsTriggerEnabled(thistype.currentTrigger) then
                        call DisableTrigger(thistype.currentTrigger)
                    else // If trigger destroyed...
                        set curr.next.prev=curr.prev
                        set curr.prev.next=curr.next
                        call curr.deallocate()
                    endif
                endif
                set curr=curr.next
            endloop
            call EventStack.decrement()
        endmethod
        method register takes trigger t returns EventReg
            local Event new=Event.allocate()
            set new.prev=this.prev
            set this.prev.next=new
            set this.prev=new
            set new.next=this
            
            set new.trig=t
            
            call EventReg(new).clear()
            return new
        endmethod
        method destroyNode takes nothing returns nothing // called on EventReg
            set this.prev.next=this.next
            set this.next.prev=this.prev
            call this.deallocate()
        endmethod
        method unregister takes trigger t returns nothing
            local thistype curr=this.next
            loop
                exitwhen curr==this
                if curr.trig==t then
                    set curr.next.prev=curr.prev
                    set curr.prev.next=curr.next
                    call curr.deallocate()
                    return
                endif
                set curr=curr.next
            endloop
        endmethod
        
        method destroy takes nothing returns nothing
            local thistype curr=this.next
            loop
                call curr.deallocate()
                exitwhen curr==this
                set curr=curr.next
            endloop
        endmethod
        method chainDestroy takes nothing returns nothing
            call this.destroy() // backwards compatability.
        endmethod
    endstruct
    
    /////////////////////////////////////////////////////
    // Demonstration Functions & Alternative Interface //
    ////////////////////////////////////////////////////////////////////////////
    // What this would look like in normal WC3 style JASS (should all inline).
    // 
    function CreateEvent takes nothing returns Event
        return Event.create()
    endfunction
    function DestroyEvent takes Event whichEvent returns nothing
        call whichEvent.chainDestroy()
    endfunction
    function FireEvent takes Event whichEvent returns nothing
        call whichEvent.fire()
    endfunction
    function TriggerRegisterEvent takes trigger whichTrigger, Event whichEvent returns EventReg
        return whichEvent.register(whichTrigger)
    endfunction
    
    // And for EventRegs...
    function SetEventRegData takes EventReg whichEventReg, integer data returns nothing
        set whichEventReg.data=data
    endfunction
    function GetEventRegData takes EventReg whichEventReg returns integer
        return whichEventReg.data
    endfunction
    function GetTriggeringEventReg takes nothing returns integer
        return Event.getTriggeringEventReg()
    endfunction
endlibrary

I couldn't upload a test map due to running out of attachment space, but here's the script I used to test it:
JASS:
scope EventTest initializer DoTest
    private function ExampleCond takes nothing returns boolean
        call BJDebugMsg("Trigger "+I2S(GetTriggerExecCount(GetTriggeringTrigger()))+" fired.")
        return false
    endfunction
    private function DoTest takes nothing returns nothing
        local trigger t1=CreateTrigger()
        local trigger t2=CreateTrigger()
        local trigger t3=CreateTrigger()
        
        local Event e=CreateEvent()
        
        call TriggerExecute(t1)
        call TriggerExecute(t2)
        call TriggerExecute(t2)
        call TriggerExecute(t3)
        call TriggerExecute(t3)
        call TriggerExecute(t3)
        
        call TriggerAddCondition(t1,Condition(function ExampleCond))
        call TriggerAddCondition(t2,Condition(function ExampleCond))
        call TriggerAddCondition(t3,Condition(function ExampleCond))
        
        call TriggerRegisterEvent(t1,e)
        
        call BJDebugMsg("Fire event with Trigger 1 registered...")
        call FireEvent(e)
        
        call TriggerRegisterEvent(t2,e)
        call TriggerRegisterEvent(t3,e)
        
        call BJDebugMsg("Fire event with Trigger 1, 2 and 3 registered...")
        call FireEvent(e)
        
        call TriggerRegisterEvent(t1,e)
        call DestroyTrigger(t2)
        
        call BJDebugMsg("Fire event with Trigger 1 (twice) and 3 registered...")
        call FireEvent(e)
        
        call DestroyEvent(e)
    endfunction
endscope
And the script to test the new data attachment on EventRegs:
JASS:
scope EventTest initializer DoTest
    private function ExampleCond takes nothing returns boolean
        call BJDebugMsg("Trigger "+I2S(GetEventRegData(GetTriggeringEventReg()))+" fired.")
        return false
    endfunction
    private function DoTest takes nothing returns nothing
        local trigger t1=CreateTrigger()
        local trigger t2=CreateTrigger()
        local trigger t3=CreateTrigger()
        
        local Event e=CreateEvent()
        
        call TriggerAddCondition(t1,Condition(function ExampleCond))
        call TriggerAddCondition(t2,Condition(function ExampleCond))
        call TriggerAddCondition(t3,Condition(function ExampleCond))
        
        call SetEventRegData(TriggerRegisterEvent(t1,e),1)
        
        call BJDebugMsg("Fire event with Trigger 1 registered...")
        call FireEvent(e)
        
        call SetEventRegData(TriggerRegisterEvent(t2,e),2)
        call SetEventRegData(TriggerRegisterEvent(t3,e),3)
        
        call BJDebugMsg("Fire event with Trigger 1, 2 and 3 registered...")
        call FireEvent(e)
        
        call SetEventRegData(TriggerRegisterEvent(t1,e),1)
        call DestroyTrigger(t2)
        
        call BJDebugMsg("Fire event with Trigger 1 (twice) and 3 registered...")
        call FireEvent(e)
        
        call DestroyEvent(e)
    endfunction
endscope
It's a pretty low-level snippet. It can be applied in a whole range of this (and already has been at the time of this update).

Updates:
- Version 1.04: Better supported recursive Events, reversed the firing order to match Warcraft III's, added .unregister(trigger), implemented .destroy() for events (deprecating .chainDestroy()) and added .destroy() for EventRegs (to unregister a given registration). Changed internal implementation to a doubly linked circular list.
- Version 1.03: Fixed a bug to do with recursive Events.
- Version 1.02: Updated documentation (finally).
- Version 1.01: Added EventRegs.
- Version 1.00: Release.
 

Viikuna

No Marlo no game.
Reaction score
265
Awesome.

You should maybe add IsTriggerEnabled check there. ( Im not sure, but I think you can evaluate and execute disabled trigers too )
 

Romek

Super Moderator
Reaction score
963
method chainDestroy would make more sense as onDestroy.
I don't see why anybody would want to destroy it without doing what chainDestroy does anyway.

> if GetTriggerEvalCount(current.trig)==0 then // If trigger destroyed...
Isn't that set to 0 when the trigger is 'reset' too? (call ResetTrigger(..))
I've never messed around with triggers as such, but I've certainly read about that.

Also, 'next' could be given a default value of 0, and the 'create' method could be removed.
 

Azlier

Old World Ghost
Reaction score
461
Not too sure how to use this, and not so sure I'll get any efficiency gain. Besides, I like DoubleClick not requiring any other systems. ;)

Edit: Ooh, now I gots it. I'll release an Event version now. And do what Romek said about the 0, I made TrigChains do that.
 

Jesus4Lyf

Good Idea™
Reaction score
397
Hmmm. I think you're right there, Viikuna. I'll fix that when I release data attachment probably.

Ahh, Romek, everything happens for a reason!

>method chainDestroy would make more sense as onDestroy.
I would need to make onDestroy recursive then, which is less efficient. (.destroy() is called inside .chainDestroy().)
vJass has this horrible "feature" of not being able to write your own .destroy() method with a .deallocate() inside it, although you can write your own .create() method with a .allocate(). :(
It's one of the things that limits the usefulness of structs.
Afterthought: No, I couldn't even make this recursive, otherwise when a trigger is destroyed it would destroy its node and all the nodes after it. I could write a way around it but this still simplifies code and makes it more efficient. If you still think I should use the .destroy() function, let me know. o.o

> if GetTriggerEvalCount(current.trig)==0 then // If trigger destroyed...
>Isn't that set to 0 when the trigger is 'reset' too? (call ResetTrigger(..))
I thought about that. Call it a feature for detaching the triggering event. :)
I might even write a function wrapper for it.

>Also, 'next' could be given a default value of 0, and the 'create' method could be removed.
But then it will set 0 unnecessarily for registering new triggers! :p
IMAGINE THE LAG!

>I like DoubleClick not requiring any other systems
Precisely why this is a snippet. You can just embed it in your system, just change everything to private (including the struct type).
 

Azlier

Old World Ghost
Reaction score
461
Did some testing, and the '=0' is unneeded. Global integer arrays naturally have 0 in each index, just as global boolean arrays have false. Locals don't do that.
 

Jesus4Lyf

Good Idea™
Reaction score
397
Azlier, beware stability. I hope you test things more thoroughly in future.

If a struct is destroyed and then another created with the same index, old values that are not initialised are retained. In this case, it could be the .next pointers. This could lead (and nearly certain would if used any reasonable amount) to events firing other events or parts of them accidentally.

It's exactly for reasons like this that I produced this snippet. Stability.
Linked lists are prone to unpredictable behavior if not thought through carefully. :)

Edit: Example (for if you remove the set 0):
Create Event E1.
Register Trigger T1 on E1.
Destroy Event E1.
Create Event E2.
Create Event E3.
Register Trigger T2 on E3.
Firing E2 at this point will fire Trigger T2 and everything else in E3. It will also, on the first time it fires, deregister E3 and attach its chain onto its own (at the end) (but its current chain was empty anyway). Therefore...
Fire E2.
Create Event E4.
Register Trigger T3 on E4.
Now when you fire E2, it will fire the old E3. Firing E3 will fire E4. E3 and E4 are in fact exactly the same and would both fire T3. This is the same as doubly allocating a handle.

And that is why you must set to 0. (I didn't test the above. It's based on my knowledge.)
 

Azlier

Old World Ghost
Reaction score
461
And I thought destroying them made it become 0. Apparently not. Perhaps I should've tested with structs. :p
 

Azlier

Old World Ghost
Reaction score
461
Too busy writing a failing random maze generator :(
 

Azlier

Old World Ghost
Reaction score
461
Well. I was wrong, then.

Oh, and I recommend either putting the snippet in a library or removing the "helpful" user functions.
 

Jesus4Lyf

Good Idea™
Reaction score
397
Ok. I've put it in a library for easy referencing (even though it's meant to be a snippet, not a system XD).

I've released the new version with data attachment. It's fully backwards compatible, and
JASS:
GetEventRegData(GetTriggeringEventReg())

should actually inline to
JASS:
Data[GlobalIntOfSomeSort]

:D
 

Azlier

Old World Ghost
Reaction score
461
JASS:
private static Event current
        static method getTriggeringEventReg takes nothing returns EventReg
            return .current
        endmethod

...Why?
JASS:
readonly static Event current


JASS:
function SetEventRegData takes EventReg whichEventReg, integer data returns nothing
    set whichEventReg.data=data
endfunction
function GetEventRegData takes EventReg whichEventReg returns integer
    return whichEventReg.data
endfunction
function GetTriggeringEventReg takes nothing returns integer
    return Event.getTriggeringEventReg()
endfunction

These are returning integers... why?

In fact, what good can possibly come from those functions?
 

Romek

Super Moderator
Reaction score
963
> These are returning integers... why?
Structs = Integers

> In fact, what good can possibly come from those functions?
They make things neater, and are inlined anyway.
One can choose not to use them if they don't want to.
 

Azlier

Old World Ghost
Reaction score
461
Yes, I know structs return integers. But integers don't allow . syntax. I would much prefer the functions returning structs.
 

Cohadar

master of fugue
Reaction score
209
This library needs some serious documentation.
For starters explining more clearly what is it, how to use it and why.

And what the hell is EventReg?
A pointless non-struct used to garbage the code for no apparent reason?

And make those damn functions public, you don't own the exclusive access right to jass namespace.
I do.


EDIT:
Who the hell approved of this resource?
 

Cohadar

master of fugue
Reaction score
209
Ah was looking at the wrong subforum. nwm.

@Jesus4Lyf
JASS:

// Just because blizzard does not have one does not...
function Event_TriggerUnRegister takes trigger whichTrigger, Event whichEvent returns nothing
 
General chit-chat
Help Users
  • No one is chatting at the moment.

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top