Storing services in _G

Typing out services is a pain, so I store the services I need in a global dictionary that all scripts and modules use. Like so:

Script:

shared.Services = {
    Players = game:GetService("Players"),
    ReplicatedStorage = game:GetService("ReplicatedStorage"),
    RunService = game:GetService("RunService"),
    ...
}

Module:

local Players = shared.Services.Players

Is this an acceptable use of global variables? How else can I address this?

1 Like

tldr: I do it the same way but with a module to avoid the global scope of _G

Sure, it’s acceptable, and functional. However there are certainly more efficient ways to do this, especially if you end up referencing those services without caching them at the top of your script (which you are, but hey)

I personally use a ModuleScript for this, see:
image

This puts all my useful services in a single table that I can very easily require in any script I need, without going through the global scope of _G.

local Services = require(game:GetService("ReplicatedStorage"):WaitForChild("Services"))

I still have to use :GetService, but only once, not 20 times.

I can then, as you are with shared, index my services by doing Services.Players, Services.RunService, etc.

4 Likes

I don’t really see what the difference is.

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Services = require(ReplicatedStorage:WaitForChild("ServiceList"))

local Players = Services.Players
local ServerStorage = Services.Serverstorage

-- or

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Players = game:GetService("Players")
-- ...

I’m curious as to why you prefer indexing them in a module over including them as local variables in the script? It seems like a fairly negligible difference in time. Auto complete handles the typing for you anyway.

1 Like

I’ve always been interested in doing things like this, and I decided that there wasn’t much point as whenever you want to access a Service you must do Module.ServiceName, and I don’t see any way that the Module can directly store it’s results as a variable under the script.
@colbert2677 just posted a really good example of what I mean.

If there is a method of directly storing the return result please let me know and I will use this!

I despise having a tower of variables at the top of my scripts, so my solution allows me to index services (among other useful references stored in another module) whenever I want, with one simple local variable at the top. like this:


local Services = require(game:GetService("ReplicatedStorage"):WaitForChild("Services"))

local function Foo()
    while Services.RunService.Heartbeat:Wait() do
        local doors = Services.CollectionService:GetTagged("Doors")
        local input_began = Services.UserInputServices.InputBegan:Connect(function(input,gp)

        end)
        --etc
    end
end

As you can see I can very easily safely reference many different services with only requiring one simple module per script.

Time saved may be negligible however, for me, it makes my scripts much more tidy and readable.

2 Likes

So it’s more of a preference thing than anything? I really don’t see the performance, efficiency or organisational difference which is why I’m asking. I personally like to have any service or local variable I need to be accessible at the top of my script, or sometimes for a change, have my constants accessible via a ModuleScript.

Sure, more of a preference. My original reply was to offer a possible (however small) more efficient alternative to storing services in a table inside of the global scope. (happy cake day :cake:)

1 Like

A more future proof solution would be this (converts dot notation into GetService calls, so you won’t need to update it):

-- use different names for some services
local Aliases = {
    Replicated = "ReplicatedStorage",
    Marketplace = "MarketplaceService",
    Collections = "CollectionService",
    -- and so on, append new aliases as necessary
}

-- get service with alias support
local function GetService(name)
    -- resolve aliases
    if Aliases[name] then
        name = Aliases[name]
    end
    return game:GetService(name)
end

-- return a table which implicitly calls GetService(name) whenever you index it
return setmetatable({}, {
    __index = function(_, service)
        return GetService(service) or error("Can't find service: " .. service)
    end
})

This solution will let you index all services without having to add each one to a table whenever you want to use it. You can also keep the alias support.

Just a small nice thing to have :stuck_out_tongue:

edit: for those of you who don’t care about aliases, you can cut down the code size considerably:

-- much smaller
return setmetatable({}, {
    __index = function(_, service)
        return game:GetService(service) or error("Can't find service: " .. service)
    end
})
6 Likes