Which is why any module that depends upon the LocalPlayer
should be loaded from the client?
The module as a whole doesnât depend on the LocalPlayer. Only a small part of it does.
Thatâs what I said. A ModuleScript that is required by a server-side script, ever, should probably not change behavior when required by a LocalScript. Like, I just donât see an example where Robloxâs API is lacking rather than there might be a smarter way to do whatever it is that leads you to needing this.
Itâs easy to argue that 0% of it should.
Please read the previous posts. This and my last two responses to you are duplicates of whatâs already been posted.
Test servers are not suitable because they are slow to start up and I am not going to increase the time it takes to test by 10x every time I want to check for issues.
So youâre saying this functionâs primary purpose would be to avoid Start Server/Start Player?
Not avoid it completely â it has its uses â but yes. I want to catch issues involving this during rapid testing (Play Solo) rather than finalizing the build for publishing. Test servers are great for hunting down problems when you know of an issue involving client/server or for final testing, but less so for every other time a game is tested.
Why not put anything that is client-dependent in StarterPlayer.StarterPlayerScripts
and then just know nothing outside of StarterPlayer.StarterPlayerScripts
should ever require anything from there?
Because itâs more of a mess to strew connectors to shared utilities across the game hierarchy than to encapsulate in the shared utility.
This is not coming from someone who jams every script and module into Workspace. I have my client-only modules in ReplicatedStorage>Client, shares in ReplicatedStorage>Shared, and server in ServerStorage>Server. As someone who already segregates client/server as much as reasonably possible, I am telling you itâd solve less problems than itâd cause to set up the hierarchy the way youâre suggesting, and the entire point of segregation is to prevent problems â not cause them. Design is not a science, and there is no rule or guideline that holds true through every scenario â I encourage you to reflect on othersâ experiences and that you may think thereâs always a better way only because you havenât yet come across an instance where that wasnât true. I, myself, made the same suggestion as you but have since turned in the opposite direction because I experienced a case where there wasnât a better way.
Thank you for this example. I now understand the problem.
There is one hard fact: a peer can be both a server and a client (play solo). Therefore, modules should be structured with this in mind from the start. In order to correctly handle the unusual but still possible case where a peer is of both types, the module must have one endpoint for each peer, so that they do not conflict. Differences in the code can be generalized, or common code can called by both endpoints.
local module = {}
if IsServer then
function module.ServerEndpoint()
commonCode()
serverCode()
moreCommonCode(generalizedServerData)
end
end
if IsClient then
function module.ClientEndpoint()
commonCode()
clientCode()
moreCommonCode(generalizedClientData)
end
end
return module
Remember that this only applies where there are differences. When the behavior is the same regardless of peer type (PrintTable
), one endpoint is sufficient.
Itâs a terrible hack and you shouldnât do it, but itâs possible to use getfenv to query the âscriptâ variable and see what kind of script it is.
function GetScriptContext()
local env = getfenv(0)
if type(env) == "table" and
typeof(env.script) == "Instance" and
env.script:IsA("LuaSourceContainer") then
return env.script.ClassName
end
return ""
end
getfenv(0) returns the global environment from the bottom of the stack. Since each script has its own global environment, it will return the environment of the script that began the call stack.
Once again, do not do this.
I donât want to know whether the module will work for a peer of both types â that defeats the purpose of testing because no in-game clients are both. Iâm testing because I want to make sure it will work in-game.
I feel like Iâm missing something. Iâve used shared modules for a long time and never had a problem with LocalPlayer not existing.
You are missing something â the experience of encountering an instance where what you suggested didnât work. Not everyone works on the same projects â just because you havenât encountered it doesnât mean it doesnât exist.
Wasnât saying itâs not happening to you, Iâm frankly shocked it hasnât happened to me, doing pretty much exactly what is said in this thread.
I do not understand how waiting for the local player will ever help find erroneous code on the server. If LocalPlayer
is equal to nil
, then you know that youâre on the server. Anything in the context of a LocalScript
will only load after LocalPlayer
has been set.
So, if youâre sure the best way to find out if your code is broken is to see that the local player is there, just simply check for Players.LocalPlayer
.
I think this is wrong, and despite your claims that this is a big problem that I just havenât encountered, you have yet to offer an example that actually proves that point.
LocalPlayer is not nil on the server in Play Solo.
It is set to nil
before the player loads.
Nevertheless, Players:WaitForLocalPlayer()
does not solve your problem. Like, at all. In Play Solo, it would just return the Player
object after a possible yield.
Iâm very tempted to do that
The only problem I see in this thread is the usage of Play Solo when starting a server and a player is the obvious solution.
I think this feature request is much too niche to be added to the API. Thatâs just my opinion.
Iâm not telling you to handle the specific case where a peer is both a server and a client, Iâm telling you to structure your code such that, if it works in play solo (server and client), then it will inherently work in-game (server or client).
Donât do it.