[vJASS] Evaluate Code

MyPad

Spell Reviewer
Level 25
Joined
May 9, 2014
Messages
1,741
After looking at the Hive for possible conflicts with existing libraries on the purpose of this library, I now give the user the following snippet:

JASS:
library Eval /*

    */ requires /*
       
        ------------------
        */ Table        /*
        ------------------
            -> Bribe
           
            link:   www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
           
        ---------------------------------------------------
       
        Eval
       
            - Run dynamic code without hassle.
           
        ---------------------------------------------------
       
        Objectives:
       
            - To make dynamic handler functions as
              lightweight as possible (in implementation).
           
        ---------------------------------------------------
       
        Overview:
       
            - This basically abstracts the creation of
              triggers for the purpose of dynamic code
              to the system here. 
           
            - Such a concrete example would be to create
              a Missile system that has an onHit handler
              function and an onMove handler. It would be
              cumbersome to manually create the triggers
              that would be used to allow such dynamism.
             
            - Instead, the System does all of the trigger
              creation and destruction for the user,
              freeing him or her of the responsibilities
              of declaring variables.
             
            - A hardcoded manner of allocation is implemented
              so having an Allocator library is not needed
              for this.
             
        ----------------------------------------------------
       
        Struct Usage:
       
            struct EvalCode extends array
           
             ----------------------------------------------
            |
            |   static method getExecutable()
            |       Gets the current EvalCode that ran.
            |       Useful for dynamic reference.
            |
            |   method destroy()
            |       Destroys an EvalCode instance.
            |       Double-free safe.
            |
            |   method getEvalCount() -> int {DEBUG}
            |       Gets the number of times the script
            |       was evaluated.
            |
            |   method run()
            |       Evaluates the code. Also destroys the
            |       instance if destroy was called inter
            |       nally. (Within the running method)
            |
            |   method operator code= (code func)
            |       Sets which code will be evaluated.
            |
            |   method operator add(code func)
            |       A method which adds code to the list
            |       of evaluations when ran.
            |
             ---------------------------------------------
   
    --------------
    Example usage: 
    --------------
    */
   
    -------------------------------------------------------

    //! novjass
        function foo takes nothing returns nothing
        endfunction
       
        function bar takes nothing returns nothing
        endfunction
       
        function asd takes nothing returns nothing
        endfunction
       
        function lrv takes nothing returns nothing
        endfunction
       
        function handler takes nothing returns nothing
            local EvalCode eval = EvalCode.create()
           
            set eval.code = function foo
           
            call eval.run() // Runs code
            call eval.add(function bar)

            call eval.run() // Runs function foo, then bar.
            call eval.add(function asd)

        endfunction
    //! endnovjass
       
    --------------------------------------------------------
    /*
   
    */

private keyword EvalCodeM

struct EvalCode extends array
    private static TableArray metaData = 0
   
    static method getExecutable takes nothing returns EvalCode
        return EvalCode.metaData[3].integer[-1]
    endmethod
   
    method destroy takes nothing  returns nothing
        //  Double-free protection!
        if not EvalCode.metaData[1].integer.has(this) or EvalCode.metaData[1].integer[this] != 0 then
            return
        endif
       
        //  Cannot destroy while an instance is running
        if EvalCode.metaData[3].integer[this] > 0 then
            if not EvalCode.metaData[4].boolean[this] then
                set EvalCode.metaData[4].boolean[this] = true
            endif
            return
        endif
       
        if EvalCode.metaData[2].trigger.has(this) then
            call DestroyTrigger(EvalCode.metaData[2].trigger[this])
            call EvalCode.metaData[2].trigger.remove(this)
        endif
       
        set EvalCode.metaData[1].integer[this] = EvalCode.metaData[1].integer[0]
        set EvalCode.metaData[1].integer[0]    = this
       
        call EvalCode.metaData[3].integer.remove(this)       
        call EvalCode.metaData[4].boolean.remove(this)
    endmethod
   
    debug method getEvalCount takes nothing returns integer
        debug return GetTriggerEvalCount(EvalCode.metaData[2].trigger[this])
    debug endmethod
   
    method run takes nothing returns nothing
        local EvalCode lastEval = EvalCode.getExecutable()
       
        set EvalCode.metaData[3].integer[-1]   = this
        set EvalCode.metaData[3].integer[this] = EvalCode.metaData[3].integer[this] + 1
       
        call TriggerEvaluate(EvalCode.metaData[2].trigger[this])
       
        set EvalCode.metaData[3].integer[this] = EvalCode.metaData[3].integer[this] - 1
        set EvalCode.metaData[3].integer[-1]   = lastEval

        if EvalCode.metaData[3].integer[this] <= 0 and EvalCode.metaData[4].boolean[this] then
            call this.destroy()
        endif
    endmethod
   
    method operator code= takes code func returns nothing
        if EvalCode.metaData[2].trigger.has(this) then
            call DestroyTrigger(EvalCode.metaData[2].trigger[this])
        endif
       
        set EvalCode.metaData[2].trigger[this] = CreateTrigger()
        call TriggerAddCondition(EvalCode.metaData[2].trigger[this], Condition(func))
    endmethod
   
    method add takes code func returns nothing
        if not EvalCode.metaData[2].trigger.has(this) then
            set this.code = func
        else
            call TriggerAddCondition(EvalCode.metaData[2].trigger[this], Condition(func))
        endif
    endmethod
   
    static method create takes nothing returns EvalCode
        local EvalCode temp = EvalCode.metaData[1].integer[0]
       
        if EvalCode.metaData[1].integer[temp] == 0 then
            set temp = temp + 1
            set EvalCode.metaData[1].integer[0]    = temp
            set EvalCode.metaData[1].integer[temp] = 0
        else
            set EvalCode.metaData[1].integer[0] = EvalCode.metaData[1].integer[temp]
            set EvalCode.metaData[1].integer[temp] = 0
        endif
       
        set EvalCode.metaData[3].integer[temp] = 0
        set EvalCode.metaData[4].boolean[temp] = false

        return temp
    endmethod
   
    private static method init takes nothing returns nothing
        set EvalCode.metaData = TableArray[5]
    endmethod
   
    implement EvalCodeM
