• 🏆 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!
  • ✅ HD Level Design Contest #1 POLL is now OPEN! Check out the stunning visuals of the final entries. 🔗Click here to cast your vote!

[Lua] Module System

Status
Not open for further replies.
Level 33
Joined
Apr 24, 2012
Messages
5,113
I'm just moving my small Github repo here.

This is a tiny module system for Wc3's Lua. Since Wc3 lacks the concept of modules (which is similar to web apps), it is better if there's a way to segregate code and concern through a module system.


Lua:
local moduleLoaded = {}
local cachedModule = {}
local moduleBody = {}
local origin = ""

local function loadModule(name)
  if((not moduleLoaded[name]) and origin ~= name) then
    local parent = origin
    origin = name
    cachedModule[name] = moduleBody[name](loadModule)
    moduleLoaded[name] = true
    origin = parent
  end
  return cachedModule[name]
end

local function moduleCreate(name, body)
  moduleBody[name] = body
  moduleLoaded[name] = false
  cachedModule[name] = nil
end

module = {
  create = moduleCreate,
  load = loadModule
}

Usage:
Lua:
module.create("A", function() return 100 end)
module.create("B", function() return 200 end)
module.create("add", function ()
  return function (a, b)
    return a + b
  end
end)
module.create("print", function ()
  return print
end)
module.create("addAB", function (require)
  local A = require("A")
  local B = require("B")
  local add = require("add")
  return add(A, B)
end)
module.create("init", function (require)
  require("print")(require("addAB"))
end)

module.load("init")

You can also use module.load over the provided require.


