Multiple Scripts, or Multiple Module Scripts with two Scripts

Hello. On the DevForum I have read quite a few of articles regarding architecture, one architecture that has popped up a lot is a architecture where you have one localscript, one server script, and then a bunch of modules to do work. My question is, would it be better to use this architecture, or just multiple local scripts / server scripts?

1 Like

I definitely recommend the 1 LocalScript and 1 ServerScript approach for large games. Maintaining copies of the same script all over your game is just awful. Many independent scripts are only really useful when portability is needed, like for free models. Clearly organized ModuleScripts run more predictably and are just easier to maintain imo.

7 Likes

Yes, it’s simply better to have one deterministic point of access per computer, it makes absolutely no sense to do otherwise. This is how almost all imperative programming languages work. Most scripts you have in your game will need to communicate with each other anyway, which modules help with.

Any codebase without this approach is probably a bad one. There’s just too much communication going on in almost every game, and event-based approaches are good but you can’t use that for everything.

2 Likes

I still don’t see how one localscript and one serverscript with modules is more beneficial than multiple local or server scripts. The only time I ever use modules is when I need to reuse the same code across different scripts. Also, what do you mean by “maintaining copies of the same script”

1 Like

Because you don’t have a deterministic point of entry when you’re using multiple Scripts and multiple LocalScripts, meaning you can get complicated concurrency bugs that are hard to identify.

When you work on full games, there’s so much communication between scripts that there’s very little you won’t need to call into. Modules make sense.

1 Like

I’m not that good with terminology, what do you mean by a deterministic point of entry? I’ve seen this terminology pop up in some posts I’ve read on here.

Deterministic as opposed to random, where you have no control over which Scripts and LocalScripts run first.

The main advantage is really for communication purposes, which I can’t stress enough. Games with many Scripts and LocalScripts have bad architecture mainly because of this.

Why would this be a problem though? When I’m developing, I don’t really need to have control over which scripts or localscripts run first.

1 Like

Because you don’t want concurrency bugs. You do not ever, ever want there to be a bug based on which code runs first. Tracking it down will be a nightmare. You’ll get bug reports that are confusing, and they’ll be hard to replicate.

If you ever have to make a decision between X and Y, where X is deterministic, and Y is “random”, you always want to pick X unless there’s a clear benefit to Y, like ergonomics or performance.

1 Like

I’m so confused, what do you mean by this? When I have multiple scripts, and I have a bug, I go to the script that I know is affiliated with the bug and fix it. I’m just really confused right now lol

Let’s say you have two Scripts, A and B. Commonly, you can have a bug if A runs first and then B runs second, but not if B runs second and A runs first. Worse, maybe A runs first 99% of the time, so only 1% of the time do you get this bug, and then you add in Script C. Maybe Script C being added changes the behavior of the scheduler, which is effectively random from your point of view, and now C runs, then B, then A. Now, you have the original bug where B runs before A, but because this only changed after adding C, you think it’s a problem in the code of C. Now the original bug which only happened 1% of the time could happen about 100% of the time. The bug happening 1% of the time in the first place is also a problem in it of itself.

It’s really easy to write a hundred or two lines of code and have it just “work” and be utterly confused about why the best practices are the best practices. This won’t cut it on projects of reasonable complexity and size.

I don’t understand how my chances of a bug appearing is decided upon which order the scripts run in.

3 Likes

As a lot of people have already said, I just want to confirm that yes, you should keep framework scripts to a minimum and have the modules do the heavy lifting. Usually you do this by providing some sort of API or class(es) for the framework to interact with as well as possibly some loading and update functions to be used in a main loop for example. But don’t go overboard by putting every single thing in its own script. A good example would be (for a car game)

Server Framework
    Vehicle API
    Vehicle Manager
        ...
    Save Manager
        ...
    Shared
    ...

And make sure that if you do include other modules multiple times that you keep them separate and don’t let them interact with one another because you could end up creating a loop where two modules require each other in order to function.

1 Like

If multiple scripts depend on an object or another script to exist before it runs then you will have an issue.

Lets think of a case where I apply a tag to a part (script A) and then my other script looks for all of the relevant tags (script B). If script A runs before script B then the run was successful. But if script B runs before script A then no parts with relevant tags would come up.

Of course this is a slightly cherry picked case (there are better ways to do something like this) and a better case might be found through random trial and error when just working on a project.

3 Likes

Say for example you have an car with a car script inside, and you put it in ServerStorage so it can be placed in-game. Now you want to have another type of car, so you simply copy the previous car along with its script, edit the appearance, and place it in ServerStorage. Now you have 2 of the same script that you need to maintain. If you get around this by cloning scripts into specific cars, then you could instead just be initializing them using ModuleScripts.

It’s important to note that Scripts will automatically disconnect and cancel threads when destroyed, but you’ll need to keep track of connections manually when creating complex objects. Also, if your car script creates temporary instances elsewhere, those instances will leak if the script is destroyed before it has a chance to destroy those objects. If you keep track of connections manually, you can simply remove the temporary instance when the car is explicitly removed and disconnected.

There isn’t an easy way to get around this problem with threads though. You’ll run in to all kinds of problems if you disconnect mid-wait(). The only way around this right now is to create some kind of custom scheduler (or just connect to heartbeat or stepped until the time has passed).