endstruct

private module EvalCodeM
    private static method onInit takes nothing returns nothing
        call thistype.init()
    endmethod
endmodule

endlibrary

library_once EvalCode requires Eval
endlibrary

If there are existing libraries that do conflict with this, I will be more than happy to accept proposed changes to this.
 

MyPad

Spell Reviewer
Level 25
Joined
May 9, 2014
Messages
1,741
Dynamic handler functions, such as handler functions on Missile movement attributes (onMove and onHit), Auras (onEnum, onEnter and onEnumRemove).

Sometimes, one would like to have a generic system that allows the user to do different things which the system permits the user to do. To do that, a trigger is usually affixed to the system, needlessly having to complicate the coding process, which led to this system. It does that, and in the process, it became somewhat of a natural extension to the code type.

In essence, it tries to act as the handle extension to the native type code.

What the library does:
JASS:
// Assume code variable varBar

function vee takes nothing returns nothing
endfunction

function foo takes nothing returns nothing
    local EvalCode handler = EvalCode.create()
    set handler.code = varBar
    call handler.run()
    set handler.code = function vee
endfunction
    call handler.run()
    call handler.destroy()
endfunction
 

MyPad

Spell Reviewer
Level 25
Joined
May 9, 2014
Messages
1,741
It looks like the nature of this library is misunderstood, and rightly so, since I cannot properly explain this without somehow relating this to "Event" libraries, which are as lightweight as they can get.

In essence, it tries to act as the handle extension to the native type code.

Perhaps the best way to explain this is by how I coded my Missile library.

Pseudo-code...

Code:
class EvalCode

run()
    TriggerEvaluate(...)
    ....

// Overwrites the former handler code the instance was "pointing" to.
void method operator code= (code func)
    if this.triggerExists()
           this.refreshTrigger()
    
    this.createTrigger()
    TriggerAddCondition(this.getTrigger(), Condition(func))

// Does not overwrite the handler code.
method add(code func)

class Missile

...

EvalCode onMove
EvalCode onHit

ondestroy()
    destroy this.onMove
    destroy this.onHit

static construct()
    this.onMove = EvalCode.create()
    this.onHit    = EvalCode.create()

void static method onUpdate()
    ....

    For Missile this in Missile.globalList: do
        if this.assertDistance(vector) <= this.getSpeed()
            this.move(vector)
            run this.onHit
        else
            this.move(this.getVectorFromTarget())
            run this.onMove

Each trigger is individually created per EvalCode instance. So, each one of them can run a specific set of code. Now, what if each Missile is treated differently, from the onMove to the onHit? That would require us to either adopt a module approach or an individual trigger per instance approach, and that is how this came to be.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I still don't see a difference between this and an Event library. I could translate your pseudocode above for example using the traditional Event lib.

