Codebase involving ModuleScripts (not exactly modular) - requesting feedback

feedback
modulescript
repro
oop
engine

#1

I’ve just recently started getting into modular scripting as opposed to my former “multi-script functional coding” style. I often switch between ways of organizing my code or displaying it and it’s a constant bother for me. It doesn’t matter whether it’s a team project or an unreleased game - the way things look is a constant leech that takes away from my productivity (and I’m trying to ignore!).

For the purposes of this thread, I’ve created a repro of the way I’m scripting right now for a certain project. It’s not the exact code that I’m dissatisfied with, but it’s how I’m using modules, which is why I provided the repro as opposed to the actual thing. I want to know whether I’m on the right track with modular coding or if there’s a way I can better improve my modular system. I’m also open to feedback on how to turn my system into an engine or something, rather than just a more-organized-but-still-clustered mess hall. My primary dissatisfaction is that I’m basically just using this system as a way to organize code but in practice it’s not any different than having many scripts lazily tossed into ServerScriptService.

ModularCodeRepro.rbxl (13.6 KB)

I tried a couple of methods of improving it, but they all fell flat for the most part. I won’t go into detail how they did since that’s more a topic for Scripting Support I intend to post later. As for what kind of improvements I’m seeking, I’d like to know how better I can use a modular system to run my game or if I’m fine with what I have right now. In some circumstances I’d also like to fit in some engine or library-esque modules, sort of like what’s going on in @Quenty’s NevermoreEngine.


Functional versus Object-Oriented: help me understand the purpose of it all
Splitting up code - events and listeners inquiry (limitations)
#2

Auto requiring sub-modules is a fairly odd way of doing things. Typically you should only require something when you need to use it (this clears up the namespace, is obvious what each module relies on and can give performance benefits depending on the language/implementation).

You should look at each script as a solution to a sub problem. Whenever you encounter this problem you can now require that script and use it to solve the problem. Ideally each script only solves one problem and solves that problem well. This is open to interpretation though as obviously you don’t want 100’s of scripts, the more you code the more you get an intuitive sense of how much you should split the code up.

It sounds like you might be wanting to move onto a more object oriented style of programming which encourages splitting up code into separate scripts. Here is a tutorial which attempts to introduce some basic concepts of object orientation as well as a way of writing object oriented code in Lua. All about Object Oriented Programming


#3

I previously marked your reply as a solution, but I’ve kind of gotten curious to this so I’ve unmarked it after some thinking. It’s been a good pointer but all in all I’m still quite lost.

What exactly is odd about automatically requiring modules like this? With this style, I have a bunch of modules set up to handle a good portion of game functions rather than having modules take on smaller, isolated parts of code. I know there’s been styles such as OOP but what of this?

What is a “good” way of splitting my code up? Either way, with the rate I’m going, I’ll either end up with tons of modules, tons of scripts or both. Are you able to explain more about these “solution to subproblems”? I don’t know whether this is literal or not, but I don’t think I need a module for every subproblem because then it becomes a game of making code unnecessarily fancy. One module only working on one subproblem rather than being versatile to several chunks doesn’t sit well with me.

As for your last point, yes I think you may be right about that. What I’d like to aim to do is keep my coding neat and organized, despite the fact that really no one will see my code, because it’s personally beneficial in the long run to learning and working on group projects. OOP is certainly something I understand well but how would I apply it in a modular environment properly while trying to run a game?

Thanks in advance.


#4

First of all I would like to say that there isn’t anything strictly wrong with doing that. It will work and it will mostly be fine. The reason I say it is odd (and I feel most people would agree) is because it is like grabbing your whole toolbox just to undo a screw so you can replace the battery in a toy. Sure your toolbox has a screwdriver in it and taking the whole thing will let you achieve what you want but you could just take only a screwdriver.

Code is essentially just a set of instructions and by auto-requiring everything you are basically saying you need every tool you have before you can begin even though in nearly every case this is unnecessary.

In Roblox you won’t run into any (significant) performance penalties from auto-requiring but it will limit you in certain ways. For example you will run into circular requires much more easily (A requires B but B requires A so nothing can run).

If you are working on a solo project and don’t expect to be sharing code with any other developers then the best way is whatever helps you work best. If you expect to collaborate with other developers then you need to be a bit stricter.

