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

Best of the Wurst 4

Status
Not open for further replies.
Level 23
Joined
Jan 1, 2009
Messages
1,610

Best of the Wurst 4



In this fourth issue of our blog we look at wurstscript's start into 2018, and our roadmap. Once again we're excited to mention that users within our awesome community are getting involved and contribute to the wurst.

The theme for this months code snippet is unit testing in wurst.

WurstScript, or Wurst for short, aims to be a completely integrated wc3 modding solution, with a focus on higher level programming and IDE experience, without sacrificing efficiency of the generated map script.


Updates

  • The runmap command now uses war3.exe if it is present, which can improve loading time. The game executable detection has also been improved.
  • Removed obsolete temp file creation when handling MPQ files, which prevents permission problems on certain systems.
  • Compiletime mocks added for force and gamecache, enabling them to be ued in object editing and unit tests.
  • The compiletime implementation ofStringHash now returns values identical to those at runtime, thanks to @LeP
  • We merged many awesome pull requests for the standard library (#23, #25, #26, #27, #28, #30, #31, #33, #34, #35, #36, #37, #38, #41, #42)
  • We have made some improvements to our continuous integration process, so standard library changes, and pull requests to it are more seamless and powerful.
  • Wurst's underlying mpq library JMPQ has been updated to support pkware decompression and file-less data extraction.
  • The Wc3libs have been updated as well, providing WTS support and Jass's StringHash implementation.
*Note*: The showcase is now official - you should submit your wurst maps for a chance in the spotlight!


2018 Roadmap

Our main goals for this year are:

  • Enhance wurst.build and the setup tool to allow more complete and seamless map compilation. In detail:
    • Support configuring scenario and force options
    • Provide a seamles API for running external tools before or after map builds
    • Make the setup tools fully functional from the commandline
  • Reach 100+ github repos to become a supported language on Github Linguist. We need you to publish your wurst repos on Github!
  • Implement most Jass natives/types for compiletime. Mainly for unit-testing wc3-specific code.
  • Refine, extend, and unit test the standard library.

Unit Testing in Warcraft 3

7j5mjpX.png


Preamble: The 'unit' in unit testing does not refer to wc3 units - rather, a unit is an individual piece of source code that is being tested.

Tests allow you to verify the behaviour of your code outside of warcraft 3 at compiletime. Instead of the game interpreting your generated Jass code, Wurst interprets it and can therefore assert correctness and find errors in code, before they happen in the game.

The major benefit of a unit test is that it can cover most imaginable behaviours, even those that rarely happens inside the game, easily, and without wasting time executing such behaviours inside the game.

One of the caveats of unit testing is that compiletime jass interpretation depends on the completeness and correctness of the wurst compiler that is performing the testing instead of the warcraft engine. Wurst does not implement every native, and since we don't know the inner workings of wc3, it can't emulate everything identically.

There are many benefits of unit testing overall, such as:
  • Prove that your code does what it's supposed to do.
  • Help you improve and understand your code through by to break it.
  • Make big changes to your codebase and verify that everything still works.
  • Inherently reduce the number of bugs.

The easiest units to test are the ones that don't include warcraft specific elements. Here are some condensed example unit tests for our data structures, arithmetics and string manipulation from the standard library.

Wurst:
@Test function testAdd()
    new HashList<int>..add(5).get(0).assertEquals(5)

@Test function linearVecTest()
    let v = linear(vec2(3,4), vec2(6,2), 0.5)
    v.x.assertEquals(4.5)
    v.y.assertEquals(3)

@Test function testJoin()
    new LinkedList<string>()..add("this")..add("is")..add("a")..add("string")
    .joinBy(" ").assertEquals("this is a string")

Writing Unit Tests

To make any function a test, just annotate it with [USER=179970]@Test[/USER]. All functions with this annotation will then be recognized by wurst. This doesn't limit the function in any way and it could theoretically still be used inside your code and not exclusivly as test, however that is not recommended.

A unit test consists of two parts - the execution of the to be tested code and an assertion, that compares the actual result with an expected value.
You can find the assert functions inside the Wurstunit package.

The most simple example is an arithmetic test, because we can easily find out the expected value. E.g.

Wurst:
@Test function example()
    let actual = (10 .squared()) * 6
    let expected = 600
    actual.assertEquals(expected)
As you can see, we calculate what we want to test and then assert that the value is what we expect it to be.

Running Unit Tests

In VScode, use F1 to open the task prompt and enter "run test" and choose one of the options.
LlaEGPc.png]image alt


