• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[vJASS] Missile

Level 19
Joined
Mar 18, 2012
Messages
1,716
Flatforms are shoes. What you mean are platforms and I don't think they are used
that widespread, with some exceptions.

I also assume that z axis is set correctly over platforms, aslong as they are not hidden
via ShowDestructable native.

Edit:

I made a ingame test and invisible platforms are detected just fine.
In wc3, when moving a unit over a terrain higher than the units fly height itself
that unit gets elevated until it's fly height equals to the cliff + unit default fly height.

If you declare onTerrain in your sruct you can detect this special case and
change the Missiles in a way you wish ( destroy, bounce, deflect, do nothing... )
static method onTerrain takes Missile this returns boolean
 
Level 11
Joined
Dec 3, 2011
Messages
366
Flatforms are shoes. What you mean are platforms and I don't think they are used
that widespread, with some exceptions.

I also assume that z axis is set correctly over platforms, aslong as they are not hidden
via ShowDestructable native.

Edit:

I made a ingame test and invisible platforms are detected just fine.
In wc3, when moving a unit over a terrain higher than the units fly height itself
that unit gets elevated until it's fly height equals to the cliff + unit default fly height.

If you declare onTerrain in your sruct you can detect this special case and
change the Missiles in a way you wish ( destroy, bounce, deflect, do nothing... )
static method onTerrain takes Missile this returns boolean

Ok :ogre_love:
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I tested it quite alot. In demo code, aswell inside a real map script ( however single player ).
Alone, it's impossible to test out all possible usage of a system, which is as big as Missile.
But as far as I can say, it's safe to use.

There is a short configuration part and you should quickly take your time to read
and setup everything correctly. This will decrease gernerated code and overhead, fitting your map.

I want to mention for experient users like you, that there is a more or less "hidden"
constant boolean debug static constant boolean DEBUG_MISSILE = true at the top of struct Missile,
which you may want to set to false. You still have the most important debug features,
but less important checks are now excluded via "static if".
This way you gain a nearly "non debug mode" performance of Missile.

Edit:
I still have some small changes in mind, like making Missile.create() the main creator function,
while giving createEx another meaning. Currently you always call Missile.createEx(), when calling other creator methods i.e Missile.create().
create() --> createEx() --> returns Missile instance.
I guess noone uses createEx at the moment.
 
Last edited:
Level 19
Joined
Aug 8, 2007
Messages
2,765
So I was experiencing a different frustrating bug with Dirac's system (placing multiple missiles on top of one unit only fires some of the missiles) so I decided to switch to this one.

From what I can see, there are no real noticeable differences between the two systems (besides bug fixes and optimizations). I ran it with my 10 or so standard spells and they all seemed to behave as they did with Dirac's. The spell I was having issues with functioned as I was expecting it should.

I ran one of my laggier spells with ~100 missles each with xyz-arc-curve and it seemed to lag about the same as it did before Dirac's.

