I have been creating a simple module loader and I have made a service that uses it. It has some local variables that I need in other scripts, but I’m not sure how to make those accessible to them. In this case I have to make the “world” variable public to other modulescripts.
local RunService = game:GetService("RunService")
local Matter = require(game:GetService("ReplicatedStorage"):WaitForChild("Shared"):WaitForChild("Modules"):WaitForChild("Matter"))
local WorldService = {}
function WorldService:Init()
self.world = Matter.World.new()
self._loop = Matter.Loop.new(self.world)
for _, child in ipairs(script.Systems:GetChildren()) do
if child:IsA("ModuleScript") then
table.insert(self._systems, require(child))
end
end
end
function WorldService:Start()
self._loop:scheduleSystems(self._systems)
self._loop:begin({
default = RunService.Heartbeat
})
end
return WorldService
I need full intellisense support, so I need to explicitly declare the type somewhere. Anyone know how to do this? Also, I don’t want to make a getter function
I can do something similar to this so that autocomplete works, but then roblox still does not have any idea what WorldService.world’s type is.
local RunService = game:GetService("RunService")
local Matter = require(game:GetService("ReplicatedStorage"):WaitForChild("Shared"):WaitForChild("Modules"):WaitForChild("Matter"))
local WorldService = {}
WorldService.world = nil
function WorldService:Init()
self.world = Matter.World.new()
self._loop = Matter.Loop.new(self.world)
for _, child in ipairs(script.Systems:GetChildren()) do
if child:IsA("ModuleScript") then
table.insert(self._systems, require(child))
end
end
end
function WorldService:Start()
self._loop:scheduleSystems(self._systems)
self._loop:begin({
default = RunService.Heartbeat
})
end
return WorldService
It sounds like you need to define a type for WorldService.
type WorldService = {
...
}
local WorldService = {} :: WorldService
The typecast would remove the need to initialize world right away but when you access it from other modules, intellisense should still know what the type should be.
Actually, while we’re at it, is there any reason to differentiate between private and public OOP fields in Lua? In Java it is considered to be essential to do that, but here we trust the client to be responsible enough.
We do trust the client to be responsible since there’s nothing stopping them from accessing any variable in the module, but it doesn’t hurt to be explicit with naming conventions.
For me personally, I like to prefix “private” variables with _ (e.g., _world). It lets others and even myself in the future know that we probably shouldn’t reference that variable directly. But that’s just a personal preference and if you find something that works for you, stick with it.
Yup, these are some pretty good reasons. It’s also very hard to keep track of things you put in _G and where they come from. In other words, it’s disorganized.
Edit: Modules are sort of their own global table. The require function always gives you the exact same table, which is the module being required. But if you are organized properly you shouldn’t need to use global tables
Hello again, it seems like intellisense doesn’t work for this solution when using the self keyword. It, for example, doesn’t know what the type of _loop is.
function WorldService:Start()
self._loop:scheduleSystems(self._systems)
self._loop:begin({
default = RunService.Heartbeat
})
end
I could replace “self” with “WorldService”, but I’d rather prefer not to.
local RunService = game:GetService("RunService")
local Matter = require(game:GetService("ReplicatedStorage"):WaitForChild("Shared"):WaitForChild("Modules"):WaitForChild("Matter"))
type WorldService = {
world: Matter.World,
_loop: Matter.Loop,
_systems: {{}}
}
local WorldService = {} :: WorldService
function WorldService:Start()
self.world = Matter.World.new()
self._loop = Matter.Loop.new(self.world)
self._systems = {}
for _, child in ipairs(script.Systems:GetChildren()) do
if child:IsA("ModuleScript") then
table.insert(self._systems, require(child))
end
end
self._loop:scheduleSystems(self._systems)
self._loop:begin({
default = RunService.Heartbeat
})
end
return WorldService
So since functions are technically just keys within the table, you need to also include those functions in the WorldService type. For example:
type WorldService = {
world: Matter.World,
_loop: Matter.Loop,
_systems: {{}},
-- function types
Start: (WorldService) -> ()
}
Note that since Start is a colon function and not a dot function, you need to include WorldService as the first parameter type. This should let the intellisense know that “self” refers to a WorldService type.
Edit: In case you’re interested in learning more about the type check system, here’s a solid resource that covers mostly everything you’d want to know: Type checking - Luau
Correct me if i’m wrong, but I think the best way to do this is to just have a literal local variable.
local variable = 1
local function GetVariable()
return variable
end
There isn’t really a point in using self in a script like this. In fact, it’s just fancy syntax sugar. It’s actually detrimental because it does not support easily support intellisense. That, or Roblox needs to improve the autocompletion to not be a constant pain
Yea, that’s definitely a solution to keeping internal variables private and exposing exactly what you want, but wasn’t this what you wanted to avoid initially?
Also, I’m curious to know what you were having trouble with getting intellisense support to work. I agree it can be a bit of a headache, but I haven’t really ran into a situation yet where I absolutely couldn’t get the type system to work the way I wanted.