[Snippet] Resource Preloader

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
RESOURCE PRELOADER v1.5.0


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
JASS:
library ResourcePreloader /* v1.5.0 https://www.hiveworkshop.com/threads/287358/


    */uses /*

    */optional BJObjectId   /*   http://www.hiveworkshop.com/threads/287128/
    */optional Table        /*   http://www.hiveworkshop.com/threads/188084/
    */optional UnitRecycler /*   http://www.hiveworkshop.com/threads/286701/


    *///! novjass

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

    */
    |-----|
    | 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

    /*
    *   Configuration
    */
    globals
        /*
        *   Preload dummy unit type id
        */
        public integer PRELOAD_UNIT_TYPE_ID                     = 'uloc'
        /*
        *   Owner of the preload dummy
        */
        public player PRELOAD_UNIT_OWNER                        = Player(PLAYER_NEUTRAL_PASSIVE)
        /*
        *   Dummy unit's y-coordinate will be positioned at (Max Y of WorldBounds) + this value
        */
        public real PRELOAD_UNIT_Y_BOUNDS_EXTENSION             = 0.00
    endglobals

    /*========================================================================================================*/
    /*            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 forward = start < end
        loop
            call Preload$NAME$(start)
            exitwhen start == end
            if forward 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(PLAYER_NEUTRAL_PASSIVE), id, 0, 0, 270))
        else
            call RemoveUnit(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 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(AddSpecialEffect(path, GetUnitX(S.dummy), GetUnitY(S.dummy)))
    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(PRELOAD_UNIT_OWNER, PRELOAD_UNIT_TYPE_ID, 0, 0, 0)
            call SetUnitY(dummy, GetRectMaxY(world) + PRELOAD_UNIT_Y_BOUNDS_EXTENSION)
            call UnitAddAbility(dummy, 'AInv')
            call UnitAddAbility(dummy, 'Avul')
            call UnitRemoveAbility(dummy, 'Amov')
            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
JASS:
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


v1.5.0
- Fixed a bug that can cause game crash in patch 1.31.1.
- Moved the important constant variables on top
- Preloading special effects now also no longer plays a sound

v1.4e
- Fixed a bug regarding the preload dummy not being hidden beyond map boundaries

v1.4d
- Changed Player(15) to Player(PLAYER_NEUTRAL_PASSIVE) in order to be compatible with newer patches

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:
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:
Level 13
Joined
Nov 7, 2014
Messages
570
This snippet allows to preload your map resources. When first time a resource is created in your map, it lags a bit.

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.
 
Level 13
Joined
Jul 1, 2008
Messages
1,314
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
JASS:
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 ...
 
Level 13
Joined
Nov 7, 2014
Messages
570
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.

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
 

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
I suggest including sounds too though
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)

Other than that, I would like to know why simply not use the actual preloading itself such as
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.

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?
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.
 
True, Sound library would handle it, though it also can be part of a preload library.

I think this is correct way, btw, AGD:
JASS:
//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.
 

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
I think this is correct way, btw, AGD:
Will be added

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.
I'm not 100% sure but based on my test it seem to.

Supporting range is also required in my opinion, so it offers something useful next to existing systems, in which it doesn't work correctly.
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 =).
 
I'm not 100% sure but based on my test it seem to.
Okay, guess some reviewer also can run some tests maybe. :)


Btw, I don't know what part is bugged with the example you've given me regarding the AbilityPreload on wc3c.net.

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.
 

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
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:
JASS:
//===================================================//
        call TriggerClearConditions(gg_trg_ResourcePreloader)
        call DestroyTrigger(gg_trg_ResourcePreloader)
        //===================================================//
TriggerConditions will be destroyed automatically when the trigger is destroyed.

JASS:
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:

JASS:
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.
 

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
why a gg_trg variable is used for trigger? It is not declared, and was probably the default trigger the system used.
Hence why I said this.
Create an empty script in your map exactly entitled as "ResourcePreloader" [Needed in order to work correctly]

Also, nothing important, but the extra line for the
code c
seems useless.
I did it so that function InitPreload doesn't have to return a boolean value

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?
A gg_trg trigger will automatically run at the GUI Map Initialization phase but only when the "Run at Map Initialization" box is checked.
- Check the "Run on Map Initialization" box
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).

I would personlay use table over all this:
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).

TriggerConditions will be destroyed automatically when the trigger is destroyed.
Okay
 
The usage of the gg_trg is bad for portability and for compability for a JASS submission. It could be changed into this:
JASS:
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.
 

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
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.
Yes I agree now =)
 

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
Updated to version 1.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
 

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
UPDATED to 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
 
Last edited:

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
UPDATED to 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
 
Last edited:
Level 1
Joined
Apr 8, 2020
Messages
111
This resource is crashing the game during loading time on patch 1.31.1.

The following line is the culprit:call SetUnitY(dummy, GetRectMaxY(world) + 1000).
Removing the + 1000 fixed the issue.

And I am not sure this resource is working for me. I still get a lag spike whenever I drop any item for the first time.
I added the item via UnitAddItemById in a module initializer, if this information has any bearing on my issue.

For preloading, I use this rather simple code:
JASS:
scope Test initializer onInit

    private function onInit takes nothing returns nothing
        call PreloadItem('I049')
        call PreloadAbility('A041')
    endfunction
endscope
 
Last edited:
Level 19
Joined
Aug 13, 2013
Messages
1,680
^ This resource works. The crash is depending on a map for sure.
Although, the lag spike you're having is hard to determine if you're using that last legacy patch
(as it's notorious for having a very poor performance compared to other legacy patches)
 

AGD

AGD

Level 14
Joined
Mar 29, 2016
Messages
678
This resource is crashing the game during loading time on patch 1.31.1.

The following line is the culprit:call SetUnitY(dummy, GetRectMaxY(world) + 1000).
Removing the + 1000 fixed the issue.

And I am not sure this resource is working for me. I still get a lag spike whenever I drop any item for the first time.
I added the item via UnitAddItemById in a module initializer, if this information has any bearing on my issue.

For preloading, I use this rather simple code:
JASS:
scope Test initializer onInit

    private function onInit takes nothing returns nothing
        call PreloadItem('I049')
        call PreloadAbility('A041')
    endfunction
endscope
Sorry for the late response. Yes it indeed crashes the game (in 1.31.1). I've put it in a constant at the top in the new update (see first post). Also, I recommend to use 'uloc' as the preload dummy type (now also configurable).

I still get a lag spike whenever I drop any item for the first time.
I added the item via UnitAddItemById in a module initializer, if this information has any bearing on my issue.
I'm not sure that that way of adding item to unit has any bearing on the problem since you mentioned that the lag occurs for any items dropped.

Your way of preloading should also be fine.
 
Top