• 🏆 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!

[Snippet] Alloc

Level 5
Joined
Dec 4, 2006
Messages
110
Alloc

Version 1.09

Requirements
- JASS NewGen

JASS:
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ Alloc ~~ By Sevion ~~ Version 1.09 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
//  What is Alloc?
//         - Alloc implements an intuitive allocation method for array structs
//
//    =Pros=
//         - Efficient.
//         - Simple.
//         - Less overhead than regular structs.
//
//    =Cons=
//         - Must use array structs (hardly a con).
//         - Must manually call OnDestroy.
//         - Must use Delegates for inheritance.
//         - No default values for variables (use onInit instead).
//         - No array members (use another Alloc struct as a linked list or type declaration).
//
//    Methods:
//         - struct.allocate()
//         - struct.deallocate()
//
//           These methods are used just as they should be used in regular structs.
//
//    Modules:
//         - Alloc
//           Implements the most basic form of Alloc. Includes only create and destroy
//           methods.
//
//  Details:
//         - Less overhead than regular structs
//
//         - Use array structs when using Alloc. Put the implement at the top of the struct.
//
//         - Alloc operates almost exactly the same as default structs in debug mode with the exception of onDestroy.
//
//  How to import:
//         - Create a trigger named Alloc.
//         - Convert it to custom text and replace the whole trigger text with this.
//
//  Thanks:
//         - Nestharus for the method of allocation and suggestions on further merging.
//         - Bribe for suggestions like the static if and method names.
//         - PurgeandFire111 for some suggestions like the merging of Alloc and AllocX as well as OnDestroy stuff.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library Alloc    
    module Alloc
        private static integer instanceCount = 0
        private thistype recycle
    
        static method allocate takes nothing returns thistype
            local thistype this
    
            if (thistype(0).recycle == 0) then
                debug if (instanceCount == 8190) then
                    debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Alloc ERROR: Attempted to allocate too many instances!")
                    debug return 0
                debug endif
                set instanceCount = instanceCount + 1
                set this = instanceCount
            else
                set this = thistype(0).recycle
                set thistype(0).recycle = thistype(0).recycle.recycle
            endif

            debug set this.recycle = -1
    
            return this
        endmethod
    
        method deallocate takes nothing returns nothing
            debug if (this.recycle != -1) then
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Alloc ERROR: Attempted to deallocate an invalid instance at [" + I2S(this) + "]!")
                debug return
            debug endif

            set this.recycle = thistype(0).recycle
            set thistype(0).recycle = this
        endmethod
    endmodule
endlibrary

JASS:
scope test initializer dotest
    private struct teststruct extends array
        implement Alloc

        private string data

        public static method create takes nothing returns thistype
            //call BJDebugMsg("Created")
            return thistype.allocate()
        endmethod
        
        public method test takes nothing returns nothing
            call BJDebugMsg("Instance: " + I2S(this))
        endmethod

        public method destroy takes nothing returns nothing
            call BJDebugMsg("Destroying: " + I2S(this))
            call this.deallocate()
        endmethod
    endstruct
    
    private function dotest takes nothing returns nothing
        local teststruct a = teststruct.create()
        local teststruct b = teststruct.create()
        local teststruct c = teststruct.create()
        local teststruct d = teststruct.create()
        
        call a.test()
        call b.test()
        call c.test()
        call d.test()
        
        call a.destroy()
        call b.destroy()
        call c.destroy()
        call d.destroy()
        
        set a = teststruct.create()
        set b = teststruct.create()
        set c = teststruct.create()
        set d = teststruct.create()
        
        call a.test()
        call b.test()
        call c.test()
        call d.test()
    endfunction
endscope

Should be faster than all other structs. This should become the new struct standard :thumbs_up:

Updates
- Version 1.09: Moved the overflow check for some efficiency.
- Version 1.08: More debug fixes.
- Version 1.07: Fixed some debug things.
- Version 1.06: Reverted debug changes.
- Version 1.05: Fixed some debug things.
- Version 1.04: Removed OnDestroy.
- Version 1.03: Removed a boolean and made things a bit more efficient.
- Version 1.02: Minor updates. Removed AllocX and AllocXS.
- Version 1.01: Minor updates and renamed functions.
- Version 1.00: Release.
 
Last edited by a moderator:
OnDestroy must be placed above the implementation. You should note that. .exists() searches upward, not downward, so it ends commenting out the lines because it returns nonexistent.

Example:
JASS:
    struct Fun extends array
        method OnDestroy takes nothing returns nothing
            call BJDebugMsg("Destroyed!")
        endmethod   
        implement AllocX
        static method create takes nothing returns thistype
            call BJDebugMsg("Created")
            return thistype.allocate()
        endmethod
        method destroy takes nothing returns nothing
            call this.deallocate()
        endmethod
    endstruct

Works nicely.

Good job though, nice idea to put it in a module. :thumbs_up:

EDIT: Now that I think about it, you can probably merge Alloc and AllocX because if the OnDestroy method doesn't exist, the line will be commented out. (so it will end up with the same efficiency, just some extra comments)

EDIT2: Also, your example has syntax errors. lol. ;P
 
Level 22
Joined
Sep 24, 2005
Messages
4,821
Double frees are still possible, while AllocX can catch the last recycled id double free, it can't catch those behind the recycled id.

JASS:
function Test takes nothing returns nothing
    local thistype a=thistype.allocate()
    local thistype b=thistype.allocate()
    
    call a.deallocate()
    call b.deallocate()
    call a.deallocate() /* <--  double free, since this
                                would be a valid id to
                                recycle with the current
                                checking method. */
