Reforged Modules
Goobstation provides a way to isolate your code into separate, custom modules. These custom modules retain full functionality like the core modules, while solving the problem of having to recompile the entire game when making changes specific to your downstream.
Why
As stated above, there are two primary reasons:
-
Streamlined Testing and Deployment: By separating downstream-specific code from the rest of the game, you can compile and test your custom features independently.
-
Enhanced Modularity: Instead of managing files and folders within the already bloated Content.Client/Shared/Server projects, you can organize your work into four distinct, fork-specific projects.
This approach is specifically designed to accommodate Robust Modularity should it be implemented in the future.
Creating new modules
Goob Reforged repository has a script to automatically create a module and connect it to the game in /Tools/create_module.py. Launch it through a command line, specify the name, and you have your module set up and ready!
Terminology
-
Core Module: Modules belonging to the base space-station-14 repository. These include
Content.Client,Content.Server,Content.Shared, their dependencies likeContent.Shared.Database, andContent.Common. -
Custom Module: Modules that are part of the Client or Server process but don’t belong to Core Modules. They are located in the
/Modules/folder and have an infix. -
Engine Module: Modules that are part of RobustToolbox (the game engine). These are prefixed with
Robust.. -
.Common: A module type intended to be shared between Custom and Core modules. These hold interfaces, components, and other types that both module types need to share.
Content.Commonis located on top of all other.Commonmodules. -
Infix: The module identifier, which is the middle part of a module name. For example, “Goobstation” in
Content.Goobstation.Common. -
Postfix: The special module identifier, which specifies its special role, for example
.Database. Read Special module projects for more details.
How
Module manager
The client loader, unless configured otherwise, loads every Assembly from the Assemblies folder with a specific prefix.
This prefix can be defined in the manifest.yml file and defaults to Content..
By creating new projects with the Content. prefix, you can have the game automatically load them at startup alongside the core modules.
Dependency tree
Here’s an example of the module dependency tree for Goobstation modules:
flowchart TD
CoreServer[Content.Server]
style CoreServer stroke:#6b9bb3
CoreClient[Content.Client]
style CoreClient stroke:#6b9bb3
CoreShared[Content.Shared]
style CoreShared stroke:#6b9bb3
CoreCommon[Content.Common]
style CoreShared stroke:#6b9bb3
GoobServer[Content.Goobstation.Server]
style GoobServer stroke:#3498db
GoobClient[Content.Goobstation.Client]
style GoobClient stroke:#3498db
GoobShared[Content.Goobstation.Shared]
style GoobShared stroke:#3498db
GoobCommon[Content.Goobstation.Common]
style GoobCommon stroke:#3498db
ModuleServer[Content.Modules.Server]
style GoobShared stroke:#3498db
ModuleClient[Content.Modules.Client]
style GoobCommon stroke:#3498db
subgraph CoreModules["Core Modules"]
CoreServer
CoreClient
CoreShared
CoreCommon
end
style CoreModules stroke:#e91e63
CoreShared --> CoreServer
CoreShared --> CoreClient
subgraph GoobModules["Goob Modules"]
GoobServer
GoobClient
GoobShared
GoobCommon
end
style GoobModules stroke:#e91e63
GoobShared --> GoobServer
GoobShared --> GoobClient
GoobCommon --> CoreShared
CoreCommon --> GoobCommon
CoreCommon --> CoreShared
CoreShared --> GoobShared
CoreServer --> GoobServer
CoreClient --> GoobClient
GoobServer --> ModuleServer
GoobClient --> ModuleClient
This means:
- Custom modules directly reference their core module counterparts.
- Core modules cannot directly access code from Custom modules if they are of the same type (Core.Shared-Custom.Shared), as this would create a circular dependency.
- If the types are the same, core modules must communicate with Custom modules through an intermediary
.Commonmodule using events. - The Common module must Not depend on either core or Custom modules and should be able to build standalone.
Module compilation
However, the module loader does not ensure that the modules actually compile in the right order.
By default, when a project is run, MSBuild will compile all references of that project first, and then run it. The issue is that custom modules depend on the core modules, so they don’t compile automatically by default!
In order to fix this, each Core project should have a special .csproj Target that waits until the project is fully compiled, and then compiles all custom modules connected to this core module. After that the results are copied into the bin folder where all other assemblies are located.
Up-to-date compilation problem
Unfortunately, the method of Module Compilation described above doesn’t solve the issue entirely. When no changes are made to any files in the core module, the .csproj Target doesn’t trigger, and modules are ignored even if they have some changes.
Because of that, when you make changes to modules and not to the core projects, you have to compile the whole solution so the changes are applied correctly. This is done automatically if you launch from an IDE.
Module organization
Modules are highly recommended to be independent of other modules. This means that each module references only the core and nothing else.
Here’s a chart to represent this structure:
flowchart TD
Core[Core]
style Core stroke:#6b9bb3
Goobstation[Goobstation]
style Goobstation stroke:#6b9bb3
Lavaland[Lavaland]
style Lavaland stroke:#6b9bb3
Utils[Utils]
style Utils stroke:#6b9bb3
Modules[".Modules projects (end)"]
style Utils stroke:#6b9bb3
subgraph CustomModules["Custom Modules"]
Goobstation
Lavaland
Utils
end
style CustomModules stroke:#e91e63
Core --> Goobstation
Core --> Lavaland
Core --> Utils
Goobstation --> Modules
Lavaland --> Modules
Utils --> Modules
The only exception for that convention are library modules that provide some general tools or API for other modules to use.
Those may be required if there are multiple modules that use same features.
For example, lets assume that both Lavaland and Goobstation modules need to use code from Utils.
Then it’s okay to change the project structure like this:
flowchart TD
Core[Core]
style Core stroke:#6b9bb3
Goobstation[Goobstation]
style Goobstation stroke:#6b9bb3
Lavaland[Lavaland]
style Lavaland stroke:#6b9bb3
Utils[Utils]
style Utils stroke:#6b9bb3
Modules[".Modules projects (end)"]
style Utils stroke:#6b9bb3
subgraph CustomModules["Custom Modules"]
Goobstation
Lavaland
Utils
end
style CustomModules stroke:#e91e63
Core --> Utils
Utils --> Goobstation
Utils --> Lavaland
Goobstation --> Modules
Lavaland --> Modules
Special module projects
In Goobstation module folder, you can find some projects with weird Postfixes, such as Content.Goobstation.Client.UIKit, Content.Goobstation.Server.Database. Those are projects that have a unique role in code. Each of them has to be explained separately.
.Server.Database- a project that specifies a single Database model. At the moment Goob Reforged uses the core model and the Goob model for proper modularity..Client.UIKit- a project that referencesRobust.ClientandContent.Shared, and which is referenced directly byContent.Client. Contains custom UI elements that have to be usable inContent.Client, without making changes to it directly.
Content.Common
Common modules sometimes need to use some of the game’s code, for example to inherit an interface for a type or to use a general data structure.
Those are usually located at Content.Shared, and are inaccessible for Common modules. That’s why Content.Common exists - it allows to use the code ported from Content.Shared in all Content.*.Common custom modules.
The files from Content.Shared are moved into Content.Common without changing the namespace. This is needed so no usings in the code of Core modules have to be changed. And even though it shows as a Content.Shared.* namespace, it’s actually also available from Content.*.Common custom modules!