Relevant: Modules for Wc3 Lua
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
Sorry, could you please help me to understand what this is doing? I am only familiar with "module" from vJass era. Is there something in vJass that this could be similarly compared to? I am just not following this at all :(
This was my attempt to port an AMD-like module system into Lua, which was slightly modified to be similar to that of Facebook's internal module system for JavaScript. The thing is, and IIRC, module system in Wc3 Lua is non-existent, and so this more of a close attempt to properly scope things up for the user.

In comparison, you can think of this as vJASS' library
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,446
Ideally, I'd like to stick to a limited number of ways of doing library requirements for Lua resources. I've also seen @ScorpioT1000 's approach here: GitHub - Indaxia/wc3-wlpm-module-manager: Warcraft 3 Lua Module Manager (like ES6) which uses their tool GitHub - Indaxia/WLPM: Warcraft 3 Lua Package Manager . I had taken a "break" from Lua for 2.5 years and am trying to play catchup, and I think it will take a lot of heads working together if we are to agree on some kind of expectations for dependencies. Libraries without dependencies are straightforward enough to compensate for, but a lot of times it's just better to rely on smaller, focused segments of code to support a small, specific system.

I've started to consider "Gobal Initialization" as a form of dependency loading, but it still needs Global Initialization itself as a dependency, so it's not a silver-bullet solution. I'm not 100% certain, but it seems like this resource suffers from the same limitation (that it, itself needs to be declared at the top of the map rather than at just some arbitrary point in the code like we used to be able to get away with with vJass resources).

@ScorpioT1000 's method uses a third-party tool to "inject" the dependencies directly into the war3map.lua file when working with a map as a folder, which makes a fair amount of sense. I would still have to look into the practicality of it, since I've just moved from Notepad++ into VSCode and perhaps VSCode already offers a similar solution? @MindWorX @Eikonium @Jampion @AGD @MyPad @maddeem @Mayday @Beckx @ZiBitheWand3r3r @Tasyen @HerlySQR @InsaneMonster @Forsakn what are your thoughts on dependencies? Should we have a standard on how Lua resources should require others, and what is your vote on which method works best?
 

Jampion

Code Reviewer
Level 15
Joined
Mar 25, 2016
Messages
1,327
I don't think we should have a standard. It's best if resources don't come with their own dependency system and are simply wrapped in do ... end. Then the user can just copy the code and use it together with the dependency system they prefer (or none at all).

A dependency system that requires maps to be saved as folders is too restrictive and the resources of Hive are supposed to work with any kind of setup. Custom campaigns for instance severely limit your options, because the map will be inside the campaign archive.

With that said, individual dependency systems can still be approved. I just think it should be user's choice which one works best for their map and workflow.


If we are to have a standard dependency system, there are a few points that are important:
  1. How does it interact with user code that is not covered by the dependency system? Ideally you only want to specify dependencies between libraries and have normal code (e.g. spells) always load after libraries without having to manually add dependencies (like scopes in vJASS)
  2. When are the modules initialized? I guess it should be done at a time when natives can be used, but that kind of goes against point 1, because code that is not managed by the dependency system will by default run before natives can be used.
  3. What changes does the user need to make to use the dependency system. Usually the code of the dependency system needs to be put at the top.
  4. Control over the load order. You may want to load some libraries before all other libraries or after all other libraries. Especially due to hooks, you may want to add some hooks very early, so that other libraries already benefit from it, even if there is no direct dependency between them.
All in all it seems very difficult to me to come up with something that works well in all cases and even if possible, it could just give so many options that it becomes confusing.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,446
I agree with those points. I haven't used TypeScriptToLua/Ceres, but I am conscious that we shouldn't be expecting users to download a bunch of third party software to properly use the code. @ScorpioT1000 seems to have a fairly reasonable approach where they've provided multiple different ways to acquire the resources (e.g. by copying them from a war3map file).

I've been hunting around trying to find different approaches. lua require ersatz shows Troll-Brain basically walking in the same steps I've been, with using Global Initialization to determine when something would initialize. Now that Global Initialization has evolved "for better or worse" to include dependencies of its own, I am wary of encouraging anyone to use it (unless I released an "easy installer" which just combined all those scripts, but still I can't expect people to want to do that, nor encourage it).

Therefore, I think the method you proposed, Jampion, makes the most sense: we shouldn't have a standard for how dependencies are declared beyond enforcing that they can be copied and pasted from HiveWorkshop into Custom Scripts within the War3 Trigger Editor.

For me, rather than defining "do/end" block for resources that require others, I've started to do "if <Required_Library> then" and include a link to that library on the right. For example:

Lua:
if  Timed                   --https://www.hiveworkshop.com/threads/timed-call-and-echo.339222/
and GlobalRemap             --https://www.hiveworkshop.com/threads/global-variable-remapper.339308
and AnyPlayerUnitEvent      --https://www.hiveworkshop.com/threads/collection-gui-repair-kit.317084/
and Event then              --https://www.hiveworkshop.com/threads/event-gui-friendly.339451/

As for hosting projects on GitHub (very practical) but sharing them to be able to be copied from Hive, the functionality doesn't yet exist. Obviously, such a feature could enable code editing and sharing to be far less painful than how it currently is.
 
Last edited:
Level 20
Joined
Jul 10, 2009
Messages
474
I agree to the current state of the discussion.

Just to add to this, dependencies in Lua are much more forgivable than in JASS, because function dependencies are evaluated at runtime instead of compile time. In most cases, you don't even need to care for the order of script pasting:
Lua:
--library A
A = {}
function A.f()
    B.f() --A depends on B, but this is evaluated at runtime, so A pasted before B works just fine as long as A.f is not executed before B is defined below
end

--library B
B = {}
function B.f() end

--do we need a module system in this case?
--No, because code order simply doesn't matter and the only possible user error is forgetting about pasting B into the map at all, which a module system can't prevent (well, it can print an error, but that's all)

Of course, using another library in the Lua root is a different story.
Lua:
--library A
A = {
    prop = B.f() --attempt to evaluate before B is even loaded. This will throw an error
}

--library B
B = {}
function B.f() end

--do we need a module system in this case?
--Again I think not, because the module system can not prevent code in the Lua root from being executed anyway.
--Using a module system would require packing the whole system into some kind of init-function, which the module-system can execute in a desired order.
--That's not only ugly, but also too restrictive.

Judging from the resources I've seen so far, dependencies in the Lua root are rather rare - and can't really be solved with a module system without ridiculous requirements for code structure anyway.

That said, I'm against requiring any form of module system.

If at all, dependencies could be managed by an application controlling the order of code insertion, i.e. which manages the build process.
Such a tool exists (Ceres), but again, requiring everyone to use it would be too restrictive.
 
Last edited:
Level 4
Joined
Jun 26, 2013
Messages
48
I don't think we should have a standard. It's best if resources don't come with their own dependency system and are simply wrapped in do ... end. Then the user can just copy the code and use it together with the dependency system they prefer (or none at all).
I agree. The cJass problem should not happen again when you create the product and then everybody must migrate from it bcz it crashes and never supported
@ScorpioT1000 seems to have a fairly reasonable approach where they've provided multiple different ways to acquire the resources (e.g. by copying them from a war3map file).
If we consider the lua solutions market, it is divided into small demos with a couple of dependencies and heavy projects with dependency chains, so there should be different ways to connect libraries. And it should be OK to add multiple installation methods, including different package managers.

Just look how it's done in react Optimizing Performance – React

I can even add Almia's module manager option to WLPM as an alternative to my MM with the remote download option.
Also it's possible to implement Almia's MM as a dependency inside the WLPM MM without updating the code base :)
 
Last edited:

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,446
Although this is well-designed, neat and functional, it has been collectively decided that this does not merit itself as a dependency when:

1) Its aim is to handle dependencies in the first place.
2) It still requires consideration to where script is positioed in the Trigger Editor when dealing with scripts initialized in the main Lua thread.
3) There are existing tools such as Ceres, TypeScript2Lua, Wurst, WLPM, which allow the code to be compiled by such third-party tools.

It would have been more ideal were Blizzard to have implemented module/require functionality into their Lua build. Without it, the closest "match" to what this system does (and they are not even similar) is Global Initialization, if not just for the sake of postponing certain types of initialization until after the Lua root has transitioned into the main/InitBlizzard stage.
 
Status
Not open for further replies.
Top