One thing I did notice (this may just be my computer's startup time) but I saw that, in the cases where creating missiles did cause lag spikes, your system had slightly more severe lag spikes than did Dirac's (0.3sec compared to 0.2sec, negligible)



From the system standpoint, you have some copy and paste issues. You're not using the same compiler as the one used to comple Dirac's. When you try to compile, theres an error stating that one of your functions (StartPeriodic? I don't remember at this point) needs to return boolean. (EDIT: this system should be able to cross compile... its not hard to just return false)

Also, you have a slight transition mistake. In Dirac's system, the method "onFinish" has a return type of nothing, where yours has a return type of boolean. You only recycle the missile if onFinish returns true, which I feel is pointless.

JASS:
 else// What now??? infinity. I decide we do this math again!
                    call a.move(a.x + .0001, a.y, a.z)
                    return 
                endif

Wtf is this...

JASS:
        debug static constant boolean DEBUG_MISSILE = true

Why is debug enabled by default?



there are probably more issues than this but I was just skimming. After a few more people experiment with this I'd be willing to accept this.
 
Level 12
Joined
Feb 22, 2010
Messages
1,115
Debug is not enabled by default, it is enabled by default if you are testing in debug mode.See the "debug" keyword.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I ran one of my laggier spells with ~100 missles each with xyz-arc-curve and it seemed to lag about the same as it did before Dirac's.
In debug mode Missile suffers great performance loss, because of multiple extra function calls.
For example the data structure checks for correct evaluation, using the ErrorMessage library.
Without debug mode checked the perfomance is much better.

This also applies to the creator functions and explains the speed difference between Dirac's and this one.

In Debug mode off, the code of this Missile version is much more optimized and should run faster.

From the system standpoint, you have some copy and paste issues. You're not using the same compiler as the one used to comple Dirac's.
Yes this version just compiles with Vexorians JassHelper not with Cohadars. I can change this in the next update.
It's just two extra lines.

In Dirac's system, the method "onFinish" has a return type of nothing
That's not true. onFinish returns in both systems a boolean, which is logical, because
onFinish is called when the distance between origin and impact is covered.

What you mean is onRemove, which is called before the deallocation process.
Yes I changed from returns nothing to return true, but in both cases the Missile instance
gets deallocated. So the outcome is always the same.

Why return boolean

return true just allows to recycle a dummy delayed ( ~2 sec after ) .
This is a feature Dirac's Missile library doesn't cover at all.

Why we need this?
1) Most dummy recycling systems, protect their dummy units for 0.73 seconds from beeing re-used again.
That the maximum rotation time a dummy unit needs ( angle stored )
Attached special effects may still be on that dummy unit ( longer death animation time )

2) Removing a unit instantly will not display the death animation time of the attached special effect

else// What now??? infinity. I decide we do this math again!
call a.move(a.x + .0001, a.y, a.z)
return
endif
if a.x == b.x and a.y == b.y we have a distance of 0 between a and b.
slope = h/distance --> h/0 --> thread crash. I don't really know how to solve this case
in a good way. I decided to move change a.x value by 0.0001

I implemented a second debug boolean ( next to DEBUG_MODE ) to
allow more advanced debug checks. It should help beginners to erase
critical mistakes an experienced user might not make in the first place.
If you think it's not useful, then I will delete it.

Does my system also create that critical bug you had with Dirac's ( multiple Missiles on a unit )
If so, can I see a demo code to figure out the problem.

Thanks for your rreview and opinion :)
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
You should definitely update the TO post with a version that compiles on Cohadar's Jasshelper, because a lot of people - including me - still use the old Jasshelper, as the new one breaks backwards compatibility for a lot of stuff.

I had no problem manually fixing this, but it should definitely be there by default because you can't expect the average user to fix that. ;)
Most people just drop systems into the map and be done with it. That's why default settings should be as practical as possible.
I'd also recommend that the default settings for your system should be with destructable and Z collision set to false.
I expect more users not to use these features than to use them, so the default for those should be off.



So far, I didn't encounter any bugs. It has a lot of unused features for me (I basicly disabled all the optional toggles), but it works fine and has a comparable performance to the old system. At least in my use-cases I never experienced any lags so far.

Great job!

I think this can be approved in it's current state and Dirac's Missile should be graveyarded.


Go ahead removing createEx. Or simply make it private. I don't see a reason why anyone would ever use that. The most important creators are obviously create and createXYZ.


As a side note, I feel that 32 FPS for missiles is pretty excessive. This might make sense for big moving units, but glowing sfx heavy missiles and particles pretty much look smooth at 16 FPS already. I don't think 16 FPS should be the default, but I wanted to add that regardless; most people waste performance on this end.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I will update it asap to work with Cohadars JassHelper aswell.
I also agree on setting optional features to false by default.

I will remove createEx from the API for now ( by making it private ), but finally
I will give it another meaning like mentioned in my post above.

Thanks :)
 
Level 19
Joined
Aug 8, 2007
Messages
2,765
Are you sure you're recycling missiles properly? My memory jumps about 2mb every spell cast and I can't tell if its my spell or your system. I'll double check w/ Dirac's

