Require(): Large Volumes of Modules and Many Copies of One Module

I have some really bad code to create all the bullets for each player’s gun in a game I’m working on. Because each weapon has a lot of settings that make it unique, the old code sends a lot more across the RemoteEvent than it needs, and often overflows the 50kb bandwidth between client and server. I have thought of a possible solution, but I want to make sure it isn’t just as bad before I waste tons of time making a broken solution work.

What happens when potentially dozens or hundreds (over the lifetime of the server) of copies of a ModuleScript get cloned and then required by the server script that processes bullet events?

I guess the main thing I don’t know is how other scripts interact with ModuleScripts. such as:

  • Yes Modules are only run once, yes scripts that require() them later can use them much like a function. But, do scripts store a reference (like a BindableFunction might) to these modules, or is it something something with more intimate access to the code itself?

  • If two instances of a module have identical code, is each one run when it is created or does it refer back to the original Module’s code?

  • If a previously-required module is destroyed, will any memory stick around until the requiring script is also destroyed? If such a memory leak does happen, will it slow down the game or waste extra memory space?

If I could get some explanations to those questions, I would really appreciate it.

I think you should try and focus on getting the server to determine more and the client to send less. Why are you sending the shape and color of the bullet through a remote event? More importantly, why is the client telling the server the damage multiplier? The server can and should be able to handle all that stuff on it’s own.

5 Likes

Really all you need to send to the server is the point where the bullet begins and direction it is going. Everything else is just a waste of valuable bandwidth

4 Likes

That was not even part of my question…

I do know it’s a bad idea, the current solution my gun package uses is from when last July’s FE scare happened. Why do you think I started by saying, “I had an idea to fix what I’m doing right now”?

My question was about how a server script would potentially be affected by requiring dozens or even a couple hundred ModuleScripts throughout the lifespan of a server.

But what we’re saying is why do you even need to require module scripts to get that data? Is it a lot or could you just store them in Values inside of the gun.

To answer your question though. If your module scripts being require()'ed by the server are mostly constants (bullet size, color, etc.) then my intuition says that you shouldn’t have any problems as long as you discard data properly (no memory leaks). However I haven’t done anything on that scale before so I can’t confirm it.

It is a lot of settings. Not enough that it could only be in a Module, but there are also a lot of local settings that the gun uses when initializing, so it doesn’t make sense to split similar information between two locations.

As for discarding data, what should I keep in mind to avoid memory leaks with Modules? Will the automatic calls to destroy a Character and the player’s Backpack items be sufficient to disconnect the require() call for instance, or how specific would I need to get if disconnect() calls are needed (is it enough to disconnect the require() call for instance)?

Thanks for the clarification.

I think you might be a little confused. require() isn’t an event like .Touched or .OnServerEvent, it’s simply a function like math.random() or any other function you can create. The thing that separates require() from other functions is that it’s the only way to run code in a module script. For example:

local currrentWeaponData = require(script.WeaponData) -- returns a table of weapon constants
-- run code until the weapon is destroyed
currentWeaponData = nil -- the currentWeaponData variable before this line points to a table, 
--but when you set the variable to nil that pointer is lost.

In order to remove a table from memory, you simply remove all pointer variables to it, and it becomes garbage collected(removed from memory). A memory leak occurs when you incorrectly remove all pointer variables to that table and while you may think that table has been long garbage collected, it’s actually taking up memory somewhere (and possibly slowing down your game).

This is just a simplistic overview of memory leaks and there’s more to know about memory leaks. However, I don’t know enough about memory leaks and garbage collection to accurately tell you everything about it. My point is that if you’re at some point leaking memory, it’s almost always fixed by changing your script and not reworking your entire gun structure.

[…] do scripts store a reference (like a BindableFunction might) to these modules, or is it something something with more intimate access to the code itself?

I’m not really sure what you mean, but all using require on a ModuleScript does is

  1. If the ModuleScript hasn’t been run before, then run its code and pass some return values
  2. Otherwise, return the same thing as before

It has nothing to do with references to the ModuleScript or access to the source code, if that’s what you’re asking.

It should be said that any return values that are passed by reference (tables, functions, threads, and userdatas) will be shared across scripts that are on the same computer.

If two instances of a module have identical code, is each one run when it is created or does it refer back to the original Module’s code?

If by “two instances of a module” you mean two ModuleScripts, then yes, each one is run independently because they are different objects, regardless of whether or not they have the same code.

If a previously-required module is destroyed, will any memory stick around until the requiring script is also destroyed? If such a memory leak does happen, will it slow down the game or waste extra memory space?

The only cases I’m aware of where this can happen is if you create new threads or connections in the ModuleScript. In that case, destroying the requiring scripts in addition to the ModuleScript has no effect, and they won’t be picked up by the garbage collector as long as they remain active. In the case of a thread, it runs until completion or until it errors before being collected. For connections, they last until Destroy is called on the associated object, or until manually disconnected (if you still have a reference to the connection, that is). Either case is potentially disastrous if you’re creating hundreds of them, although results may vary.

There’s also the case of user-created userdatas (via newproxy), as well as if you’re storing any data in _G. I can’t really imagine why you’d be using either of those, though.

2 Likes