You can see the result inside the Output tab.

dstOW8w.png]image alt


And that's it! You are now a testing guru. We hope you enjoyed reading this, and we look forward to next month's blog post wherein we'll take a practical look at composing visually beautiful spells in wurst.
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
I have to say, that I am really happy with my wurst experience so far. It enables faster map making, because of it's simplified syntax and higher level abstraction. At the same time this results in fewer bugs, which really speeds up the whole process.
I especially like the standard library, which covers the most frequently needed systems for a map, such as common data structures, events and mathematical functions.
The addition of closures is really nice and saves me a ton of time. Being able to simply use
Code:
doAfter(x, () -> ...)
is just so much faster than how you would do it in jass.
With that in mind I want to thank you and everyone else who made wurst possible.

For everyone who never tried wurst before, I can only recommend to give it a try. Once you got used to it, you won't want to go back anymore.


btw. you added the chapter "Writing Unit Tests" twice to the post.

how can I write wurst code on hive? [wurst]...[\wurst] does not work
 
I know this isn't related to the current blog but I want to know how generics replace textmacros.

Is it implemented from this (vJass)

JASS:
//! runtextmacro someText("param1")

to this? (Wurst)

Wurst:
someText<param1>

My main concern in transitioning to Wurst is the ability to create linked list modules in one line, like this... (Taken from AllocationAndLinks)

JASS:
//! runtextmacro link_module("global", "private") // generates a private module DoubleLink_global
//! runtextmacro link_module("local", "private") // generates a private module DoubleLink_local

struct SSS
    implement DoubleLink_global
    implement DoubleLink_local
endstruct
 
First, here's the linked list module included in wurst's stdlib.

Wurst:
package LinkedListModule
/** Turns a class into a linked list where each instance knows it's previous
    and next member in the list */
public module LinkedListModule
    static thistype first = null
    static thistype last = null
    static int size = 0
    thistype prev
    thistype next
    construct()
        size++
        if size == 1
            first = this
            prev = null
        else
            prev = last
            last.next = this
            first.prev = this
        next = null
        last = this
    static function getFirst() returns thistype
        return first
  
    function getNext() returns thistype
        if next == null
            return first
        return next

    function getPrev() returns thistype
        if prev == null
            return last
        return prev
  
    function remove()
        size--
        if this != first
            prev.next = next
        else
            first = next
        if this != last
            next.prev = prev
        else
            last = prev
      
    ondestroy
        remove()
 
    /** An iterator which iterates over all instances of this class */
    static function iterator() returns Iterator
        return new Iterator(true)
  
    private static let staticItr = new Iterator(false)
    /** An iterator which iterates over all instances of this class */
    static function staticItr() returns Iterator
        return staticItr..reset()
    /** An iterator which iterates over all instances of this class in reverse */
    static function backIterator() returns BackIterator
        return new BackIterator(true)
    private static let staticBackItr = new BackIterator(false)
    /** An iterator which iterates over all instances of this class in reverse */
    static function staticBackItr() returns BackIterator
        return staticBackItr..reset()
    static class Iterator
        LinkedListModule.thistype current = first
        protected bool destroyOnClose
        construct(bool destroyOnClose)
            this.destroyOnClose = destroyOnClose
        function hasNext() returns boolean
            return current != null
 
        function next() returns LinkedListModule.thistype
            let res = current
            current = current.next
            return res
        function reset()
            current = first
      
        function close()
            if destroyOnClose
                destroy this
 
    static class BackIterator
        LinkedListModule.thistype current = last
        protected bool destroyOnClose
        construct(bool destroyOnClose)
            this.destroyOnClose = destroyOnClose
      
        function hasNext() returns boolean
            return current != null
 
        function next() returns LinkedListModule.thistype
            let res = current
            current = current.prev
            return res
        function reset()
            current = last
      
        function close()
            if destroyOnClose
                destroy this