E/ it appears to be on your end. I switched and it had similar behavior for the first ~5 casts than (im assuming the missile recycler kicked in) and it fluxuated by +-100kb every cast

2E/ im not entirely sure how effective your recycling system is...


I'm not sure how much of this information is even accurate so I'm going to cut it

3E/ test it yourself

JASS:
library BugTester initializer onInit requires Missile
    
    struct Arrow extends array
        implement Alloc
        implement MissileStruct
    
        public static method launchEx takes nothing returns nothing
            local Missile m
            set m             = Missile.create(0, 0, 50, 0, 1000, 50)
            set m.model       =  "Abilities\\Weapons\\Arrow\\ArrowMissile.mdl"
            set m.speed       = 50
            set m.collision   = 75
            set m.arc = 0
            call launch(m)
        endmethod
    
    endstruct
    
    private function onInit takes nothing returns nothing
        call TimerStart(CreateTimer(), 0.02, true, function Arrow.launchEx)
    endfunction
endlibrary

this snippet sees a rise of 800kb per task manager tick

e/ i got my wc3 from 300mb at start of game to 1gb by doing nothing and got bored so now im going to sleep. Ciao~
 
Last edited:
Level 19
Joined
Mar 18, 2012
Messages
1,716
Edit:
Your snippet does not use the delayed recycling feature. To enable it for your struct, you have to declare onRemove and return true.
So I assume, that dummy units are simply don't get removed/recycled.

Old:
My memory jumps about 2mb every spell cast and I can't tell if its my spell or your system. I'll double check

Interesting. Can you tell me if you have an external snippet for dummy re-using ( Only these are supported: Dummy, MissileRecycler, xe )
or are dummy units created directly via Missile library?

If you use one of the three mentioned above, you have to ensure that the dummy unit id
in the Missile configuration part is equal to the one of your Dummy units.

Otherwise you are not matching this important condition:
JASS:
            if (GetUnitTypeId(dummy) == Missile_DUMMY_UNIT_ID) then
                static if LIBRARY_MissileRecycler then
                    call RecycleMissile(dummy)
               
                elseif Dummy.create.exists and LIBRARY_Dummy then
                    call Dummy[dummy].destroy()
                   
                elseif LIBRARY_xedummy and xedummy.release.exists then
                    call xedummy.release(dummy)
                   
                else
                    call RemoveUnit(dummy)
                endif
            endif

I'm gonna run your demo code and hopefully find the problem. Thanks for pointing out :)

Are you sure you're recycling missiles properly?
Yes it should be correct. I will check everything again.

Edit: I ran your script for ~10 min and didn't see any significant changes in the task manager.

2E/ im not entirely sure how effective your recycling system is...
Hard to say, which is best. At first I just used ExecuteFunc and TriggerSleepAction, then I switched to the stack + timer.
 
Last edited:
Level 19
Joined
Aug 8, 2007
Messages
2,765
In my map I have the following-

BoundSentinel
MissileRecycler
GroupUtils
UnitIndexer

Your snippet does not use the delayed recycling feature. To enable it for your struct, you have to declare onRemove and return true.
So I assume, that dummy units are simply don't get removed/recycled.

No..? If the delayed recycling feature is off than the missiles should simply get recycled immediately

2e/ If your using an external missile system than you should rely on it's properties for recycling >.> I hadn't had the dummy unit id set. I'll see what the kb's are at now.

3e/ When i fixed the unit id, most of the mem leak went away but it still fluctuated a little going up and down (but gaining net gains going up, about 1mb every 10 sec or so)
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
No..? If the delayed recycling feature is off than the missiles should simply get recycled immediately
How it works:
- Enable delayed recycling in the globals --> compiler doesn't ignore the static if
- delcare onRemove and return true ( otherwise dummies get recycled instantly )
- onRemove has no impact on the deallocation process, it just decided the future of the dummy unit.

External resources can be used optionally, but the Missile configuration is very well
documentated and explains everything.

I guess the problem is located in the CreateUnit native. It's very intense compare to other natives.
 