Code:
class Event

    method register(code c)
    // - equivalent to your add()
    method unregister(code c)
    // - imo for a handler system to be truly dynamic, you should also provide this aside from clear()
    method clear()
    // - your 'operator code=' would then be equivalent to clear() followed be register()


class Missile

    Event onHit
    Event onMove

    onCreate
        this.onHit = new Event
        this.onMove = new Event

    onDestroy
        delete onHit
        delete onMove

    static method onUpdate()
        for Missile this in missileList
            if this.assertDistance(target) <= this.speed
                this.move(target)
                this.run(this.onHit)
            else
                this.move(nextPos)
                this.run(this.onMove)


But hey, since we have no lightweight event lib here, we can have one. It's certainly needed and I personally find myself keep repeating the same codes for my systems.

In your add method, you can actually simplify it to just TriggerAddCondition(), no need for the if-else statement. That is if you create the trigger beforehand in the create method.

Also, why are you currently using Table? The way I see it, u can just directly use arrays. Unless you want to minimize variable declarations, but still arrays are much more readable, lightwieght, + doesn't need an onInit method.

As I also said earlier, you could also maybe support unregistering code if you want to achieve total dynamism. Otherwise, most (maybe all) methods would just be one liner which would mean that it would be pretty straightforward to achieve them without using a system.
 
Last edited:

MyPad

Spell Reviewer
Level 25
Joined
May 9, 2014
Messages
1,741
If an Event object fires, all triggers registered to that will be evaluated. Such examples are the following: one by jesus4lyf (Event using struct and linked lists), and one by Nestharus (using TriggerRegisterVariableEvent). However, EvalCode instances are not necessarily bound to such event objects and can be freely run wherever and whenever. (They can be bound or not). That is what makes the difference.

Edit:

I noticed that what you call Events is what I consider as EvalCodes. Anyway, I hope that the definition of Event above will be enough to clarify things.

End edit:

Also, I like having a lot of instances just in case the system is overloaded, but I'll see if I can incorporate a USE_VARIABLES flag into the system.

That being said, I feel like revamping the safety of the system by not destroying the running trigger whenever a destroy call is made inside the system, or when a triggercondition is to be removed, (in retrospect, this might bloat the code a bit, and lead to the approach taken by Nes (balanced heap) on Trigger).

Edit 2:

@AGD If you feel like looking at another Event system, I had one in the Lab. It's called Pseudo-Var Event or EventStruct (the former name is because of how it behaved, syntactically).
 
Last edited:

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
If an Event object fires, all triggers registered to that will be evaluated. Such examples are the following: one by jesus4lyf (Event using struct and linked lists), and one by Nestharus (using TriggerRegisterVariableEvent). However, EvalCode instances are not necessarily bound to such event objects and can be freely run wherever and whenever. (They can be bound or not). That is what makes the difference.
The term Event evolved over the years. At start, it only meant a snippet that is basically a wrapper for the TRVE with the object instance converted to real and the real is used the value for firing the real event. Then, people combined this with the ability to register codes (aside from triggers) - see Nes' early versions of his event lib on the lab. Later on, some preferred to exclude trigger registration altogether because of the inability to unregister them (if using the TRVE method) or due to the huge overhead in performance due to iterating + evaluating each if you're going to use Jesus4Lyf's method, and also for the reason that there's not much you can't do with triggers that you can't do with codes anyway, except only for TSA. Today 'Event' is just another term for custom events, something that you need to fire manually, whether your way of registration is with a single code or a set of codes (like a trigger) does not matter.
You can even look at some of the event libs (not custom event, but still in the 'event' category) as an analogy for what I'm saying
JASS:
RegisterAnyPlayerUnitEvent(playerunitevent, code)
RegisterAnyPlayerEvent(playerevent, code)
They don't include trigger registration firstly because it defeats one of their purpose (handle minimization) and finally because you can just use codes instead of triggers. But even so, they are still event libs. So I hope this clears the semantic issue we have here.

Btw off topic: Nes' latest Trigger doesn't use TRVE. Its reference(whichTrigger) method replicates boolexprs of the input trigger and registers them all to the referencing Trigger, greatly increasing handle count but also unlocks great possibilities, namely, handler prioritization.
 

MyPad

Spell Reviewer
Level 25
Joined
May 9, 2014
Messages
1,741
Alright. I never saw Event objects in that light, so I had developed an approach of my own to that. Taking the semantical definition into account, I basically have two Event systems, one which replicates the functionality of TRVE but also extending it to act as a proper object and this one, which I did not know fits the niche as it stands now.

Since there aren't a lot of these things, I might as well rename this (After the Techtree Contest #12).

 
Top