This is how you'd create a generic module in wurst:

Wurst:
public module GenericModule<T>
    T something // attribute of type T

    construct() // called after class instantiation

    ondestroy // called after class' ondestroy

This is a generic class, HashMap<K, V>.

Wurst:
package HashMap
import NoWurst
import HashList
import public TypeCasting
import public Table
/** Generic Table Wrapper */
public class HashMap<K,V> extends Table
 
    /** Whether a value exists under the given key or not */
    function has(K key) returns boolean
        return hasInt(key castTo int)
 
    /** Saves the given value under the given key */
    function put(K key, V value)
        saveInt(key castTo int, value castTo int)
  
    /** Retrieves the value saved under the given key */
    function get(K key) returns V
        return loadInt(key castTo int) castTo V
 
    /** Removes the value saved under the given key */
    function remove(K key)
        removeInt(key castTo int)

And you can also create generic functions, but they aren't that good since you can't make generics extend or implement a class yet, so stuff like someFunc<T implements SomeInterface> doesn't work yet.

Also, before implementing stuff yourself, check out if it is not already part of the stdlib. You can save a lot of time and work by just fiddling around the stdlib.
 
Last edited:
Level 23
Joined
Jan 1, 2009
Messages
1,610
Thanks @HappyTauren

I know this isn't related to the current blog but I want to know how generics replace textmacros.

They don't. A certain limited usage scenario of textmacros can be achieved with generics, but otherwise they don't have much in common except reducing boilerplate code.

My main concern in transitioning to Wurst is the ability to create linked list modules in one line, like this... (Taken from AllocationAndLinks)

I'm curious why this is your main concern. Why do you need it as modules instead of just a List object?
In any case, wurst doesn't support named module uses at this point, so your given example won't work out of the box.
 
Ah, alright. Perhaps that could be a possible feature, that is, adding another capability to generics, to simplify code generation, due to a powerful inliner and optimizer which would clean it up anyway. Then again, it might become what it was not supposed to be, a textmacro. :)

Still, the ability to test things without having to manually open the War3.exe is a huge plus in terms of visualizing the actual code behavior.
 
I am not completely opposed to adding macros to wurst, especially since its misuse is the user's own problem to begin with, and the feature could be made to work alongside the compiled nature of the language if implemented well.

However, I don't find it to be particularly important, either, because it's possible to accomplish everything without using such a crutch solution. Macros have their place, but vjass did force users to use them when they really shouldn't, because the language lacked features.

So instead of macros, I just wish to see generics reworked to be more powerful, and since there's a finite amount of free time for both Frotty and peq (the only two people who work on wurst's internals), I am more than thankful for what's already out there anyways.
 
Level 4
Joined
Jan 27, 2016
Messages
89
Is it possible to create a script that exports object data into wurst format? Primarily so that older maps can have their object data exported and then worked on in a different environment?
 
Level 4
Joined
Jan 27, 2016
Messages
89
Thanks. But now they all seem to look like this

..setInt("uhpm", 550)
..setInt("umpm", 100)
..setInt("urtm", 1)
..setInt("ulum", 0)


Instead of the functions of how its defined in the UnitObjEditing.wurst
And I dont know how to build maps with the above form, aka reusing said old object data because it gives me errors.
 
Last edited:
Level 23
Joined
Jan 1, 2009
Messages
1,610
You should be able to copy and paste those functions, just make sure you have the necessary imports.
Of course you have to delete the old object from the map, otherwise you will create an object with an id that already exists in the map and wurst prevents that.
You can batch copy all functions and delete all objects if you wish.
And yes, it's the lowlevel format, which you can improve on manually. There is a ticket iirc to make the output use the nicer abstractions, but it's not a high priority for now.
 
Level 4
Joined
Jan 27, 2016
Messages
89
I fixed the issues :) , the problems I were facing were
>no matter what, def would be an unused variable, but just adding _ as a prefix works
>the unit editing.wurst functions cannot be used with the default output type, which I just have to deal with then.
>the package was listed after the import and this caused issues for some reason (I didnt change this, it was like this default)
>I put the things in the wrong order

