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

Lua Submission Rules Discussion

Status
Not open for further replies.

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
We already have a lot of Lua submissions, so it's time to come up with Lua submission rules. In this thread I want to hear your opinions and thoughts on the topic, so that in the end we will have rules that everyone can agree on.

I'll introduce some example topics in order to give you an idea about possible rules. You can also check out the JASS Submission Rules, the JASS Proper Application Guide or the Wurst Submission Rules for ideas.

____________________________________________________________________

Naming Conventions and Formatting

Blizzard functions use FunctionName, but Lua uses functionname.
JASS uses 4 spaces for indentation. The Lua Style Guide says two spaces is common.

Do we dictate a certain style or let the author decide?

Code Structure

vJASS uses libraries, which allow dependencies between systems and ensure correct initialization order. Private members can be used to distinguish between the global API and internal members. They also prevent name collisions.

Most Lua submission seem to simply use a "do" ... "end" block and directly execute their code.
I've also seen custom module systems like Module System and wc3-wlpm-module-manager (by ScorpioT1000).

Do you know other ways? What do you prefer? Should we stick to one way or leave it up to the author?

Documentation

Lua is dynamically typed, so additional documentation may be necessary, such as the expected type of function arguments or the return value.

____________________________________________________________________
 

Wrda

Spell Reviewer
Level 25
Joined
Nov 18, 2012
Messages
1,864
I would argue that indentation should be the same as JASS, since we're so used to know that wouldn't make sense to suddenly change it just for another language.
I prefer "do" ... "end", although I have no idea about other ways, so take this one with a grain of salt.
Regarding variables, I think constants should remain the same way as we do in JASS, but function parameters ehhhh, that looks hard if not impossible to enforce. Best case scenario is writing comments for users to know what they're dealing with.
 
The do .. end structure that is present in most, if not all Lua submissions is done, by my speculation, in order to circumvent the 200 local variable limit. Most of the direct code executions so far are usually meant for initializing the essential members of the code itself. As far as I've witnessed, there are at least two ways to inject lua code into the map script, either by dealing with it in the World Editor, or by using external tools such as Ceres.
 
Level 20
Joined
Jul 10, 2009
Messages
474
I find it hard to say what to enforce and what just to encourage, but even encouragements could be part of Submission Rules, right?

My personal preference is as follows:

1. Documentation

