Dependencies between packages or free models

I have made packages containing ModuleScripts. I’ve also uploaded two of them (RagdollSystem and GeneralUtility) as free models and made a devforum topic about RagdollSystem. RagdollSystem is dependent on GeneralUtility and RemoteEventSystem, and RemoteEventSystem is dependent on GeneralUtility.

Currently, I have a copy of GeneralUtility in RemoteEventSystem and a copy of both GeneralUtility and RemoteEventSystem in RagdollSystem. I wrote the dependency handler modules of RagdollSystem and RemoteEventSystem such that if there’s a copy of a needed package as a sibling of the package, they’ll use that copy instead of the copy inside the package. So for example, if RagdollSystem has a GeneralUtility package as its sibling, it will use that instead of its nested GeneralUtility package. I made this sibling option because I think it’d be ideal that only one copy of each package would be used per game.

However, I don’t like the requirement of having the required package as a sibling. I think it’d be better if I could allow the user of any of the packages/free models to store each of them wherever they want, especially if my packages will later be dependent on third party packages that may be used for other purposes in the same game as well.

Here are some ideas I’ve considered.

  • ObjectValues: I was thinking of adding an ObjectValue for each needed package. However, changing the Value property of an ObjectValue marks the package as modified.
  • Configuration string attributes: I’ve considered having string attributes in the top folder of the package. Each of these string attributes would tell the instance path to a package. Changing these wouldn’t mark the package as modified which is good.
  • Searching in the instance hierarchy: I’ve considered using ReplicatedStorage:GetDescendants() and checking which descendant has a package link with the correct PackageId. This could be quite inefficient if there are a lot of Instances in ReplicatedStorage. Also, if there are multiple copies of the same package, the package this search give isn’t necessarily the one that was supposed to be chosen.

None of the options I’ve considered feel very elegant so I’d like to know if there’s a better way.

Solution is to not rely on dependencies, make your codebase self-contained.

I would also agree with this suggestion.

Do you mean that my packages should use a nested copy of each package they depend on which is what they already do as a secondary option? This way, the package contains its dependencies while still having a clear definition of what it’s dependent on. Or do you mean that I should stop reusing functions and instead implement similar code separately in each package? I’m definitely not going to do the latter.

Why I don’t like the former approach much either
The former approach may not be problematic if the packages that other code depend on don’t have state and don’t automatically modify the state of the game (make changes to Instances for example). However, if a package has state stored as Luau tables or if it changes the state of the game, there may be problems when there are multiple copies of the package.

For example, let’s say the server side code of a package creates a RemoteEvent with a specific name in ReplicatedStorage and the client code uses FindFirstChild or WaitForChild to get a reference to this RemoteEvent. If there are multiple copies of this package and each of them creates their own RemoteEvent, the client may connect its handler function to the RemoteEvent of the other copy of the same package which will break the behavior of one or both package copies. I’m pretty sure the only way to ensure that the client has a reference to the correct instance is by sending the instance via a RemoteEvent. That doesn’t help when the RemoteEvent is exactly the thing we are trying to get a client reference to. This problem could be solved by not creating a new RemoteEvent in the server code if there already is a RemoteEvent with the correct name. However, each of those package copies still has its own Luau state which may cause problems.

Also, what if a package that is used for handling RemoteEvents has an event that fires every time any RemoteEvent handled by the package is fired? The game developer might want to track the whole game’s RemoteEvent traffic and if there are multiple copies of the package, the developer needs to make a connection to the event of each of them.

There could also be a package that has features for utilising two packages together. Other code in the game in which that package is used should probably be able to access data of the the same copies of the two packages that the package using both of them accesses.

Some reasons why I want to keep using package dependencies
I feel like it’s more organized to not have everything in one package. If a specialized package, like RagdollSystem, needs to do such a general thing that the same kind of thing with different data might need to be done in other unrelated code, it feels more logical to me to write a general purpose function for it in a separate package even if I don’t have other code needing that function at the moment.

The reason I am making packages is that I wanted to finally find a practical way to reuse code across projects. My earlier projects didn’t have a proper way of reusing code. Instead, I rewrote or copy pasted code. The packages I mentioned are some of my first Roblox packages and I think I started writing them at around the same time last year. I have been writing the packages simultaneously. Whenever my other packages have needed code for something that feels too general purpose to be implemented in a specialized package, I have added it to GeneralUtility. So the functions and data in GeneralUtility are mostly things that were written because they were needed in a package that uses GeneralUtility.

The purpose of these packages is to become a modular feature rich framework that contains both general purpose packages usable in many kinds of projects and more specialized packages usable in certain kinds of projects. In a project that doesn’t need ragdolls, GeneralUtility can still be useful. Dividing code into packages with clearly defined one-way dependencies makes it easy to reuse some packages without having to also add the packages that depend on them. If all the code used for something was in one package or not in a package at all, it wouldn’t be as clear which code needs which other code.

Admittedly, when a package uses another package that has more features than the dependent package needs, that results in there being unused code. However, I think unused code is a less significant problem than duplicated logic. I don’t think the memory used by that unused Luau code is usually significant unless there’s a huge amount of such code. The engine has a much larger codebase and a specific game only uses specific parts of the API so there’s already a lot of unused engine code taking memory and Luau code is quite insignificant compared to that.

Code should be maintainable and extendable. Centralizing and reusing logic makes updating easier. Also, rewriting the same kind of code multiple times means that you may sometimes forget some details, for example handling some special cases, and that may cause problems that could be avoided by using code that you’ve already used and found to work correctly.

Of course, one problem with making reusable code is that you may not be able to include some optimizations that you could include in code that is written for a specific purpose. However, it’s important to consider how significant that optimization is. If the code is only ran occasionally, and it’s not a big optimization, sacrificing reusability for it is probably not worth it.

At least when the dependencies are my own packages, package versions shouldn’t be a problem as long as I update the dependent packages when I make such changes in a dependency that they affect the dependent package.

1 Like

I understood what you want, you want to make a reusable framework that you can use in multiple games and updating the framework should update ALL games using your framework but games can still have their own state/have a different codebase. I’m afraid that’s not possible within Roblox

PackageLinks have an auto-update property which allows me to easily keep my packages up to date in different projects that I own as long as the copies in different projects aren’t modified, although this property doesn’t work in nested packages.

I know I can’t auto-update other people’s games that use my packages (the free model versions of them which can’t auto-update). And I don’t consider that a problem. Actually, I consider it a good thing overall as it prevents other people’s games from suddenly breaking because of a change in my packages.

My main goal with packages is easy reusability and consistency. An example of this is having a package for RemoteEvent handling so that it can be done in the same way in different packages instead of them all doing it differently.

The problem is that I don’t know any practical way to add a configurable reference in package A for a package B copy outside A such that changing the reference doesn’t mark A as modified.

Changing a part reference of a Weld inside a package that references a part outside the package doesn’t mark the package as modified so I wonder why changing the instance reference of an ObjectValue that references an instance outside the package does mark it as modified.

If Packages treated ObjectValue.Value changes the same way as they treat Weld.Part0/Part1 changes, or if Instance attributes were added, I would have a solution for this dependency reference problem.

1 Like