Heres how it looks now


package WurstExportedObjects_w3u
import ObjEditingNatives



init

CreateUnit( Player(0), 'e000', -126.6, 102.2, 228.600 )

@compiletime function create_w3u_e000()
let _def = createObjectDefinition("w3u", 'e000', 'earc')
..setReal("usca", 0.8)
..setInt("ua1b", 14)
..setString("utub", "long descr")
..setString("urac", "human")
..setReal("ussc", 0.8)
..setInt("ubld", 5)
..setString("usnd", "name")

Should be easy to export en masse now, just have to replace def with _def

Also, is there a native that allows direct retrival of any object data?
 
Level 23
Joined
Jan 1, 2009
Messages
1,610
Please use proper formating.
no matter what, def would be an unused variable, but just adding _ as a prefix works
That's a warning, not an error. You don't need the variable at all actually.
the unit editing.wurst functions cannot be used with the default output type, which I just have to deal with then.
No idea what you mean by "default output type"
the package was listed after the import and this caused issues for some reason (I didnt change this, it was like this default)
I will check on that, thanks.
Should be easy to export en masse now, just have to replace def with _def

Also, is there a native that allows direct retrival of any object data?
Yes, except you dont need to change the def - and no, but you can save the data yourself and then read it at runtime.

e: I have checked, the order of package and import is correct:
rRAd9y8.png


If you get a different order, please provide steps and material to reproduce the error.

As I said, you can directly copy the content into another package, omitting the package header.
Here is an example: wurstbin
If you want to get rid of the warnings, just replace "let def = " with "".
 
Last edited:
Level 4
Joined
Jan 27, 2016
Messages
89
Alright, I have one more question, is it possible to have it export default unit modifications as well? Basically, on a map where its primarily changes to vanilla units, I cant export that data with wurstscript as text, only objects that were already made, any possible way to get through this?
 

Cokemonkey11

Spell Reviewer
Level 29
Joined
May 9, 2006
Messages
3,534
Alright, I have one more question, is it possible to have it export default unit modifications as well? Basically, on a map where its primarily changes to vanilla units, I cant export that data with wurstscript as text, only objects that were already made, any possible way to get through this?

That sounds like a bug. Warcraft 3 represents modified vanilla units as custom units (just the object editor doesn't appear that way).

Maybe you can raise a bug in Issues · wurstscript/WurstScript · GitHub ?
 
Level 23
Joined
Jan 1, 2009
Messages
1,610
Alright, I have one more question, is it possible to have it export default unit modifications as well? Basically, on a map where its primarily changes to vanilla units, I cant export that data with wurstscript as text, only objects that were already made, any possible way to get through this?
That sounds like a bug. Warcraft 3 represents modified vanilla units as custom units (just the object editor doesn't appear that way).

Maybe you can raise a bug in Issues · wurstscript/WurstScript · GitHub ?

it's not a bug, the modified standard units simply aren't supported because it then causes problems on reimport because those units can't be marked as wurst generated iirc.
The compromise would be to offer a way to export the modified units into another file or commented out, so they can be translated into proper own units.
Modifying original units is just bad and a no go, I don't get why anyone even does it.
 
Level 4
Joined
Jan 27, 2016
Messages
89
Because it was already reaching the object limit and so the only option to make more w/out crashes was to edit old ones
The compromise would be to offer a way to export the modified units into another file or commented out, so they can be translated into proper own units.
Well, I hope it can be done :\
And if it is, the refferences for modified units would need to be altered in both other objects and triggers too which is a bit annoying.
 
Level 23
Joined
Jan 1, 2009
Messages
1,610
Because it was already reaching the object limit and so the only option to make more w/out crashes was to edit old ones
Seems unlikely. And if so, why do you need thousands of different units?
Do you hit the same limit with wurst?

Well, I hope it can be done :\
And if it is, the refferences for modified units would need to be altered in both other objects and triggers too which is a bit annoying.
There will be no support for altering default units. The most I offered was to extract them so you can generate custom ones from that.
 
Status
Not open for further replies.
Top