A general rule of thumb is a module should do what it says on the tin. What I mean by this is I should be able to mostly tell what a module will achieve for me just by looking at it’s name. I don’t want to come across a module which is called “Items” which handles item descriptions, spawning items, players inventories, saving to datatstores, letting players craft items, what items a player is allowed, shops etc. While all these problems are related to items they are all also distinctly different. This is what I mean by subproblems.

Now instead of having one module called “Items” I would instead use around 5 seperate modules. Along the lines of “Items”, “Inventories”, “Crafting”, “Spawns” and “Shops”. The item’s module deals with purely just items. It lays out what an Item actually is (ie it’s data structure) and how you use them in code. This also handles their descriptions, who can use them, their models etc. Note how these could be considered subproblems but are all directly related to what an item is and what we can do with them.

Now that we have an Items module we have a ready to use concept of what an Item is. Now we can implement a players inventory. In it’s most basic sense an inventory is just a table holding items. So all it has to do is require the Items module, make a table and put items in that table. It doesn’t have to worry about item descriptions or what they look like etc as all this should already be handled in the Items module. From the point of view of the Inventory module it is just putting tables (which happen to be items) into another big table (which happens to be an inventory). Now we can extend this and add cool functions to make it easy to check if the player has a certain item or how heavy the whole inventory is etc. These are also subproblems but are only directly related to the inventory.

The crafting module can now require the items module and the inventory module and have all the information it needs to be able to craft items. Hopefully by now you understand the subtle art of splitting problems into smaller subproblems but not splitting too much. I hope this also shows why auto-requiring is a bit odd. For example if we made the Spawns module (which would handle spawning items into the world dynamically so you can loot them but could also handle spawning npc’s etc) it doesn’t make any sense for it to go requiring the Crafting module. It wouldn’t stop it working but it’s also unnecessary.

OOP is so handy because it creates an intuitive way to break up our code. It encourages us to think about
our code in terms of objects and how they interact with eachother (which is also how we mostly think about the real world). So you split your code whenever you need a new object. It’s much more intuitive to think “I need cars in my game so I’ll make a car object” than something along the lines of “Players can use a car so I’ll add some behavior to my player script which will detect when a player hops in a car model and control that car etc”.

This way of thinking actually achieves the opposite of what you want. A module which solves a single subproblem is highly reusable. A recent example of this in one of my projects is the way I am controlling the camera. I wanted a smooth camera and decided to use a PID controller for this (PID is just a way of smoothly moving from one value to a target value). Now I could have just put the logic for this into my camera script and be done but because I didn’t I can now use it to smooth other things such as the steering on my car or custom replication of players positions.

Because this module does one specific problem well, it can be used by many different scripts and is very versatile.

Hope this explanation was helpful for you!


#5

You know the funny thing is that I actually almost ended up making a circular require while redoing my networking module, which failed miserably. The parent module was holding methods and requiring the children, but the children weren’t getting anything from the parent. That’s part of what drove me to make this request, aside from the fact that I feel like I’m using modules either wrongly or awkwardly. I feel like it should be more an OOP-kind of thing and representative of your toolbox analogy.

All in all though, I think I have a better understanding of it though. It’ll certainly take a bit of time to get the hang of, but it was an insightful post. Thanks for the help.


I’m still open to receiving any input or tips on modules, or feedback on my practice in general around modules. I’d love to start incorporating modules fully into my works once I adapt better practices and methods. I’m keeping this style isolated to one project right now so I don’t catch other groups into this odd loop.

I have to say though, this is better than my previous attempt at a modular system which broke an entire game. I ran a whole game off of 5 modules and that was a disaster, trying to cram in things.


#6

My advice to you is to stop and carefully consider why you are opting to use ModuleScripts. Think of their advantages and disadvantages compared to ‘doing it all in one Script’ or spreading your game logic across multiple Scripts / LocalScripts. This is a good point to note that simply using ModuleScripts as source containers doesn’t mean you have a modular system.

As you noted in your original post, with the way you have it currently set up, your ModuleScripts could equally just be regular Scripts/LocalScripts because you are not referencing the return values from requiring the modules. ModuleScripts are a great way to package together reusable code in a logical and coherent way (e.g. a library of functions that relate to doing certain game operations), but you should be careful not to make a whole game process run ‘in’ a module, because you’ll end up having trouble passing in and getting out values from other scripts or modules.