Level 7
Joined
Oct 11, 2008
Messages
304
You can 'preload' some 'missiles', create it onInit or something, and just recycling the instance of the struct, them you prevent CreateUnit every time, maybe what? 100 unit? I don't know, maybe too much...

Edit: Or better, a optional module that you don't create unit dynamic, just reuse the system one, like I said, create 50-100 units for the system, and use them for missiles, 'recycling' it but don't removing it, if all the 50-100 units precreated is being used, then you createunit dynamic and remove it when that instance finish.
 
Level 19
Joined
Aug 8, 2007
Messages
2,765
How it works:
- Enable delayed recycling in the globals --> compiler doesn't ignore the static if
- delcare onRemove and return true ( otherwise dummies get recycled instantly )
- onRemove has no impact on the deallocation process, it just decided the future of the dummy unit.

External resources can be used optionally, but the Missile configuration is very well
documentated and explains everything.

I guess the problem is located in the CreateUnit native. It's very intense compare to other natives.

You can 'preload' some 'missiles', create it onInit or something, and just recycling the instance of the struct, them you prevent CreateUnit every time, maybe what? 100 unit? I don't know, maybe too much...

Edit: Or better, a optional module that you don't create unit dynamic, just reuse the system one, like I said, create 50-100 units for the system, and use them for missiles, 'recycling' it but don't removing it, if all the 50-100 units precreated is being used, then you createunit dynamic and remove it when that instance finish.

I was using MissileRecycler.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I gotta say I'm not a huge fan of the internal code structure. It's extremely convoluted and cryptic; the variable names don't speak for itself and the overall style just isn't my taste...

Not that I care as long as the system works, but due to Arhowk's tests I was checking out the code to find what could be the issue here, but I absolutely could not read it...

All the optional stuff makes this hard to maintain, I think. Especially the delayed missile recycler. I'm not sure if the pros outweight the cons here.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I understand that optional libraries hurt read-ability and so does debug only code.
Also the linked list, which is not implemented from an external public module.

For me the code very read-able, just because I wrote it. :p

I still think, that optional requirements are a pro for Missile, because I only choose such
which Missile really benefits from.

Towards the delayed recycling process:

There were 3 reasons for writting the recylcling tool.
1.) Library Dummy moves dummy units to the edge of the map --> Can't see death animation. ( bad )
2.) Library MissileRecycler protects dummies from beeing re-used for .73 seconds --> special effect is still on the unit. ( very bad ). Applies to library Dummy aswell.
3.) RemoveUnit native doesn't display the effect death animation at all. ( acceptable )

I agree with Zwiebelchen, that the easiest way would be to destroy the desired effect ( again ), once the Missile is removed.
That's why the recycling delayed is just an optional toogle.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I know why it's there, it's just that it's not as useful as you think. Creating and destroying another effect creates so little overhead that it is easily overshadowed by a timer running in the background that recycles the missile delayed. ;)

The only reason why you would actually need a delayed destruction is for those rare sfx models that don't display correctly when destroyed immediately.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I benefit alot from the delayed recycling as I'm currently using Nestharus's Dummy.
So I guess it could turn out be useful for others aswell.
It's an optional toggle anyway.
I can outsource the code to an external library if that is more read-able.

I made a few changes in the API ( not uploaded yet, I'm still testing ).
  • Reduced API down to create, createXYZ, createEx
  • create is now the core creator function --> no longer calls createEx after
Creators:
  • static method create takes x, y, z, angle, distance, endZ returns Missile
    --> Basic creator used in most cases.
    .
  • static method createXYZ takes x, y, z, endY, endY, endZ returns Missile
    --> converts arguements to fit for Missile.create()
    .
  • static method createEx takes unit, angle, distance, endZ returns Missile
    --> stand-alone creator
    --> allow to launch any unit i.e heroes ( benefits: jump, knockback, .... )
    --> launched unit does not get removed/recycled, if UnitTypeId does not match MISSILE_DUMMY_ID
    --> if unitId == DUMMY_ID it is treated like a normal allocated Missile instance --> removed in the end
    Remember Missile uses a parabola function:
    1.) jumping is place is not supported.
    2.) settting speed for small distances is difficult ( needs flytime based calculation )

