Skip to content

Commit 9b3cd18

Browse files
authored
Merge pull request #23 from Roudenn/reforged-guides
Reforged modules
1 parent cac9a00 commit 9b3cd18

6 files changed

Lines changed: 180 additions & 40 deletions

File tree

src/SUMMARY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ General Development
3838
- [Module Conventions](en/general-development/codebase-info/conventions/modules.md)
3939
- [Pull Request Guidelines](en/general-development/codebase-info/pull-request-guidelines.md)
4040
- [Codebase Organization](en/general-development/codebase-info/codebase-organization.md)
41+
- [Goob Reforged](en/general-development/codebase-info/goob-reforged/goob-reforged.md)
42+
- [Reforged Modules](en/general-development/codebase-info/goob-reforged/reforged-modules.md)
4143
- [Feature Proposals](en/general-development/feature-proposals.md)
4244
- [Feature Proposal Template](en/templates/proposal.md)
4345
- [Expected Team Decorum & Usage](en/general-development/feature-proposals/expected-feature-proposal-decorum.md)

src/en/general-development/codebase-info/codebase-organization.md

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ SS14 and RobustToolbox are split into several different projects. The main ones
66

77
`Client`, `Shared`, and `Server` are each packaged into different 'assemblies', which is basically .NET talk for executables or shared libraries.
88

9-
The `Client` project in both Robust and SS14 contains client-specific code, like UI. This assembly is only sent to the client, the person actually playing the game.
10-
11-
The `Server` project contains server-specific code that no specific client should be able to interact with, like atmospherics or botany. This assembly is only located on the game server.
12-
13-
The `Shared` project contains shared code that can be used by the client or the server. This assembly is not executable, and it relies on the client or server to call functions in it or use data classes located within it. The purpose of shared is to allow for network prediction (where the client and server run the same code, to make things smoother) as well as to specify shared data classes, like network messages, so that the client and server can speak to each other effectively.
9+
- The `Client` project in both Robust and SS14 contains client-specific code, like UI. This assembly is only sent to the client, the person actually playing the game.
10+
- The `Server` project contains server-specific code that no specific client should be able to interact with, like atmospherics or botany. This assembly is only located on the game server.
11+
- The `Shared` project contains shared code that can be used by the client or the server. This assembly is not executable, and it relies on the client or server to call functions in it or use data classes located within it. The purpose of shared is to allow for network prediction (where the client and server run the same code, to make things smoother) as well as to specify shared data classes, like network messages, so that the client and server can speak to each other effectively.
1412

1513
Shared code is only allowed to access other shared code, not client or server code. However, client and server code are always allowed to access shared code.
1614

15+
All modules also have a `Common` project in them. They allow modules to provide their own code (mostly events) to the main `Shared` project.
16+
1717
## Game Code
1818

19-
In SS13, all game code is randomly thrown around under `code/`, and instead of grouping by relevance to systems, is grouped by abstract things like whether the file is a list of constants or whether a file pertains to a master controller subsystem. In SS14, we first delineate by which game system we're working with (atmos/botany/buckling, etc) and then by the classes needed for it, which is much easier for anyone actually trying to work within a single system.
19+
In SS14, we first delineate by which game system we're working with (atmos/botany/buckling, etc) and then by the classes needed for it, which is much easier for anyone actually trying to work within a single system.
2020

21-
`Content.Client`, `Content.Shared`, and `Content.Server` all follow this organization. RobustToolbox's equivalents do not currently, but will in the future.
21+
`Content.Client`, `Content.Shared`, and `Content.Server` all follow this organization.
2222

