1. Evolution complete! Make Darwin proud and go vote in the Techtree Contest #12 - Poll.
    Dismiss Notice
  2. Icon Contest #17 - Results are out! Step by to congratulate our winners!
    Dismiss Notice
  3. We've created the Staff Job Openings thread. We're currently in need of icon, video production, and social/multimedia positions to be filled. Thank you!
    Dismiss Notice
  4. Melee Mapping Contest #2 - Results are out! Step by to congratulate the winners!
    Dismiss Notice
  5. C'thun has spoken! Texturing Contest #29 - Results are published - check 'em out!
    Dismiss Notice

Best of the Wurst 4

Discussion in 'Latest Updates and News' started by Frotty, Feb 1, 2018.

  1. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,270
    Resources:
    9
    Models:
    3
    Tools:
    1
    Maps:
    3
    Tutorials:
    1
    Wurst:
    1
    Resources:
    9

    Best of the Wurst 4


    [​IMG]

    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 of
      StringHash
      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


    [​IMG]

    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.

    Code (WurstScript):
    @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.

    Code (WurstScript):
    @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.
    [​IMG]

    You can see the result inside the Output tab.

    [​IMG]

    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: Feb 2, 2018
  2. Jampion

    Jampion

    JASS Reviewer

    Joined:
    Mar 25, 2016
    Messages:
    1,264
    Resources:
    0
    Resources:
    0
    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 (Text):
    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
     
  3. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,270
    Resources:
    9
    Models:
    3
    Tools:
    1
    Maps:
    3
    Tutorials:
    1
    Wurst:
    1
    Resources:
    9
    Thanks for the kind words :)
    There seems to be no inline wurst that I know of. I current use [.highlight=wurst]
     
  4. IcemanBo

    IcemanBo

    JASS Class Tutor

    Joined:
    Sep 6, 2013
    Messages:
    5,763
    Resources:
    22
    Maps:
    3
    Spells:
    11
    Template:
    1
    Tutorials:
    4
    JASS:
    3
    Resources:
    22
    Wow, unit tests for wc3 coding. The whole Wurst project really brought coding on a completly new level.

    By the way, if you come up at any time with a cool idea for a competition for Wursties, we also might use challange or arena section as platform.
     
  5. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,049
    Resources:
    2
    Models:
    1
    Icons:
    1
    Resources:
    2
    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)

    Code (vJASS):

    //! runtextmacro someText("param1")
     


    to this? (Wurst)

    Code (WurstScript):

    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)

    Code (vJASS):

    //! 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
     
     
  6. HappyTauren

    HappyTauren

    Joined:
    Nov 3, 2006
    Messages:
    8,426
    Resources:
    87
    Models:
    61
    Icons:
    23
    Packs:
    1
    Tutorials:
    2
    Resources:
    87
    First, here's the linked list module included in wurst's stdlib.

    Code (WurstScript):

    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:

    Code (WurstScript):

    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>.

    Code (WurstScript):

    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: Feb 5, 2018
  7. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,270
    Resources:
    9
    Models:
    3
    Tools:
    1
    Maps:
    3
    Tutorials:
    1
    Wurst:
    1
    Resources:
    9
    Thanks @HappyTauren

    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.

    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.
     
  8. MyPad

    MyPad

    Spell Reviewer

    Joined:
    May 9, 2014
    Messages:
    1,049
    Resources:
    2
    Models:
    1
    Icons:
    1
    Resources:
    2
    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.
     
  9. HappyTauren

    HappyTauren

    Joined:
    Nov 3, 2006
    Messages:
    8,426
    Resources:
    87
    Models:
    61
    Icons:
    23
    Packs:
    1
    Tutorials:
    2
    Resources:
    87
    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.
     
  10. Somebassoon

    Somebassoon

    Joined:
    Jan 27, 2016
    Messages:
    135
    Resources:
    0
    Resources:
    0
    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?
     
  11. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,270
    Resources:
    9
    Models:
    3
    Tools:
    1
    Maps:
    3
    Tutorials:
    1
    Wurst:
    1
    Resources:
    9
    It's done automatically on any build command. You can find the data in _build/objeditingoutput
     
  12. Somebassoon

    Somebassoon

    Joined:
    Jan 27, 2016
    Messages:
    135
    Resources:
    0
    Resources:
    0
    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: Feb 15, 2018
  13. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,270
    Resources:
    9
    Models:
    3
    Tools:
    1
    Maps:
    3
    Tutorials:
    1
    Wurst:
    1
    Resources:
    9
    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.
     
  14. Somebassoon

    Somebassoon

    Joined:
    Jan 27, 2016
    Messages:
    135
    Resources:
    0
    Resources:
    0
    Im still getting errors. Can you attach an example project where you got it to work, and created a unit with another file?
     
  15. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,270
    Resources:
    9
    Models:
    3
    Tools:
    1
    Maps:
    3
    Tutorials:
    1
    Wurst:
    1
    Resources:
    9
    If you would specify your issues further than just "im getting errors" im sure i could help you.
     
  16. Somebassoon

    Somebassoon

    Joined:
    Jan 27, 2016
    Messages:
    135
    Resources:
    0
    Resources:
    0
    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?
     
  17. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,270
    Resources:
    9
    Models:
    3
    Tools:
    1
    Maps:
    3
    Tutorials:
    1
    Wurst:
    1
    Resources:
    9
    Please use proper formating.
    That's a warning, not an error. You don't need the variable at all actually.
    No idea what you mean by "default output type"
    I will check on that, thanks.
    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:
    [​IMG]

    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: Feb 18, 2018
  18. Somebassoon

    Somebassoon

    Joined:
    Jan 27, 2016
    Messages:
    135
    Resources:
    0
    Resources:
    0
    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?
     
  19. Cokemonkey11

    Cokemonkey11

    Wurst Reviewer

    Joined:
    May 9, 2006
    Messages:
    3,137
    Resources:
    17
    Maps:
    5
    Spells:
    3
    Tutorials:
    2
    JASS:
    7
    Resources:
    17
    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 ?
     
  20. Frotty

    Frotty

    Wurst Reviewer

    Joined:
    Jan 1, 2009
    Messages:
    1,270
    Resources:
    9
    Models:
    3
    Tools:
    1
    Maps:
    3
    Tutorials:
    1
    Wurst:
    1
    Resources:
    9
    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.