Missing:

  • Proper usage of turn.

I think this is already ready for approval.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Btw, there is an issue with the MissileRecycler sub-lib of Nestharus' Dummy:
It seems that it has problems allocating dummy units on the first uses of abilities that make use of a lot of missiles.

After I changed to the standalone MissileRecycler, I got it working. Before, I used only "Dummy" and the included MissileRecycler in Dummy, and frequently had dummies not showing correctly.
 

Kazeon

Hosted Project: EC
Level 33
Joined
Oct 12, 2011
Messages
3,449
Btw, there is an issue with the MissileRecycler sub-lib of Nestharus' Dummy:
It seems that it has problems allocating dummy units on the first uses of abilities that make use of a lot of missiles.

After I changed to the standalone MissileRecycler, I got it working. Before, I used only "Dummy" and the included MissileRecycler in Dummy, and frequently had dummies not showing correctly.

Nestharus' Dummy is bugged, same thing happens in my frozen spellpack as well when I was trying to import it somewhere else, forcing me to modify it to remove dummy completely (since I always had missile facing problem with these kind of dummy recycler). What's so important about Dummy libraries anyway? It can save times alright by avoiding dummy re-creations. But still, having more preloaded dummies means more memory is costed, it's not exactly win imo, and just causing terrible bugs a lot of times. Suggestion: remove it.
 
Level 19
Joined
Aug 8, 2007
Messages
2,765
Nestharus' Dummy is bugged, same thing happens in my frozen spellpack as well when I was trying to import it somewhere else, forcing me to modify it to remove dummy completely (since I always had missile facing problem with these kind of dummy recycler). What's so important about Dummy libraries anyway? It can save times alright by avoiding dummy re-creations. But still, having more preloaded dummies means more memory is costed, it's not exactly win imo, and just causing terrible bugs a lot of times. Suggestion: remove it.

CreateUnit() leaks.

(this is a rather simplified version of the issue; CreateUnit() itself doesnt leak but the consequences of repeated calling are similar to that of repeated leaking)
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
@Zwiebelchen:
I have to check the combination of the two resources. I'm used Dummy without MissileRecycler and I don't experience that problem.
However when it comes to using a spell with uses a lot of dummies, I always
had spikes on the first cast. I always blamed UnitIndexer and the onIndex event.
( My opinion about UnitIndexer usage in Dummy at the end of the comment )

@Quilnez
Nestharus' Dummy is bugged, same thing happens in my frozen spellpack as well when I was trying to import it somewhere else, forcing me to modify it to remove dummy completely
I will look into Dummy.

It can save times alright by avoiding dummy re-creations. But still, having more preloaded dummies means more memory is costed, it's not exactly win imo, and just causing terrible bugs a lot of times. Suggestion: remove it.
Dummy libraries have proven useful, as the CreateUnit() native is a very heavy one.
I can't say anything about small leaks when using CreateUnit, but different
sources say they exist.

@Arhowk.
Yeah I head about the CreateUnit() problem, very often to be honest.
Can't say anything about it, except that it's not so heavy problem since the latest wc3 patch.

About UnitIndexer & Dummy.
I think it is a very bad idea to use/enable your UnitIndexer when creating Dummy units.
For the sake of squeezing Dummy into arrays, you have a lot of overhead and eventual
extra memory usage. Let's quickly combine two of Nestharus resources. Dummy & DDS.
For each unit allocated DDS creates triggers and ( iirc ) runs a bubble sort within TriggerRefresh,
Dummy units also go through this process, as there is no filter integrated.
Ergo every dummy unit uses a lot of extra memory and requires additional
computation time. However dummy units never deal or recieve damage.

Conclusion:
I will look into Dummy and eventually remove it from the optional requirements.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
@Zwiebelchen:
I have to check the combination of the two resources. I'm used Dummy without MissileRecycler and I don't experience that problem.
However when it comes to using a spell with uses a lot of dummies, I always
had spikes on the first cast. I always blamed UnitIndexer and the onIndex event.
( My opinion about UnitIndexer usage in Dummy at the end of the comment )