2323
- All game code will be organized in folders directly under Content.Client/Shared/Server etc.
2424
- Game code folders are split into Components, EntitySystems, Visualizers, UI, Prototypes, etc
@@ -34,17 +34,3 @@ A real example, under `Content.Server` at `da11cbd8e6bef3373ec1f570df7d7b9155a38
3434
- Atmos is a fairly large game system. It has many folders, and many files that do not need to go in these folders.
3535
- Botany is a smaller game system. However, it only has one folder for Components since that's all that's really there.
3636
- ItemCabinets are a very small game system. They just have a component and EntitySystem, and thus do not need folders for each.
37-
38-
## Resources
39-
40-
The resources folder is another area where we hope to improve over the organization structure of SS13 codebases.
41-
42-
### Entity Prototypes
43-
44-
New folders should usually only be created for a new parent type. If you find something that can be pulled out into a parent prototype, it should go in its own folder.
45-
46-
Parent prototypes should be contained in `base.yml` in this folder, while other prototypes go in a different file.
47-
48-
Not everywhere is organized like this; however, the `Structures` folder is.
49-
50-
This was chosen to make the directory structure mirror the prototype inheritance tree, making it obvious where to place new prototypes as well as being fairly unambiguous when choosing to create new folders.

src/en/general-development/codebase-info/conventions.md

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,3 @@ Read the [Pull Request guidelines](pull-request-guidelines.md) to learn how to m
1111
```admonish info
1212
Keep in mind that some older areas of the codebase might not follow these conventions. These should be refactored in the future to follow them. All new code should try to follow these conventions as closely as possible.
1313
```
14-
15-
# Navigation
16-
17-
This is the recommended order to read these conventions for the first time.
18-
19-
1. [General programming](./conventions/general.md)
20-
2. [Project programming](./conventions/project.md)
21-
3. [ECS Conventions](./conventions/ecs.md)
22-
4. [Architecture](./conventions/architecture.md)
23-
5. [Networking Conventions](./conventions/networking.md)
24-
6. [Resource Conventions](./conventions/resources.md)
25-
7. [Module Conventions](./conventions/modules.md)

src/en/general-development/codebase-info/conventions/architecture.md

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,43 @@
1-
# Architecture Conventions
1+
# Code Architecture Conventions
2+
3+
## Composition > Singletons
4+
5+
- **Composition** is when a single component holds a single function.
6+
- **Singleton** is when a single component holds multiple functions.
7+
8+
In general, the main coding principle you have to follow is to **prefer composition to singletons**.
9+
10+
The issue with "singleton" design is the lack of modularity. When all functionality is tied to a single component, it becomes harder to tweak the structure further.
11+
12+
**Always split the functionality between multiple components and systems**. The simplest way to imagine how it should work is to think about how to implement your mechanic without ever actually saying its name in C#.
13+
14+
<details>
15+
<summary>Bingle code design example</summary>
16+
17+
Let's take Bingles as an example for this architecture strategy.
18+
19+
On the base level, Bingle mechanics are simple:
20+
- A pit spawns from a gamerule;
21+
- This pit works just like a chasm, gets points when items or people fall in, and spawns Bingles when enough points are reached;
22+
- Tiles spread randomly around the pit when some amount of points is reached, and living Bingles get upgraded when they drag enough items into the pit.
23+
24+
### The intuitive way
25+
26+
The most straightforward way to add Bingles is to assign a single component to the Bingle pit and a single component to Bingles, then code all necessary mechanics in a single system. That will be the fastest and easiest path, and everything will work just fine for a bit of time.
27+
28+
But that strategy makes it really hard to implement a new feature for bingles because of how hardcoded they are to their own structure!
29+
30+
### How to implement Bingles properly
31+
32+
Pit should use the same code as chasms, but instead of deleting the entities it should store them and add points to some generic internal counter. That requires rewriting `ChasmSystem` to be more generic, since it's hardcoded to deleting fallen entities, and adding components like `ChasmContainerComponent` that force the chasm to store the entity and `ChasmChargeComponent` that adds a charge to a chasm entity.
33+
34+
Then a new system that spawns entities when a certain amount of charges on an entity is reached is required, so the pit can spawn Bingles when enough items are dragged in.
35+
36+
A similar system for tile spreading is needed in order to randomly spawn bingle floors around the pit.
37+
38+
As the result, instead of just copy-pasting or hardcoding, we made a lot of generic components that can be later used for any other purposes, not just Bingles!
39+
40+
</details>
241

342
## In-simulation or out-of-simulation
443

@@ -37,22 +76,24 @@ Here are some of the differences between how in-simulation and out-of-simulation
3776
| Check elapsed time | `IGameTiming.CurTime` | `IGameTiming.RealTime`, `(R)Stopwatch`, `DateTime`, etc. |
3877
| Send custom network messages | Networked entity events | Custom `NetMessage` |
3978

40-
### Dependencies On Other Systems
41-
Inside an entity system, prefer a system dependency instead of resolving the system using the IoCManager. For example, instead of:
79+
## Dependencies
80+
Inside an entity system, prefer a system dependency instead of resolving the system using the IoCManager.
4281

82+
Bad:
4383
```csharp
4484
var random = IoCManager.Resolve<IRobustRandom>();
4585
random.Prob(0.1f);
4686
```
4787

48-
Add an entity system dependency:
49-
88+
Good:
5089
```csharp
5190
[Dependency] private readonly IRobustRandom _random = default!;
5291
_random.Prob(0.1f);
5392
```
5493

55-
### C\# Events vs EventBus Events
94+
## Code Design Choices
95+
96+
### C# Events vs EventBus Events
5697
The EventBus should generally be used over C# events where possible. C# events can leak, especially when used with components which can be created or removed at any time.
5798

5899
C# events should be used for out-of-simulation events, such as UI events.
@@ -61,7 +102,7 @@ Remember to *always* unsubscribe from them, however!
61102
### Async vs Events
62103
For things such as DoAfter, always use events instead of async.
63104

64-
Async for any game simulation code should be avoided at all costs, as it's generally virulent, cannot be serialized (in the case of DoAfter, for example), and usually causes icky code.
105+
Async for any game simulation code should be **avoided at all costs**, as it's generally virulent, cannot be serialized (in the case of DoAfter, for example), and usually causes icky code.
65106
Events, on the other hand, tie in nicely with the rest of the game's architecture, and although they aren't as convenient to code, they are definitely way more lightweight.
66107

67108
### Enums vs Prototypes
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Goob Reforged
2+
3+
**Goob Reforged** is a Fresh fork of vanilla Wizard’s Den SS14, with a goal to port Goob features over one-by-one, refactoring and relicensing things properly as the mechanics are ported in order to end up with a clean separation from upstream.
4+
5+
This is done because upstream merges of the current repository had became hell, since many changes were made right in the upstream code.
6+
7+
With that, relicensing is also happening from **AGPL-3.0** to **MPL-2.0**.
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
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

Comments
 (0)