1. The long-awaited results for Concept Art Contest #11 have finally been released!
    Dismiss Notice
  2. Join Texturing Contest #30 now in a legendary battle of mythological creatures!
    Dismiss Notice
  3. The 20th iteration of the Terraining Contest is upon us! Join and create exquisite Water Structures for it.
    Dismiss Notice
  4. Hivers united and created a bunch of 2v2 melee maps. Vote for the best in our Melee Mapping Contest #4 - Poll!
    Dismiss Notice
  5. Check out the Staff job openings thread.
    Dismiss Notice

[Snippet] Resource Preloader

Discussion in 'JASS Resources' started by AGD, Aug 12, 2016.

  1. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    RESOURCE PRELOADER v1.4c


    This snippet allows to preload your map resources. When first time a resource is created in your map, it lags a bit. In order to prevent that, this system preloads them at map initialization.
    This system simplifies your tasks in preloading with its simple APIs. Furthermore, this system ensures that there will be no duplicates on the resource that will be preloaded which is nice.

    Importing:
    - Create an empty script in your map
    - Copy the script below and then paste it to the empty script stated above
    - You're done


    Resource Preloader Script
    Code (vJASS):
    library ResourcePreloader /* v1.4c


        */
    uses /*

        */
    optional BJObjectId   /*   http://www.hiveworkshop.com/threads/bjobjectid.287128/
        */
    optional Table        /*   http://www.hiveworkshop.com/threads/snippet-new-table.188084/
        */
    optional UnitRecycler /*   http://www.hiveworkshop.com/threads/snippet-unit-recycler.286701/


        */
    //! novjass

        |================|
        | Written by AGD |
        |================|

            [CREDITS]
        /*      IcemanBo - for suggesting further improvements
                Silvenon - for the sound preloading method                            */



            |-----|
            | API |
            |-----|

                function PreloadUnit takes integer rawcode returns nothing/*
                    - Assigns a certain type of unit to be preloaded

              */
    function PreloadItem takes integer rawcode returns nothing/*
                    - Assigns a certain type of item to be preloaded

              */
    function PreloadAbility takes integer rawcode returns nothing/*
                    - Assigns a certain type of ability to be preloaded

              */
    function PreloadEffect takes string modelPath returns nothing/*
                    - Assigns a certain type of effect to be preloaded

              */
    function PreloadSound takes string soundPath returns nothing/*
                    - Assigns a certain type of sound to be preloaded


              The following functions requires the BJObjectId library:

              */
    function PreloadUnitEx takes integer start, integer end returns nothing/*
                    - Assigns a range of units to be preloaded

              */
    function PreloadItemEx takes integer start, integer end returns nothing/*
                    - Assigns a range of items to be preloaded

              */
    function PreloadAbilityEx takes integer start, integer end returns nothing/*
                    - Assigns a range of abilities to be preloaded


        */
    //! endnovjass

        /*========================================================================================================*/
        /*            Do not try to change below this line if you're not so sure on what you're doing.            */
        /*========================================================================================================*/

        private keyword S

        /*============================================== TextMacros ==============================================*/

        //! textmacro PRELOAD_TYPE takes NAME, ARG, TYPE, INDEX, I
        function Preload$NAME$ takes $ARG$ what returns nothing
            static if LIBRARY_Table then
                if S.tb[$I$].boolean[$INDEX$] then
                    return
                endif
                set S.tb[$I$].boolean[$INDEX$] = true
                call Do$NAME$Preload(what)
            else
                if LoadBoolean(S.tb, $I$, $INDEX$) then
                    return
                endif
                call SaveBoolean(S.tb, $I$, $INDEX$, true)
                call Do$NAME$Preload(what)
            endif
        endfunction
        //! endtextmacro

        //! textmacro RANGED_PRELOAD_TYPE takes NAME
        function Preload$NAME$Ex takes integer start, integer end returns nothing
            local boolean mode = start < end
            loop
                call Preload$NAME$(start)
                exitwhen start == end
                if mode then
                    set start = BJObjectId(start).plus_1()
                    exitwhen start > end
                else
                    set start = BJObjectId(start).minus_1()
                    exitwhen start < end
                endif
            endloop
        endfunction
        //! endtextmacro

        /*========================================================================================================*/

        private function DoUnitPreload takes integer id returns nothing
            static if LIBRARY_UnitRecycler then
                call RecycleUnitEx(CreateUnit(Player(15), id, 0, 0, 270))
            else
                call RemoveUnit(CreateUnit(Player(15), id, 0, 0, 0))
            endif
        endfunction

        private function DoItemPreload takes integer id returns nothing
            call RemoveItem(UnitAddItemById(S.dummy, id))
        endfunction

        private function DoAbilityPreload takes integer id returns boolean
            return UnitAddAbility(S.dummy, id) and UnitRemoveAbility(S.dummy, id)
        endfunction

        private function DoEffectPreload takes string path returns nothing
            call DestroyEffect(AddSpecialEffectTarget(path, S.dummy, "origin"))
        endfunction

        private function DoSoundPreload takes string path returns nothing
            local sound s = CreateSound(path, false, false, false, 10, 10, "")
            call SetSoundVolume(s, 0)
            call StartSound(s)
            call KillSoundWhenDone(s)
            set s = null
        endfunction

        //! runtextmacro PRELOAD_TYPE("Unit", "integer", "unit", "what", "0")
        //! runtextmacro PRELOAD_TYPE("Item", "integer", "item", "what", "1")
        //! runtextmacro PRELOAD_TYPE("Ability", "integer", "ability", "what", "2")
        //! runtextmacro PRELOAD_TYPE("Effect", "string", "effect", "StringHash(what)", "3")
        //! runtextmacro PRELOAD_TYPE("Sound", "string", "sound", "StringHash(what)", "4")

        static if LIBRARY_BJObjectId then
        //! runtextmacro RANGED_PRELOAD_TYPE("Unit")
        //! runtextmacro RANGED_PRELOAD_TYPE("Item")
        //! runtextmacro RANGED_PRELOAD_TYPE("Ability")
        endif

        /*========================================================================================================*/

        private module Init
            private static method onInit takes nothing returns nothing
                local rect world = GetWorldBounds()
                static if LIBRARY_Table then
                    set tb = TableArray[5]
                endif
                set dummy = CreateUnit(Player(15), 'hpea', 0, 0, 0)
                call UnitAddAbility(dummy, 'AInv')
                call UnitAddAbility(dummy, 'Avul')
                call UnitRemoveAbility(dummy, 'Amov')
                call SetUnitY(dummy, GetRectMaxY(world) + 1000)
                call RemoveRect(world)
                set world = null
            endmethod
        endmodule

        private struct S extends array
            static if LIBRARY_Table then
                static TableArray tb
            else
                static hashtable tb = InitHashtable()
            endif
            static unit dummy
            implement Init
        endstruct


    endlibrary
     


    Sample Usage
    Code (vJASS):

    scope Test initializer onInit

        private function OnInit takes nothing returns nothing
            call FogEnable(false)
            call FogMaskEnable(false)
            call PanCameraToTimed(0, 0, 0)
            call PreloadUnit('hpea')
            call PreloadUnit('hfoo')
            call PreloadUnit('hrif')
            call PreloadUnit('ogru')
            call PreloadUnit('hfoo')// If debug mode is on, this will display "Operation Cancelled: Entered unit data was already preloaded"
            call PreloadEffect("Abilities\\Weapons\\RocketMissile\\RocketMissile.mdl")
            call PreloadEffect("Abilities\\Spells\\Human\\Heal\\HealTarget.mdl")
            call PreloadEffect("Abilities\\Weapons\\RocketMissile\\RocketMissile.mdl")// If debug mode is on, this will display "Operation Cancelled: Entered effect data was already preloaded"
        endfunction

    endscope
     



    Changelogs

    v1.4c
    - Made BJObjectId an optional requirement
    - Removed the debug messages

    v1.4b
    - Fixed a bug in the Ranged preloading in which the condition start == end won't be met and therefore the loop will not exit thus resulting to hitting the op limit (important update)
    - Reduced function DoAbilityPreload into 1 line
    - Uses GetWorldBounds() instead of bj_mapInitialPlayableArea as the distance basis of the dummy unit's placement
    - Improved function DoUnitPreload

    v1.4
    - Made Table optional
    - Added UnitRecycler as an optional requirement
    - Upon calling PreloadUnit(), if the unit is not a hero and UnitRecycler is found, the unit will be added to the unit stock instead. Otherwise, it goes with normal preloading.
    - Dummy unit's movement is disabled to prevent possible game crash.
    - Other changes

    v1.3
    - You can now preload at any time during the game instead of only during the map initialization
    - Significantly optimized the code
    - Removed the unnecessary custom function for checking preload duplicates
    - Added Table to the library requirements
    - Other changes

    v1.2
    - Preloading does not anymore happen in a single phase at the GUI Map Initialization
    - Resources are now preloaded at the instant you call the preload function

    v1.1
    - Replaced the struct API
    Preload.$TYPE$()
    to
    Preload$TYPE$()
    because using a reserved word as the struct name can be buggy
    - Added sounds to the preloadable types and removed destructables
    - Added the functionality for ranged preloading
    - Some code restructuring

    v1.0
    - First Release
     
    Last edited: Jan 8, 2017
  2. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,054
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Why to use

    call Preload.effect("Abilities\\Weapons\\RocketMissile\\RocketMissile.mdl")

    over this:
    call Preload("Abilities\\Weapons\\RocketMissile\\RocketMissile.mdl")


    ? modelpaths, soundpaths, alike can be nativly preloaded easily. Actually it seems.
    But have you maybe noticed a difference? It might be there exists one, I don't know honestly how it will be handled exactly with the preload versus an actual creation and destruction of a an object.


    In jass submissions we suprisingly really lack of preloading all kind of things together, that we might need. I found:
    this GUI Preload System for GUI users
    this [Snippet] ItemPreloader to preload only items.
    this AbilityPreload - Wc3C.net to preload abilities, which has Range included but buggy, see here.
    this xe0.9 - Wc3C.net to preload abilities, which has no Range included.

    So I think we should have one common PreloadUtils which should cover every useful preload + support range preload with a correct/fixed tequnique.
     
    Last edited: Aug 12, 2016
  3. Chaosy

    Chaosy

    Joined:
    Jun 9, 2011
    Messages:
    10,528
    Resources:
    17
    Maps:
    1
    Spells:
    10
    Tutorials:
    6
    Resources:
    17
    Useful.
    I suggest including sounds too though, because that's the only thing I've needed personally. Perhaps there are more that can say that.
     
  4. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    I think only abilities (and their [special] effects) need preloading because they are the source of the lag.

    Try making many (10-20) units without abilities and see if they cause first time creation lag.
    Then make 1 unit and add 1-3 abilities to it and see if it lags more than all the units without abilities.
     
  5. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,054
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    You might be true.
     
  6. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Yes. But its still nice to have the option to preload units and other types because aside from abilities, their model, icons, sounds, etc. also needs to be preloaded.
     
  7. Emm-A-

    Emm-A-

    Joined:
    Jul 1, 2008
    Messages:
    1,311
    Resources:
    0
    Resources:
    0
    hey there,

    I totally agree with chaosy, that preloading sounds is a very important thing because of this "1st time no play" bug.

    Other than that, I would like to know why simply not use the actual preloading itself such as
    Code (vJASS):
    RemoveUnit(CreateUnit(Player(15), thistype.unitArg[i], 0, 0, 0))


    I do not intend to sound destructive here at all, I really just dont know the advantage and would like to know :)
    because actually I do preloading like this in my maps manually and have to copy every single object by hand anyway ...
     
  8. Aniki

    Aniki

    Joined:
    Nov 7, 2014
    Messages:
    517
    Resources:
    4
    Spells:
    1
    JASS:
    3
    Resources:
    4
    If the point of preloading is to avoid the "first time creation lag" and if only abilities are the cause of that lag why would you preload anything else beside abilities?

    PS: a decent library for wokring with sounds: SoundUtils
     
  9. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Okay, but I have not used sound in any instance before so I want to ask if this is correct:
    call StopSound(CreateSound(thistype.soundArg[i], false, false, false, 0, 0, null), true, false)


    It's because that needs more work while this is much shorter. And also this avoids redundant preloads of the same types which can be useful to our spells here which some of them probably have similar SFXs and dummies.

    Well forget my previous reason there I now have a better reason for this :D. It's still probably important because instead of enumerating the abilities that the unit has, its easier to just preload the unit instead in which case all their abilities will be preloaded with them. Although, this only applies to units with preplaced abilities, it can be useful specially when preloading heroes.
     
  10. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,054
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    True, Sound library would handle it, though it also can be part of a preload library.

    I think this is correct way, btw, AGD:
    Code (vJASS):

    //By Silvenon
    function PreloadSoundPath takes string path returns nothing
       local sound snd = CreateSound(path, false, false, false, 10, 10, "")
       call SetSoundVolume(snd, 0)
       call StartSound(snd)
       call KillSoundWhenDone(snd)
       set snd = null
    endfunction


    I tend to support Aniki's opinion now. Abilities needs to be preloaded. Destructables not, for sure.

    Abilities, yes.
    Sounds, yes.
    Effects, yes. (not only strings, but probably creation/destruction is good)

    Supporting range is also required in my opinion, so it offers something useful next to existing systems, in which it doesn't work correctly.
    Have you tested it with items and units that it does the same result as with preloadng the abilities seperatly? If it does, it also might be a handy function, and can exist.
     
  11. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Will be added

    I'm not 100% sure but based on my test it seem to.

    Btw, I don't know what part is bugged with the example you've given me regarding the AbilityPreload on wc3c.net. I can't also understand Aniki's method that you cited. If you can explain to me in a simple way how to do it I would be glad =).
     
  12. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,054
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Okay, guess some reviewer also can run some tests maybe. :)


    The problem is that in object editor, the created objects id's not necessarily are incremented by "+1" (decimal numbers ). So the loop in linked resource can possibly fail.

    Example where it will still work:

    Range: A001 - A005

    Abilties in decimal numbers:
    1093677105 ('A001')
    1093677106 ('A002')
    1093677107 ('A003')
    1093677108 ('A004')
    1093677109 ('A005')

    so exactly +1 always. Works fine here.

    Example where it won't work:

    Range: A006 - A00A

    Abilties in decimal numbers:
    1093677110 ('A006')
    1093677111 ('A007')
    1093677112 ('A008')
    1093677113 ('A009')
    1093677121 ('A00A')

    ... the jump from '9' to 'A' is not incremented by "+1" in decimal as you can see. But in Aniki's system the range works correctly.
     
  13. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Thanks for the explanation, I understand now. So would it be okay to use Aniki's system even though it's still in submission?
     
  14. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,054
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Maybe it's recommended to wait a bit, it's still pretty new and some things may change... but in general yes, because from dependability it seems good.
     
  15. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Updated to v1.1
    - Replaced the struct API
    Preload.$TYPE$()
    to
    Preload$TYPE$
    because using a reserved word as the struct name can be buggy
    - Added sounds to the preloadable types and removed destructables
    - Added the functionality for ranged preloading
    - Some code restructuring

    EDIT:

    Updated to v1.2
    - Preloading does not anymore happen in a single phase at the GUI Map Initialization
    - Resources are now preloaded at the instant you call the preload function

    These changes were made to avoid the possibility of hitting the op limit at the single preloading phase in case there are too many resources to be preloaded =)
     
    Last edited: Aug 24, 2016
  16. Trigger.edge

    Trigger.edge

    Joined:
    Jun 21, 2012
    Messages:
    424
    Resources:
    0
    Resources:
    0
    Without underestimating, but all these lag things are solved processing the map with Wc3SLKOpt.
     
  17. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,054
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Code (vJASS):

    //===================================================//
            call TriggerClearConditions(gg_trg_ResourcePreloader)
            call DestroyTrigger(gg_trg_ResourcePreloader)
            //===================================================//

    TriggerConditions will be destroyed automatically when the trigger is destroyed.

    Code (vJASS):

    local code c = function InitPreload
    set gg_trg_ResourcePreloader = CreateTrigger()
    call TriggerAddCondition(gg_trg_ResourcePreloader, Filter(c))
    set dummy = CreateUnit(Player(15), 'Hblm', 0, 0, 0)

    why a gg_trg variable is used for trigger? It is not declared, and was probably the default trigger the system used.
    Also, nothing important, but the extra line for the
    code c
    seems useless.

    But actually I don't really understand the usage of the trigger anyways. When/where does it run? Why is it needed? Why limitate timespan of preloading?

    I would personlay use table over all this:

    Code (vJASS):

    globals
            private integer unitCount        = 0
            private integer itemCount        = 0
            private integer abilityCount     = 0
            private integer effectCount      = 0
            private integer soundCount       = 0
            private boolean donePreloading   = false
            private integer array unitArg [MAX_PRELOAD_CAPACITY]
            private integer array itemArg [MAX_PRELOAD_CAPACITY]
            private integer array abilityArg [MAX_PRELOAD_CAPACITY]
            private string array effectArg [MAX_PRELOAD_CAPACITY]
            private string array soundArg [MAX_PRELOAD_CAPACITY]
        endglobals

    Not that you will preload probably more than 8190 objects, but just for simple lookup.

    @Trigger.edge yeh I guess preloading will become not important is you use certain tools, but me for example don't usually optimize nor widgetize my maps.
     
  18. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Hence why I said this.
    I did it so that function InitPreload doesn't have to return a boolean value

    A gg_trg trigger will automatically run at the GUI Map Initialization phase but only when the "Run at Map Initialization" box is checked.
    I limited the span of preloading because users probably don't want to preload resources after initialization anyway. But if they want to, they can just uncheck the "Run on Map Initialization" box (Will add a note about that).

    Okay, maybe I'll put an option if users want to preload more than 8190 objects (in which case the system will use table) or not (normal array for faster lookups).

    Okay
     
  19. IcemanBo

    IcemanBo

    Joined:
    Sep 6, 2013
    Messages:
    6,054
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    The usage of the gg_trg is bad for portability and for compability for a JASS submission. It could be changed into this:
    Code (vJASS):

    globals
        private trigger PreloadTrigger = CreateTrigger()
    endglobals


    But it should be removed then if the reason is only to limit the user to use the API after init. It makes no set the limitation to onInit.
    Most of preloading should be probably one onInit, yes but we should let it to the user. Maybe he wants also to preload certain things in a special gameplay mode or what ever.
    An other possibility is if the user doesn't init onInit, but with a 0-timer to avoid OP limit. Then it would not work anymore as well.
     
  20. AGD

    AGD

    JASS Reviewer

    Joined:
    Mar 29, 2016
    Messages:
    396
    Resources:
    13
    Spells:
    7
    Tutorials:
    1
    JASS:
    5
    Resources:
    13
    Yes I agree now =)