1. Updated Resource Submission Rules: All model & skin resource submissions must now include an in-game screenshot. This is to help speed up the moderation process and to show how the model and/or texture looks like from the in-game camera.
    Dismiss Notice
  2. DID YOU KNOW - That you can unlock new rank icons by posting on the forums or winning contests? Click here to customize your rank or read our User Rank Policy to see a list of ranks that you can unlock. Have you won a contest and still havn't received your rank award? Then please contact the administration.
    Dismiss Notice
  3. We have recently started the 16th edition of the Mini Mapping Contest. The theme is mini RPG. Do check it out and have fun.
    Dismiss Notice
  4. Dismiss Notice
  5. Choose your ride to damnation in the 5th Special Effect Contest Poll.
    Dismiss Notice
  6. The winners of the 13th Techtree Contest have been announced!
    Dismiss Notice
  7. Check out the Staff job openings thread.
    Dismiss Notice
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[vJASS] Evaluate Code

Discussion in 'Submissions' started by MyPad, Jul 31, 2018.

  1. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,338
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    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:

    Code (vJASS):

    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.
     
  2. Bribe

    Bribe

    Joined:
    Sep 26, 2009
    Messages:
    8,123
    Resources:
    25
    Maps:
    3
    Spells:
    10
    Tutorials:
    3
    JASS:
    9
    Resources:
    25
    What are the intended uses for this resource?
     
  3. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,338
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    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:
    Code (vJASS):

    // 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
     
     
  4. Bannar

    Bannar

    Joined:
    Mar 19, 2008
    Messages:
    3,087
    Resources:
    20
    Spells:
    5
    Tutorials:
    1
    JASS:
    14
    Resources:
    20
    To me this looks like sophisticated "event".
    Something similar can be achieved with typical event library:

    init:
    - register event handler

    later:
    - retrieve event trigger
    - evaluate trigger

    Both are "lightweight in implementation".
     
  5. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,338
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    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.

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

    Pseudo-code...

    Code (Text):

    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.
     
  6. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    400
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    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 (Text):

    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: Aug 7, 2018
  7. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,338
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    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: Aug 7, 2018
  8. AGD

    AGD

    Joined:
    Mar 29, 2016
    Messages:
    400
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    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
    Code (vJASS):

    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.
     
  9. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,338
    Resources:
    7
    Models:
    1
    Icons:
    2
    Spells:
    3
    JASS:
    1
    Resources:
    7
    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).