You’ve mentioned the Nevermore Engine. That is a great place to start your research into well-planned modular systems that take advantage of the functionality of modules: it should introduce you to concepts such as dependencies and help you understand how you can avoid things such as ‘circular requires’.


#7
  1. There is no reason to put ModuleScripts in ServerStorage** or to use WaitForChild on them. ServerScriptService is the conventional location, and a server script in ServerScriptService does not need to wait for the modules. ModuleScripts don’t execute until required, so you can just move your scripts folder in there.

  2. You generally want to return a value from a ModuleScript, and use it. Though a bit unusual, there is nothing inherently wrong with requiring things in order to execute them at a specific time and discarding the return value, but there should be another require somewhere in your code that actually uses the return value, otherwise you’re not making use of the main benefit of ModuleScripts.

  3. In a real game, Modules often have dependencies and need references to each other or the instances they are used to create. This structure, while “modular” in the organizational sense, gives no indication that there is going to be any kind of OOP or functional pattern implemented, or how. As-shown, there is just an include chain of modules that are all isolated, run-once code chunks that don’t communicate with each other.

My recommendation would be to absorb everything you can learn about the utility of ModuleScripts, and design patterns commonly used in Roblox games, and let necessity guide your hand in structuring things as you build your game. Arranging empty ModuleScripts without clear motivation for why or what problems the arrangement solves is fine for just seeing how things work, but you shouldn’t expect to have any amazing design breakthroughs until you put it to the test making a game.

**Side note, there is a valid reason to put ModuleScripts in ReplicatedStorage, when you want to guarantee that client and server execute the same logic, or use the same copy of shared data.


#9

Based on the feedback above, I’ve applied a new system to my work. I’m still hoping for a reasonable split between reusable and normal code (modules and regular scripts), as well as OOP and functional code. I’ve also taken a bit of a brief stroll through the new PlayerScripts and have been learning a bit of how they work.

At the moment, I am currently splitting my code into two folders in ServerScriptService: Modules and Scripts. My codebase won’t yet extend enough to that of an actual game since it’s more of a group institution, but I feel like this divide should suit my needs.

image

The modules folder shouldn’t need to fetch anything from the Scripts folder, but it may have to require another module. The scripts folder may access a module to fit its needs (if necessary, I’ll create a module that facilitates the retrieval of other modules). I’m also switching to OOP so modules are only acting when they need to as opposed to auto-running; auto-ran code will be in the Scripts folder.

I’m not sure if this is a good idea, but I’m also keeping all my RemoteEvents and RemoteFunctions in a single folder called “Remotes”. This is not necessarily related to the original topic posed in OP, just a side remark if it holds any bearing on the system I want to achieve.

Here’s example code for a module that may be found in modules (unfinished Remote class, I guess - should be in ReplicatedStorage with a check if it’s being ran from the client or server):

--[[
	Intended to register and work remotes, primarily.
	
	colbert2677 - November 2018
--]]

local Remotes = game:GetService("ReplicatedStorage").Remotes
local RemoteEvent = {}
RemoteEvent.__index = RemoteEvent

function New(Name, Function)
	local Remote = Instance.new("RemoteEvent")
	Remote.Name = Name
	Remote.Parent = Remotes
	return Remote
end

function Get(Name)
	local self = setmetatable({}, RemoteEvent)
	self.Actual = Remotes:FindFirstChild(Name) or New(Name)
	self.Connections = {}
	return self
end

-- **Reimplement RemoteEvent methods

function RemoteEvent.ConnectEvent(self, ConnectionName, Function)
	self.Connections[ConnectionName] = self.Actual.OnServerEvent:Connect(Function)
end

function RemoteEvent.DisconnectEvent(self, ConnectionName)
	if self.Connections[ConnectionName] then
		self.Connections[ConnectionName]:Disconnect()
		self.Connections[ConnectionName] = nil
	end
end

return setmetatable({}, {
	__call = function(self, Name)
		return Get(Name)
	end,
	__newindex = {},
	__metatable = "Locked",
})