|
| 1 | +# Reforged Modules |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +## Why |
| 6 | + |
| 7 | +As stated above, there are two primary reasons: |
| 8 | + |
| 9 | +1. **Streamlined Testing and Deployment**: By separating downstream-specific code from the rest of the game, you can compile and test your custom features independently. |
| 10 | + |
| 11 | +2. **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. |
| 12 | + |
| 13 | +This approach is specifically designed to accommodate [Robust Modularity](https://github.com/space-wizards/docs/pull/152) should it be implemented in the future. |
| 14 | + |
| 15 | +## Creating new modules |
| 16 | + |
| 17 | +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! |
| 18 | + |
| 19 | +## Terminology |
| 20 | + |
| 21 | +- **Core Module**: Modules belonging to the base space-station-14 repository. These include `Content.Client`, `Content.Server`, `Content.Shared`, and their dependencies like `Content.Shared.Database`. |
| 22 | + |
| 23 | +- **Custom Module**: Modules that are part of the Client or Server process but don't belong to Core Modules. For example, in Goobstation's case, these are prefixed with `Content.Goobstation.`. |
| 24 | + |
| 25 | +- **Engine Module**: Modules that are part of RobustToolbox (the game engine). These are prefixed with `Robust.`. |
| 26 | + |
| 27 | +- **.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. |
| 28 | + |
| 29 | +- **Infix**: The module identifier, which is the middle part of a module name. For example, "Goobstation" in `Content.Goobstation.Common`. |
| 30 | + |
| 31 | +## How |
| 32 | + |
| 33 | +### Module manager |
| 34 | + |
| 35 | +The client loader, unless configured otherwise, loads every Assembly from the `Assemblies` folder with a specific prefix. |
| 36 | +This prefix can be defined in the `manifest.yml` file and defaults to `Content.`. |
| 37 | + |
| 38 | +By creating new projects with the `Content.` prefix, you can have the game automatically load them at startup alongside the core modules. |
| 39 | + |
| 40 | +### Dependency tree |
| 41 | + |
| 42 | +Here's an example of the module dependency tree for Goobstation modules: |
| 43 | + |
| 44 | +```mermaid |
| 45 | +flowchart TD |
| 46 | + CoreServer[Content.Server] |
| 47 | + style CoreServer stroke:#6b9bb3 |
| 48 | + CoreClient[Content.Client] |
| 49 | + style CoreClient stroke:#6b9bb3 |
| 50 | + CoreShared[Content.Shared] |
| 51 | + style CoreShared stroke:#6b9bb3 |
| 52 | +
|
| 53 | + GoobServer[Content.Goobstation.Server] |
| 54 | + style GoobServer stroke:#3498db |
| 55 | + GoobClient[Content.Goobstation.Client] |
| 56 | + style GoobClient stroke:#3498db |
| 57 | + GoobShared[Content.Goobstation.Shared] |
| 58 | + style GoobShared stroke:#3498db |
| 59 | + GoobCommon[Content.Goobstation.Common] |
| 60 | + style GoobCommon stroke:#3498db |
| 61 | +
|
| 62 | + ModuleServer[Content.Modules.Server] |
| 63 | + style GoobShared stroke:#3498db |
| 64 | + ModuleClient[Content.Modules.Client] |
| 65 | + style GoobCommon stroke:#3498db |
| 66 | + |
| 67 | + subgraph CoreModules["Core Modules"] |
| 68 | + CoreServer |
| 69 | + CoreClient |
| 70 | + CoreShared |
| 71 | + end |
| 72 | + style CoreModules stroke:#e91e63 |
| 73 | +
|
| 74 | + CoreShared --> CoreServer |
| 75 | + CoreShared --> CoreClient |
| 76 | +
|
| 77 | + subgraph GoobModules["Goob Modules"] |
| 78 | + GoobServer |
| 79 | + GoobClient |
| 80 | + GoobShared |
| 81 | + GoobCommon |
| 82 | + end |
| 83 | + style GoobModules stroke:#e91e63 |
| 84 | +
|
| 85 | + GoobShared --> GoobServer |
| 86 | + GoobShared --> GoobClient |
| 87 | +
|
| 88 | + GoobCommon --> CoreShared |
| 89 | + CoreShared --> GoobShared |
| 90 | + CoreServer --> GoobServer |
| 91 | + CoreClient --> GoobClient |
| 92 | + GoobServer --> ModuleServer |
| 93 | + GoobClient --> ModuleClient |
| 94 | +``` |
| 95 | + |
| 96 | +This means: |
| 97 | +- Custom modules directly reference their core module counterparts. |
| 98 | +- 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. |
| 99 | +- If the types are the same, core modules must communicate with Custom modules through an intermediary `.Common` module using events. |
| 100 | +- The Common module must Not depend on either core or Custom modules and should be able to build standalone. |
| 101 | + |
| 102 | +### Module compilation |
| 103 | + |
| 104 | +However, the module loader does not ensure that the modules actually compile in the right order. |
| 105 | + |
| 106 | +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! |
| 107 | + |
| 108 | +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. |
| 109 | + |
| 110 | +### Up-to-date compilation problem |
| 111 | + |
| 112 | +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. |
| 113 | + |
| 114 | +This problem is solved by adding the `Content.Modules.Server` and `Content.Modules.Client` projects. They reference all modules at once as a direct Project reference, so when a change is made to any module, it gets recompiled correctly. |
| 115 | + |
| 116 | +That's why you should **always use `Content.Modules` projects for development!** |
0 commit comments