@Quilnez
I will look into Dummy.


Dummy libraries have proven useful, as the CreateUnit() native is a very heavy one.
I can't say anything about small leaks when using CreateUnit, but different
sources say they exist.

@Arhowk.
Yeah I head about the CreateUnit() problem, very often to be honest.
Can't say anything about it, except that it's not so heavy problem since the latest wc3 patch.

About UnitIndexer & Dummy.
I think it is a very bad idea to use/enable your UnitIndexer when creating Dummy units.
For the sake of squeezing Dummy into arrays, you have a lot of overhead and eventual
extra memory usage. Let's quickly combine two of Nestharus resources. Dummy & DDS.
For each unit allocated DDS creates triggers and ( iirc ) runs a bubble sort within TriggerRefresh,
Dummy units also go through this process, as there is no filter integrated.
Ergo every dummy unit uses a lot of extra memory and requires additional
computation time. However dummy units never deal or recieve damage.

Conclusion:
I will look into Dummy and eventually remove it from the optional requirements.
It's not dummy itself that is broken, it's just that the old MissileRecycler that was part of dummy seems to bug if you use it to fetch too many missiles with the same general direction at the same time (my cone-type spell only shot 1 or 2 missiles on the first cast instead of 10 as intended; after several more casts, it worked).
The standalone MissileRecycler works (http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/). Just remove dummy from the requirements and use the standalone MissileRecycler only.

And imho, I think MissileRecycler should be specifically mentioned in the documentation to be absolutely recommended for this. You definitely do not want the user to heavily use this resource without MissileRecycler. Actually, I go a step further and say MissileRecycler should be mandatory, even if I generally dislike forced requirements. In this case, it makes a lot of sense.


Btw, the trigger refresh thing is the reason why I don't use DDS. I only need a damage system to block and detect all damage. Everything else is customized by me. DDS creates way too much overhead for my taste.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
And imho, I think MissileRecycler should be specifically mentioned in the documentation to be absolutely recommended for this. You definitely do not want the user to heavily use this resource without MissileRecycler. Actually, I go a step further and say MissileRecycler should be mandatory, even if I generally dislike forced requirements. In this case, it makes a lot of sense.
I will point out that MissileRecycler is best to use and very recommended. I will not make it mandatory.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
I will point out that MissileRecycler is best to use and very recommended. I will not make it mandatory.
Fair enough. Depends on the use-case scenario anyway, as always. After all, some people might possibly import missile as a requirement for a downloaded spell, not because they actually use it themselves.
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
Nice to see Missile approved. I want to metion once again, that MissileRecycler ( Bribe )
is the optimal dummy recycling snippet for Missile. Bribe implemented a dummy protection
timeout, so effects can now be displayed without flaw.
In case you don't want to display the of the model on your Missile, simply move
the dummy unit out of screen in onRemove

I'm gonna update the first page soon ( better demo )
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Can you try running it in debug mode and see if any errors are being spit out? I could also take a look at the map for you.
Will do. The problem is that I changed absolutely nothing about the code. I just updated both Missile and MissileRecycler.
I'll revert one of both systems back to the old version and see which one is to blame.
 
Level 6
Joined
Jun 20, 2011
Messages
249
this is insane
it's been over 4 years and there's still a lot of love for these kind of libraries
i totally forgot about this
kinda sad to see my old lib graveyarded, but before that it achieved 11k views (crazy)
good work!


I forgot everything about jass too lol
 
Level 19
Joined
Mar 18, 2012
Messages
1,716
I edited the first post. Now I'm going to rule out the problem of v1.4.
Expect an update in the next days.

this is insane
it's been over 4 years and there's still a lot of love for these kind of libraries
i totally forgot about this
kinda sad to see my old lib graveyarded, but before that it achieved 11k views (crazy)
good work!
I wrote this library based on yours. It was an inspiring example of using a struct id as an array index.
I will add your name to the credit list. if it is not already there. :)
 
Top