wurstscript / WurstScript

Programming language and toolkit to create Warcraft III Maps
https://wurstlang.org
Apache License 2.0
226 stars 28 forks source link

Is there a way using 'this' in static function? #1040

Closed tester1101 closed 2 years ago

tester1101 commented 2 years ago

When I implemented some vjass code into wurstscript. I have encountered a problem below.

I tried to write this vjass code in wurstscript. (This is not exactly same code what I'm working. it's just a example for showing problem)


//vjass

struct Example
    unit u
    real dx
    real dy
    static hashtable ht = InitHashtable()

    static method meth1 takes nothing returns nothing
        local thistype this = LoadInteger(ht, 0, GetHandleId(GetTriggeringTrigger()))
        call SetUnitX(.u, GetUnitX(.u) + .dx)
        call SetUnitY(.u, GetUnitY(.u) + .dy)
    endmethod

endstruct

so it may look like this in wurst...


//wurstscript

class Example
    unit u
    real dx
    real dy
    static hashtable ht = InitHashtable()

    static function meth1()
        thistype this = LoadInteger(ht, 0, GetHandleId(GetTriggeringTrigger()))
        u.setX(u.getX() + dx)
        u.setY(u.getY() + dy)

but this emitted errors.

The important point is 'local thistype this = LoadInteger...' in static function meth1 in vjass. That statement have no problem in vjass. However in wurst, it emits error. That means, in vjass, I could use 'this' in static method. but I can't use 'this' in wusrt's static function.

Is there a way using 'this' in static function??

Thank you.

Frotty commented 2 years ago

You seem to have the wrong idea about using Wurst, because you come from vJass, which is expected. First of all you can't just use ints as objects in Wurst, you have to cast them. But then - why are you using a static function in the first place if you want to use this? Kinda doesn't make sense. You should use proper wurst and don't translate bad vjass to bad wurst. If you want to attach data to a trigger (which is what I assume u want to do here), you can do it like so:

constant map = new HashMap<trigger, Example>

class Example
  function doSomething()

function onEvent()
  map.get(GetTriggeringTrigger()).doSomething()
tester1101 commented 2 years ago

Thank you for your replying.

You said, "why are you using a static function in the first place if you want to use this? Kinda doesn't make sense." I agreed that. But some cases, using 'this' in static function may be convenient.

This is the reply for that question.

First of all, I have made a simple bullet system in vjass below. (This example is abbreviated for understanding easily.)


struct Bullet
    static hashtable ht = InitHashtable()

    unit u
    real dx
    real dy

    trigger TT

    // essential components.

    method addEngine takes code func returns nothing
        call TriggerAddCondition(.TT, Condition(func))
    endmethod

    static method create takes unit u returns thistype
        local thistype this = thistype.allocate()
        set .u = u
        set .dx = 100
        set. dy = 100

        set .TT = CreateTrigger()
        call TriggerRegisterTimerEvent(.TT, 0.03125, true)
        call SaveInteger(.ht, 0, GetHandleId(.TT), this)

        return this 
    endmethod

    method destroy takes nothing returns nothing
        call RemoveSavedInteger(.ht, 0, GetHandleId(.TT))
        call DestroyTrigger(.TT)
        set .TT = null
        set .u = null
    endmethod

endstruct

function moveEngine1 takes nothing returns nothing
    local Bullet bull = LoadInteger(Bullet.ht, 0, GetHandleId(GetTriggeringTrigger()))
    call SetUnitX(bull.u, GetUnitX(bull.u) + bull.dx)
    call SetUnitY(bull.u, GetUnitY(bull.u) + bull.dy)
endfunction

function SomeFunction takes nothing returns nothing
    local Bullet bull = Bullet.create(CreateUnit(Player(0), 'hfoo', 0., 0., 0.))
    call bull.addEngine(function moveEngine1)
endfunction

This system creates a bullet class instance bull but it has no moving engine. So we have to attach moving engine to bull by using bull.addEngine(function moveEngine1).

This looks enough, but I tried moveEngine1 to be included in struct Bullet for coding easily.


struct Bullet
    static hashtable ht = InitHashtable()

    unit u
    real dx
    real dy

    trigger TT

    // included moving engines

    static method moveEngine1 takes nothing returns nothing
        local thistype this = LoadInteger(.ht, 0, GetHandleId(GetTriggeringTrigger()))
        call SetUnitX(.u, GetUnitX(.u) + .dx)
        call SetUnitY(.u, GetUnitY(.u) + .dy)
    endmethod

    // essential components.

    method addEngine takes code func returns nothing
        call TriggerAddCondition(.TT, Condition(func))
    endmethod

    static method create takes unit u returns thistype
        local thistype this = thistype.allocate()
        set .u = u
        set .dx = 100
        set. dy = 100

        set .TT = CreateTrigger()
        call TriggerRegisterTimerEvent(.TT, 0.03125, true)
        call SaveInteger(.ht, 0, GetHandleId(.TT), this)

        return this 
    endmethod

    method destroy takes nothing returns nothing
        call RemoveSavedInteger(.ht, 0, GetHandleId(.TT))
        call DestroyTrigger(.TT)
        set .TT = null
        set .u = null
    endmethod

endstruct

function SomeFunction takes nothing returns nothing
    local Bullet bull = Bullet.create(CreateUnit(Player(0), 'hfoo', 0., 0., 0.))
    call bull.addEngine(function Bullet.moveEngine1)
endfunction

By including moveEngine1 in struct, I could make this code

function moveEngine1 takes nothing returns nothing
    local Bullet bull = LoadInteger(Bullet.ht, 0, GetHandleId(GetTriggeringTrigger()))
    call SetUnitX(bull.u, GetUnitX(bull.u) + bull.dx)
    call SetUnitY(bull.u, GetUnitY(bull.u) + bull.dy)
endfunction

become concised like this.

    static method moveEngine1 takes nothing returns nothing
        local thistype this = LoadInteger(.ht, 0, GetHandleId(GetTriggeringTrigger()))
        call SetUnitX(.u, GetUnitX(.u) + .dx)
        call SetUnitY(.u, GetUnitY(.u) + .dy)
    endmethod

It may look not having enough difference. but moveEngine1 is a very simple engine. another engine functions can become more complicated and including massive calculations.

In conclusion, I used 'this' in static method for coding conveniently.

However, I recognize that is not programmatically good coding as you said. So I accept your opinion and will try to find a proper way for wurst.

Frotty commented 2 years ago

I agreed that. But some cases, using 'this' in static function may be convenient.

I think it's just a hack which's necessity arises from the limitations of vJass and it is just syntactic sugar (jasshelper inserts the custom defined this variable, even if it wouldn't make sense). And even then, the better option would be to call a non-static method from your static one where you loaded the object instead of this implicity. I didn't check your whole example, but if you want to pass functions in Wurst, you would use Closures. The issue tracker isn't really the right place for this, feel free to join the discord to discuss implementation details.

Below is a small mockup on how you could do this in wurst. Consider checking vectors and such too.

package Bullet
import TimerUtils

interface MoveEngine
    function move(Bullet bullet)

class Bullet
    use TimedLoop

    vec2 pos
    vec2 velocity

    MoveEngine moveEngine = bullet -> bullet.addPos(bullet.velocity)

    construct()
        startTimedLoop()

    function addPos(vec2 pos)
        this.pos += pos

    override function onTimedLoop()
        moveEngine.move(this)

init
    let normalBullet = new Bullet()

    let differentBullet = new Bullet()
    differentBullet.moveEngine = bullet -> bullet.addPos(bullet.velocity * 0.5)