I think, these rules should be mandatory:
(Most of this section taken from JASS and Wurst submission rules)
A submission should always clearly describe on top of the code
  • what purpose it has
  • how to install it (even if it's just copy-pasting the code to your map)
  • which requirements it has
  • which settings the user can change to adapt the system to his or her needs.
  • which functions are part of the API (meant to be used by the user)
  • how to use the submission in general
My personal golden rule is: A good submission should never require the user to read the code internals, but provide all necessary knowledge in the documentation.

To be discussed
I'm a big fan of always asking for example code and would even prefer it being a mandatory requirement for all code submissions. Example code is for me, what often makes the difference between conveniently using a resource on the fly opposed to trying to get it to work for hours. However, given that many current submissions don't have example code attached, other people seem to have a different opinion on this (or have just been lazy, when writing their submission).

Regarding function parameters, I am more the hardliner guy and would prefer to ask for both the type and the meaning of all input and output parameters in the documentation (at least for API-functions, i.e. those meant to be used by the user). I even think, that this is not much to ask for. The question remains, how the parameter information should be specified. Maybe it's not necessary to force a specific way, but still two examples for the sake of brainstorming:
Lua:
--[[
A library with useful functions for Timer Utility.
*******************
* -------------------
* | Timer Utils API |
* -------------------
*    TimerUtils.callDelayed(real delayInSeconds, function callback, ...) --> nothing
*        - Executes callback after delayInSeconds with the specified arguments (...). Does not delay the following lines of codes.
*        - Not usable during loading screen.
*    TimerUtils.getElapsedGameTime() --> real
*        - Returns the time in seconds elapsed since map start.
*******************
--]]
Lua:
    ---Calls a function (or callable table) after the specified delay with all specified arguments (...). Does not delay the following lines of codes.
    ---@param delayInSeconds real
    ---@param callback function|table if table, must be callable
    ---@vararg any arguments of the callback function
    function TimerUtils.callDelayed(delayInSeconds, callback, ...)
        <code>
    end

    ---Returns the elapsed game time in seconds.
    ---@return real
    function TimerUtils.getElapsedGameTime()
        <code>
    end

I personally use both styles in my submissions, i.e. mentioning parameter types in the documentation on top AND also specifying them in the code above the function definitions. I think, all ways are equally fine as long as it's clear for the user. Please note that emmy annotation has the additional advantage of being compatible with VSCode plugins like sumneko.lua.

Btw, I would not require users to use a custom modulo system for Lua. As long as the requirements are clearly stated in the submission, a user can easily copy a system to his map below the required system to ensure correct initialization order.

2. Formatting

I agree with @Wrda that indentation should be the same as in JASS. It makes things easier to read, especially when viewing both JASS and Lua resources.

Same goes for naming convention, I'd prefer to have them similar to JASS, i.e. UPPER_CASE for constants (no matter if global or local), CamelCase for globals (class names, library names, global variables, global functions), camelCase for locals (variables, functions) and methods, 1 for integers and 1. for reals.

3. Technical Implementation

Lua is very different to JASS, so it may be hard for all people to get used to the many specific things it has. We don't necessarily have to be strict on everything, but at least mentioning some basic submission rules for technical implementation might definitely help.

I think, the following four bullets are mandatory and should be stated in the submission rules:
  • Memory Leaks should be avoided.
    As similar as it sounds to the JASS rules, this is a different story in Lua, because it very much relies on the garbage collector. While you had to actively destroy objects in JASS, in Lua you would simply try to remove any reference to an object, when it's not needed anymore. This is not always trivial and may require knowledge about using weak keys and values in tables.
    We might also need to require Warcraft 3 types to be removed via standard destructors like in JASS. For instance, TriggerHappy once shared a finding on hive that the number of executions of TimerStart(CreateTimer(), 0.1, true, function() print("bla") end) were non-deterministic and rely on garbage collection timing, so you could even produce desyncs, when relying on the garbage collector to remove timers. In other words, explicitely destroying a timer via DestroyTimer should still be the preferred option.
  • Avoid global scope, whenever possible -> use do... end!
    In contrast to vanilla JASS, where you only had global scope outside of function definitions, Lua offers good options to hide stuff in scope blocks.
    Using the local scope has two major advantages: It's faster and it avoids name clashes.
    Especially the latter is super important in Lua, as (in contrast to JASS) the syntax checker won't even inform you about name clashes (as it is legitimate to do that on purpose), but just silently break your code and lead to absurd bugs. I can't stress this enough, don't use global scope for your functions and variables or you would risk name clashing with other submissions!
    IMO, the only things written to global scope should be Classes, Libraries and redefinitions of existing Wc3 natives.
    Lua:
    do
        PlayerUtils = {} --library (global scope)
    
        local playerMouseLocationX = {} ---@type table<player,real> --contains the latest known mouse x coordinate for every player
        local playerMouseLocationY = {} ---@type table<player,real> --contains the latest known mouse y coordinate for every player
        local trigger                   ---@type trigger --updates the values in PlayerMouseLocationX and PlayerMouseLocationY, whenever a player moves their mouse
    
        ---Get the X value of target players current mouse location
        ---@param whichPlayer player
        ---@return real
        function PlayerUtils.getPlayerMouseX(whichPlayer)
            return playerMouseLocationX[whichPlayer]
        end
    
        ---Get the X value of target players current mouse location
        ---@param whichPlayer player
        ---@return real
        function PlayerUtils.getPlayerMouseY(whichPlayer)
            return playerMouseLocationY[whichPlayer]
        end
    
        <further code defining the trigger>
    end
    If a system needs to store data of any type (in this case the last known mouse coordinates for every player), it can easily hide that data from any other system by just packing everything in do... end and localizing the variables.
    The library functions are packed into the library, but still accessible from outside.
    The only name that can clash is "PlayerUtils".
    Long story short: The submission rules should ask for avoiding global scope whereever possible. Using libraries/classes and do... end allows you to do so.
  • Avoid Lua functionality that can desync.
    E.g., iteration order within pairs-loops is not guaranteed to be the same for all players, so changing game state within such a loop can lead to a desync. SyncedTables exist as a solution.
    Wc3 hashtables can (afaik) also produce desyncs in combination with the Lua garbage collector, especially after using FlushChildHashtable, so people should just use Lua tables.
    GetHandleId(object) and tostring(object) are also not synced in Lua (and there is no reason to use GetHandleId in Lua anyway).
  • Don't use Wc3 natives in the Lua root (i.e. code not part of any function).
    @Tasyen conducted some tests about this. Conclusion is, just don't do it. Earliest point to use Wc3 natives is during the initialization process, typically the GUI Map Init event.
Whereas the following bullets could be mentioned as best practices, but we could also collect these things in a different thread dedicated to Lua best practices... (or not at all and leave that to every contributor to decide on his own).
  • Prefer Lua natives over Wc3 natives, when possible.
    Mainly because it's faster.
    For instance, string.sub(s, i, j) is preferable over SubString(s, i-1, j).
    As another example, Lua tables are better than Wc3 hashtables.
  • Arrays start at index 1
    While Lua in theory can handle arrays starting at index 0, many core functionalities of the language depend on an array starting with index 1 (like ipairs and the #-operator), so it's definitely best practice to always stick to this. I know, it can be weird, when coming from other languages.
  • When working with Lua tables, you can use any object as index.
    As shown in the example about global space, you can just write t[p] for any table t and player p. There is no need anymore to store data in t[GetPlayerId(p)], as the Wc3 native is both slower and makes the code less readable.
    There is still a drawback: If you want to loop over t, you would need the asynchronous pairs-loop. So if you don't want to use SyncedTables, you can use t[GetPlayerId(p)] instead and loop via ipairs, which is always synchronous. But that is a special case. I hope, you get my point :)
    This is more a best practice that can be encouraged, though. Not really a "rule".
  • Avoid the %-symbol in your code.
    This is actually a very specific thing, which might not be necessary to state in submission rules.
    Two reason, why the %-symbol is bad in Wc3-Lua:
    1. having the string %s in any script of your map can crash the world editor upon syntax check, even if it was only part of a comment. Can happen even with other symbols in between the % and the s.
    2. Wc3 somehow escapes %, so you mostly need to write %% instead, even if you just want to use the modulo operator. This is not only annoying and can interfere with syntax checks in external developing environments like VSCode, but it also effectively prevents the code from being interpretable by other Lua VMs. While this is not necessarily a problem, it's still best practice to use ModuloInteger or math.fmod for the modulo operation and "\x25" within strings.

A further topic for discussion might be specific submission rules for the object oriented approach of Lua (which is, where classes come into play). There are definitely best practices for that, too, but maybe this leads to far...
Same goes for how to define optional parameters in functions.

Wall of text, it somehow always gets like this... :D
 
Last edited:

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,182
Unless there is something unique about lua compared to the other options I don't really know what there is to talk about.

I think you can copy any of the text based rules and it'd be fine.

As far as I am concerned it just comes down to:
Functionality: needs to be useful + MPI/MUI
Usability: Should be as easy to import as possible given the tools available.
Readability: Code should be consistent.
Documentation: required but there is no standard on how much or how to format it

Someone in the position of Spell reviewer will know what good code looks like, trying to put it into words is kinda pointless.
Some rough guidelines should be more than enough.
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
A 1-to-1 copy of JASS syntax to Lua syntax doesn't really make sense. JASS has been a 4-space block, but Lua has more flexibility (anywhere from 2-4 spaces).

As long as it's between 2-4 spaces AND is consistent through that script, then there shouldn't be any issue. Lua Style Guide — Element 0.46.2 documentation presents a compelling argument for 3 spaces, and that's what I usually use.

As far as FunctionName convention goes, this is a hard sell. Indeed, Blizzard natives are still LikeThis - HOWEVER, Lua functions are likethis (e.g. print/math/table/setmetatable.

Therefore, I think that, for Lua, the rules should be slightly more relaxed.
  • FunctionName -- ok
  • functionName -- depends on how it's scoped. onUnitIndex/onUnitDeindex kind of make sense in their own world, whereas "doStuff" definitely doesn't. This kind of syntax is used in vJass structs, but unless someone is using something like "variable:doStuff()" then the scope should conform to some kind of pre-existing standard. The "onInit" naming convention carries over from vJass structs and methods, so onGlobalInit/onGameStart do make sense from a a familiarity point of view, and considering how useful they are and direct in their intentions, the "onSomething" versus "OnSomething" makes sense to tend towards the former.
  • functionname -- definitely disallow this for a public function as it then becomes too similar to Blizzard's types (unittype, damagetype, etc).
  • SCOPE_PREFIX_functionName -- possibly the best of all worlds, since it carries from the "public" naming convention introduced by Vexorian. I'm using this type of convention in the Lua Damage Engine 2.0 update (Damage_register/Damage_unregister/etc). Whether the scope starts with an uppercase letter or a lowercase letter may not be too important, as long as there is an underscore separating that scope from the variable name.
Either of the below should be fine (with or without the preceding underscore)
  • (_)THIS_IS_A_PRIVATE_CONSTANT
  • (_)SCOPE_PREFIX_THIS_IS_A_PUBLIC_CONSTANT --here, SCOPE_PREFIX should be in ALL_CAPS.
All existing functions that may be overridden by your submission must be specified. Additionally, if there are compatibility concerns with other overrides of the same function, they should also be stated.

What we need is a standard so that overrides don't mess up other resources. vJass hooks are limited, but they are certainly friendlier when it comes to multiple resources wanting to do their own thing with those hooks.

The following is something that I had created, but I also hate it, because it's limited to doing just one thing. We don't need 7,000 separate resources just for overriding something. [Lua] Variable Event Replacement

I am pretty sure that I've seen someone on Discord who had their own smart way of doing variable overrides. I can't recall exactly who it was. @TriggerHappy @Tasyen does this sound familiar to either of you (a resource that can sort of emulate vJass hooks, in that multiple resources can hook the same thing)?

Anyone is welcome to debate my understanding of Lua syntax, since I am not at all an expert on the criteria. However, I do know enough about JASS, vJass and Lua to know that we shouldn't try to force those kinds of standards on Lua, which is an entirely different programming language with an entirely different development lifecycle.
 
Last edited:

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
A 1-to-1 copy of JASS syntax to Lua syntax doesn't really make sense. JASS has been a 4-space block, but Lua has more flexibility (anywhere from 2-4 spaces).

As long as it's between 2-4 spaces AND is consistent through that script, then there shouldn't be any issue. Lua Style Guide — Element 0.46.2 documentation presents a compelling argument for 3 spaces, and that's what I usually use.
Coding conventions aren't super strict. It's mostly for people who aren't sure yet what conventions to follow and in case some code is formatted badly, so we have an official document that states how to improve it. If your code is perfectly readable and nicely formatted, I wouldn't personally reject it just because it has 3 spaces for indentation.

functionName -- depends on how it's scoped. onUnitIndex/onUnitDeindex kind of make sense in their own world, whereas "doStuff" definitely doesn't. This kind of syntax is used in vJass structs, but unless someone is using something like "variable:doStuff()" then the scope should conform to some kind of pre-existing standard. The "onInit" naming convention carries over from vJass structs and methods, so onGlobalInit/onGameStart do make sense from a a familiarity point of view, and considering how useful they are and direct in their intentions, the "onSomething" versus "OnSomething" makes sense to tend towards the former.
I'm not sure I understand. Do you mean specifically functions that start with "on" should be "on" instead of "On" because of vJASS "onInit"?
If so, that seems contradictory to your point that we should not force vJASS standards on Lua.

functionname -- definitely disallow this for a public function as it then becomes too similar to Blizzard's types (unittype, damagetype, etc).
Agreed. It's also just terrible to read if more words once it gets longer and you no longer know where one word ends and a new one starts.

SCOPE_PREFIX_functionName -- possibly the best of all worlds, since it carries from the "public" naming convention introduced by Vexorian. I'm using this type of convention in the Lua Damage Engine 2.0 update (Damage_register/Damage_unregister/etc). Whether the scope starts with an uppercase letter or a lowercase letter may not be too important, as long as there is an underscore separating that scope from the variable name.
There are no scopes in Lua, so I don't think something like that is necessary. If you want to use a prefix for your API you obviously can do that, but it's not required. You could also put your API in a table:
Lua:
myAPI = {
        Foo = function ()
        end,
        Bar = function ()
        end
}
Has the advantage that you do not have to write the prefix in front of every function declaration and it's easier to change later.

What we need is a standard so that overrides don't mess up other resources. vJass hooks are limited, but they are certainly friendlier when it comes to multiple resources wanting to do their own thing with those hooks.

The following is something that I had created, but I also hate it, because it's limited to doing just one thing. We don't need 7,000 separate resources just for overriding something. [Lua] Variable Event Replacement

I am pretty sure that I've seen someone on Discord who had their own smart way of doing variable overrides. I can't recall exactly who it was. @TriggerHappy @Tasyen does this sound familiar to either of you (a resource that can sort of emulate vJass hooks, in that multiple resources can hook the same thing)?
I agree that something better would be nice, though I think in your case overriding the TriggerRegisterVariableEvent function 3 times should work. You would end up with something like:

Lua:
function Override1(trig, var, op, val)
    for i = 1, eventCount1 do
            if events1[i](trig, var, val) then return end
    end
    TriggerRegisterVariableEvent(trig, var, op, val)
end
function Override2(trig, var, op, val)
    for i = 1, eventCount2 do
            if events2[i](trig, var, val) then return end
    end
    Override1(trig, var, op, val)
end
function Override3(trig, var, op, val)
    for i = 1, eventCount3 do
            if events3[i](trig, var, val) then return end
    end
    Override2(trig, var, op, val)
end
Which is essentially:
Lua:
    local oldEvent = TriggerRegisterVariableEvent
    function TriggerRegisterVariableEvent(trig, var, op, val)
        for i = 1, eventCount3 do
            if events3[i](trig, var, val) then return end
        end
        for i = 1, eventCount2 do
            if events2[i](trig, var, val) then return end
        end
        for i = 1, eventCount1 do
            if events1[i](trig, var, val) then return end
        end
        oldEvent(trig, var, op, val)
    end

It starts to really become problem when you override a function without referencing the old function.
Lets say you override the KillUnit function and change it so a dummy unit deals infinite instead. If KillUnit was overridden previously (e.g. adding a special effect) this will now be lost.
 
Status
Not open for further replies.
Top