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

Structfollowers

Level 26
Joined
Aug 18, 2009
Messages
4,097
Exposition
I have searched for new ways to write my vJass codes. Particularly, the semi-optimal encapsulation always annoyed me. You cannot nest libraries, you may do so with scopes but equal-labelled identifiers of parents are in the way nor will be overshadowed, same when extending structs. Private implementing of modules was not introduced, so modules are actually only nestable textmacros but without parameters, quasi like import. Well, now it does not seem like vJass is still going on with development, so I do something on my own.

Structs are fairly good since they allow a short notation on calling through their instances. When you extend structs, the members of the parent will simply get transfered, resulting in you being unable to redeclare its identifiers. Even if you could overshadow them, you would not be able to access them anymore. A precise indicator is missing, folders in structs. It's a simple strategy: You jump from one struct into another by using a local identifier in the origin struct that redirects to the target struct by returning an instance of this target struct.

Read "Realization" to see single steps of how to acquire this target and which problems may appear. It finalizes in my current solution.
Realization
That would be my basic approach how I wanted it:
JASS:
scope SurpriseBag
    public struct B
        static method Goal takes nothing returns nothing
        endmethod
    endstruct
endscope

struct A
    static SurpriseBag_B B
endstruct

Now you could write
JASS:
call A.B.Goal()

This works already for the static call. But it's harder to go via an instance of A because you would need to pass the id of this instance to struct B and cast again. This is because "B" shall be a folder of A, store information for the instance of A you enter B with and therefore need to identify to which A instance it belongs.

This means it's necessary to assign each B instance to its parallel A instance.

JASS:
scope SurpriseBag
    public struct B
        method Goal takes nothing returns nothing
        endmethod

        static method StaticGoal takes nothing returns nothing
        endmethod
    endstruct
endscope

struct A
    SurpriseBag_B B

    static method create takes nothing returns thistype
        local thistype this = thistype.allocate()

        set this.B = this

        return this
    endmethod
endstruct

Call:
JASS:
call <InstanceOfA>.B.Goal()

But it ain't that easy. It would only work this way if you allocated the instance, which isn't a must since you would want to have deeper nestings without necessarily allocating.

Unit.Position.X.Get() for example, you would create a new Unit but not a new Position, this should just work as a folder that contains the inner part X. You also would not want to write this create-method with modifyings everytime, which would be error-prone anyway.

So my idea: Allocate each instance onInit but immediately destroy them again. Their members will not be reset. I also use some textmacros here to finalize the shape and BaseStruct/Folder/Struct are meant for avoiding name conflicts as you might have a inner struct Position for both Units and Items for example.

Current version
JASS:
//! textmacro Folder takes name
    scope Folder$name$
//! endtextmacro

//! textmacro LinkToStruct takes folder, name
    Folder$folder$_Struct$name$ $name$
//! endtextmacro

//! textmacro Struct takes name
    public struct Struct$name$
//! endtextmacro

//! textmacro BaseStruct takes name
    struct $name$
        static thistype THIS = NULL
//! endtextmacro

globals
    trigger InitLinks_DUMMY_TRIGGER = CreateTrigger()
    integer InitLinks_ITERATION
    integer InitLinks_THREAD_BREAK_COUNTER
    constant integer InitLinks_THREAD_BREAK_LIMIT = 1500
endglobals

globals
    constant boolean DEBUG = true
    constant integer STRUCT_EMPTY = 0
    constant integer STRUCT_MAX = 8190
    constant integer STRUCT_MIN = 1
endglobals