endfunction
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
Couple of notes-

JASS:
        private static integer instanceCount = 0
        private static thistype recycle = 0
        private thistype recycleNext

can just be
JASS:
        private static integer instanceCount = 0
        private thistype recycleNext

use index of 0 in recycleNext as the head. thistype[0].recycleNext would be the first recycled instance. It might be a tad bit of a slower operation (reading an array rather than a value), but it'll help keep global variable count lower, which is more important ;P.

You need a debug allocated array (like isAllocated) so that you can check for double free. As chobibo correctly pointed out, your current method can't really detect double free ;P.

You only need one module**, get rid of the other 2.

Your onDestroy isn't correct, it needs to be a trigger and you need like an extension method. onDestroy is there for polymorphism. Imagine you have an object B that extends A and you store it as A. When calling A.destroy(), it should properly destroy it (meaning that first B is destroyed, then A). This is the reason for the trigger evaluations with onDestroy.

All of your protection stuff should be debug only
->if (instanceCount == 8191) then
 
Level 5
Joined
Dec 4, 2006
Messages
110
Alright, fixed up double-free stuff. I was thinking previously about double-free stuff. Already knew about the previous problems :p I wasn't sure if an array was the way to go. Maybe there was a better method. I used an integer array. Would using a boolean array be faster or are integers faster than arrays?

Merged all three modules.

Not sure about what you mean with the OnDestroy stuff.

Made all debug stuff... debug.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
This needs to be in debug

set isAllocated[this] = 1

and it needs to be a boolean.

This should be a trigger evaluation
->call this.OnDestroy()

Remember that onDestroy is evaluated in a trigger, and I explained the reasoning behind this. Extending method would generate a trigger for an extended struct by adding all the boolean expressions of every struct along that chain starting at the furthest derived struct and going all the way down to the root.
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
Nestharus said:
use index of 0 in recycleNext as the head. thistype[0].recycleNext would be the first recycled instance. It might be a tad bit of a slower operation (reading an array rather than a value), but it'll help keep global variable count lower, which is more important ;P.

Why ?

I would say the speed and the global variable count are both equally negligible here.
I mean i don't see why it's more important to keep the global variable count lower, would you precise your point ?
 
Level 5
Joined
Dec 4, 2006
Messages
110
I have heard that more globals on the map decreases speed. And it makes sense if globals are taking up more RAM, then there's less RAM to go around for other things.

Either way, I'm not changing it back to what it was before unless there's a really good reason to :p It took me a while to figure that part out.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
I have a big philosophy: "doing more with less", which supports using only two globals per module instead of 3.

Maybe I'll make a new JASS parser to circumvent the fact that wc3c.net simply doesn't care about anything these days. Thing is... what to use to inject the data? Jass Shop Pro, Jass NewGen Pack, something else?
 
Level 17
Joined
Apr 27, 2008
Messages
2,455
JassNewGenPack is a very wonderful and complete ide when you have wc3 installed and run on a windows OS.
But JassCraft (or maybe jass shop pro) is cooler when you only want to make some scripts, and/or doesn't run on windows (works fine on ubuntu with wine at least)

Btw isn't jasshelper able to use other parsers ?
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
What he's asking for is impossible.

He's asking for delegates that point to parent structs, but remember that delegates have to be assigned in the create method and destroyed in the destroy method, so you can't do it with a textmacro ; ).

The instance of the struct is ParentStruct1.create() and the first delegate (parentStruct1) is set to the instance. The rest are just
set parentStructX = ParentStructX.create()
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
http://www.thehelper.net/forums/showthread.php/163378-Boomerang-and-Orbital?p=1343087#post1343087

If struct B : A

A a = B.create()
call a.destroy()

w/o the trigger stuff, b.destroy would never be called. It should do-
b.destroy()
a.destroy()


This is why vjass has trigger evaluations whenever a struct is extended by another struct >.<. Really, this should always occur with destroy if supporting polymorphism... ; P

However, polymorphism is rarely needed =). I suggest that you make onDestroy support polymorphism and destroy not =D. onDestroy should just call destroy ^)^.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
As said on TheHelper.net:

With the null instance protection and the 8190 protection enabled, your module is now just as slow as Vexorian's structs, stripping away the advantage entirely. Safety should only be optional, not mandatory. That's the problem with wc3c.net, they really love their verbosity.

Really, this is 99.99% useless now with that enabled.

Please don't take the strange robustness of wc3c.net to the rest of the table. For now, this goes back into the Submissions.

What I did with the Table library on wc3c.net was to add a constant boolean SAFETY to appease their weirdness while also making it optional so that speed freaks can get what they want.

The problem is that Alloc should not even have that configuration, because if the safety is on then there's really no point in using this.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
set this.recycle = -1

^ That should be debugged out

JASS:
            debug if (this == 0) then
                debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Alloc ERROR: Attempted to deallocate a null entry!")
                debug return
            debug endif

^ You don't need this block because you already check if the instance's recycle is -1 (which 0 will never be) so if it's not -1 you can simply throw "TypeError: Tried to destroy invalid instance: " + I2S(this)
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
instance count check should be debug only...

It's still in the wrong spot, it should only check if it it's increasing the instance counter.

You made it worse than it was before =).

JASS:
if (r[0] == 0) then
    debug if (ic == 8191) then
        debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, "INSTANCE OVERFLOW")
        debug return 0
    debug else
        set this = ic+1
        set ic = this
    debug endif
else
    set this = r[0]
    set r[0] = r[this]
endif

return this
 
Top