module InitStructLinks
    private static method InitStructLinks2 takes nothing returns nothing
        local integer iteration = InitLinks_ITERATION

        loop
            call thistype(iteration).deallocate()

            set iteration = iteration - 1

            exitwhen (iteration < STRUCT_MIN)

            set InitLinks_THREAD_BREAK_COUNTER = InitLinks_THREAD_BREAK_COUNTER + 1

            exitwhen (InitLinks_THREAD_BREAK_COUNTER > InitLinks_THREAD_BREAK_LIMIT)
        endloop

        if (iteration > STRUCT_EMPTY) then
            set InitLinks_ITERATION = iteration
            set InitLinks_THREAD_BREAK_COUNTER = 0

            call TriggerEvaluate(InitLinks_DUMMY_TRIGGER)
        else
            static if (DEBUG) then
                set InitLinks_ITERATION = STRUCT_EMPTY
            endif
        endif
    endmethod

    private static method InitStructLinks takes nothing returns nothing
        local integer iteration = InitLinks_ITERATION

        loop
            call thistype.allocate()

            set iteration = iteration - 1

            exitwhen (iteration < STRUCT_MIN)

            set InitLinks_THREAD_BREAK_COUNTER = InitLinks_THREAD_BREAK_COUNTER + 1

            exitwhen (InitLinks_THREAD_BREAK_COUNTER > InitLinks_THREAD_BREAK_LIMIT)
        endloop

        if (iteration > STRUCT_EMPTY) then
            set InitLinks_ITERATION = iteration
            set InitLinks_THREAD_BREAK_COUNTER = 0

            call TriggerEvaluate(InitLinks_DUMMY_TRIGGER)
        else
            static if (DEBUG) then
                set InitLinks_ITERATION = STRUCT_EMPTY
            endif
        endif
    endmethod

    private static method onInit takes nothing returns nothing
        local boolexpr condition = Condition(function thistype.InitStructLinks)

        set InitLinks_ITERATION = STRUCT_MAX
        set InitLinks_THREAD_BREAK_COUNTER = 0

        call TriggerClearConditions(InitLinks_DUMMY_TRIGGER)

        call TriggerAddCondition(InitLinks_DUMMY_TRIGGER, condition)

        call TriggerEvaluate(InitLinks_DUMMY_TRIGGER)

        call DestroyBoolExpr(condition)

        set condition = null

        static if (DEBUG) then
            if (InitLinks_ITERATION > STRUCT_EMPTY) then
                call BJDebugMsg("InitLinks: thread break in " + InitStructLinks.name + " with " + I2S(InitLinks_ITERATION))
            endif
        endif

        set condition = Condition(function thistype.InitStructLinks2)
        set InitLinks_ITERATION = STRUCT_MAX
        set InitLinks_THREAD_BREAK_COUNTER = 0

        call TriggerClearConditions(InitLinks_DUMMY_TRIGGER)

        call TriggerAddCondition(InitLinks_DUMMY_TRIGGER, condition)

        call TriggerEvaluate(InitLinks_DUMMY_TRIGGER)

        call DestroyBoolExpr(condition)

        set condition = null
    endmethod
endmodule

//! textmacro LinkToStruct takes folder, struct
    Folder$folder$_Struct$struct$ $struct$ = this

    implement InitStructLinks
//! endtextmacro
JASS:
//Example

//! runtextmacro Folder("Apple")
    //! runtextmacro Struct("Worm")
    endstruct
endscope

//! runtextmacro BaseStruct("Apple", "Apple")
    //! runtextmacro LinkToStruct("Apple", "Worm")
endstruct

//Worm is an inner part of Apple
 
Last edited:
Level 31
Joined
Jul 10, 2007
Messages
6,306
rewrite
Ok, now that I read through this I see what you are trying to do: folders in structs.

...

JASS:
struct A extends array
    private static integer instanceCount = 0
    private static integer recycleCount = 0
    private static integer array recycle

    public static method create takes nothing returns thistype
        local thistype this

        if (recycleCount == 0) then
            set instanceCount = instanceCount + 1
            set this = instanceCount
        else
            set recycleCount = recycleCount - 1
            set this = recycle[recycleCount]
        endif

        return this
    endmethod

    public method destroy takes nothing returns nothing
        set recycle[recycleCount] = this
        set recycleCount = recycleCount + 1
    endmethod
endstruct

struct B extends array
    private static integer instanceCount = 0
    private static integer recycleCount = 0
    private static integer array recycle

    public static method create takes nothing returns thistype
        local thistype this

        if (recycleCount == 0) then
            set instanceCount = instanceCount + 1
            set this = instanceCount
        else
            set recycleCount = recycleCount - 1
            set this = recycle[recycleCount]
        endif

        return this
    endmethod

    public method destroy takes nothing returns nothing
        set recycle[recycleCount] = this
        set recycleCount = recycleCount + 1
    endmethod
endstruct

struct C extends array
    readonly delegate B B
    readonly delegate A A

    public static method create takes nothing returns thistype
        local thistype this = A.create()
        set A = this
        set B = B.create()

        return this
    endmethod

    public method destroy takes nothing returns nothing
        call A.destroy()
        call B.destroy()
    endmethod
endstruct

gg

Not only is it easier to read/understand/implement, but it has less overhead and has more functionality! Woohoo.

If you want more complex implementations with scopes, protected fields, internal fields, and etc, check http://www.hiveworkshop.com/forums/jass-functions-413/stacked-fields-169977/.

And yes, this allows you to override fields as well as access super fields as well as have multi inheritance or have that folder syntax you were talking about.

This is how I make structs extend other structs.
 
Last edited:
Level 26
Joined
Aug 18, 2009
Messages
4,097
JASS:
delegate
pastes its own methods and variables in the parent struct if there are not already methods/variables of the same name.

JASS:
struct B
    method ChildMethod takes nothing returns nothing
    endmethod
endstruct

struct A
    readonly delegate B B

    method ParentMethod takes nothing returns nothing
        call this.ChildMethod()
    endmethod
endstruct

This does not throw a syntax error but I'd want it to.

Other than that, you do nearly the same as I do.

JASS:
struct C extends array
    readonly delegate B B
    readonly delegate A A

    public static method create takes nothing returns thistype
        local thistype this = A.create()
        set A = this
        set B = B.create()

        return this
    endmethod

    public method destroy takes nothing returns nothing
        call A.destroy()
        call B.destroy()
    endmethod
endstruct

The links are initialized here, too. Delegates have the same problem that they do not automatically return the casted version of the input struct instance.

I, however, wanted to not allocate instances of the child structs.

It should look this way

JASS:
struct C extends array
    readonly delegate B B
    readonly delegate A A

    public static method create takes nothing returns thistype
        local thistype this = thistype.allocate()

        set A = this
        set B = this

        return this
    endmethod
endstruct

Now, to not manually obfuscate my code through my syntax problems, I split it off and used textmacros. The link preload at least has the advantage that it does not have to be done multiple times as structs would have when reallocated.
 
Level 31
Joined
Jul 10, 2007
Messages
6,306
You can't do this..

JASS:
        local thistype this = thistype.allocate()

        set A = this
        set B = this

Unless you are only using B and A for that one struct... what you are trying to do is intense coupling, which should never be done.

And the fact is that your textmacros obsfucate the code and I doubt anyone will want to use them. The scope and struct macros are extreme examples, but examples nonetheless ; P.
 
Level 26
Joined
Aug 18, 2009
Messages
4,097
Update: I am abusing the allocation macros now like in
JASS:
struct A
    integer B = 4
endstruct

When an instance of A is allocated, it's B member will be initialized to 4. To init the links to the inner structs, every instance will be allocated onInit and deallocated again, so you do not need another allocation method. Since the number of struct links may vary, therefore said macro will too and the normal allocation method is of undetermined length, which may crash the thread. Fetching ~8k instances and laying them back already takes some operations, so I have integrated that from time to time a new thread will be started. But this time/execution count may be adjusted according to the amount of max links in a struct. The debug message will give information if the limit is hit.

The advantage of this new version is that the InitLinksToStruct-macros are no longer required and I was told that you can have onInit multiple times in a struct when putting them in modules and making them private, so I might as well use onInit and you do not need an extra call.
 
Top