Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ General Development
- [Module Conventions](en/general-development/codebase-info/conventions/modules.md)
- [Pull Request Guidelines](en/general-development/codebase-info/pull-request-guidelines.md)
- [Codebase Organization](en/general-development/codebase-info/codebase-organization.md)
- [Goob Reforged](en/general-development/codebase-info/goob-reforged/goob-reforged.md)
- [Reforged Modules](en/general-development/codebase-info/goob-reforged/reforged-modules.md)
- [Feature Proposals](en/general-development/feature-proposals.md)
- [Feature Proposal Template](en/templates/proposal.md)
- [Expected Team Decorum & Usage](en/general-development/feature-proposals/expected-feature-proposal-decorum.md)
Expand Down
28 changes: 7 additions & 21 deletions src/en/general-development/codebase-info/codebase-organization.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ SS14 and RobustToolbox are split into several different projects. The main ones

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

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.

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.

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.
- 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.
- 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.
- 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.

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.

All modules also have a `Common` project in them. They allow modules to provide their own code (mostly events) to the main `Shared` project.

## Game Code

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.
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.

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

- All game code will be organized in folders directly under Content.Client/Shared/Server etc.
- Game code folders are split into Components, EntitySystems, Visualizers, UI, Prototypes, etc
Expand All @@ -34,17 +34,3 @@ A real example, under `Content.Server` at `da11cbd8e6bef3373ec1f570df7d7b9155a38
- Atmos is a fairly large game system. It has many folders, and many files that do not need to go in these folders.
- Botany is a smaller game system. However, it only has one folder for Components since that's all that's really there.
- ItemCabinets are a very small game system. They just have a component and EntitySystem, and thus do not need folders for each.

## Resources

The resources folder is another area where we hope to improve over the organization structure of SS13 codebases.

### Entity Prototypes

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.

Parent prototypes should be contained in `base.yml` in this folder, while other prototypes go in a different file.

Not everywhere is organized like this; however, the `Structures` folder is.

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.
12 changes: 0 additions & 12 deletions src/en/general-development/codebase-info/conventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,3 @@ Read the [Pull Request guidelines](pull-request-guidelines.md) to learn how to m
```admonish info
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.
```

# Navigation

This is the recommended order to read these conventions for the first time.

1. [General programming](./conventions/general.md)
2. [Project programming](./conventions/project.md)
3. [ECS Conventions](./conventions/ecs.md)
4. [Architecture](./conventions/architecture.md)
5. [Networking Conventions](./conventions/networking.md)
6. [Resource Conventions](./conventions/resources.md)
7. [Module Conventions](./conventions/modules.md)
Original file line number Diff line number Diff line change
@@ -1,4 +1,43 @@
# Architecture Conventions
# Code Architecture Conventions

## Composition > Singletons

- **Composition** is when a single component holds a single function.
- **Singleton** is when a single component holds multiple functions.

In general, the main coding principle you have to follow is to **prefer composition to singletons**.

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.

**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#.

<details>
<summary>Bingle code design example</summary>

Let's take Bingles as an example for this architecture strategy.

On the base level, Bingle mechanics are simple:
- A pit spawns from a gamerule;
- This pit works just like a chasm, gets points when items or people fall in, and spawns Bingles when enough points are reached;
- 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.

### The intuitive way

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.

But that strategy makes it really hard to implement a new feature for bingles because of how hardcoded they are to their own structure!

### How to implement Bingles properly

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.

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.

A similar system for tile spreading is needed in order to randomly spawn bingle floors around the pit.

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!

</details>

## In-simulation or out-of-simulation

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

### Dependencies On Other Systems
Inside an entity system, prefer a system dependency instead of resolving the system using the IoCManager. For example, instead of:
## Dependencies
Inside an entity system, prefer a system dependency instead of resolving the system using the IoCManager.

Bad:
```csharp
var random = IoCManager.Resolve<IRobustRandom>();
random.Prob(0.1f);
```

Add an entity system dependency:

Good:
```csharp
[Dependency] private readonly IRobustRandom _random = default!;
_random.Prob(0.1f);
```

### C\# Events vs EventBus Events
## Code Design Choices

### C# Events vs EventBus Events
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.

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

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.
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.
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.

### Enums vs Prototypes
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Goob Reforged

**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.

This is done because upstream merges of the current repository had became hell, since many changes were made right in the upstream code.

With that, relicensing is also happening from **AGPL-3.0** to **MPL-2.0**.
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# 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:

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.

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.

This approach is specifically designed to accommodate [Robust Modularity](https://github.com/space-wizards/docs/pull/152) 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`, and their dependencies like `Content.Shared.Database`.

- **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.`.

- **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.

- **Infix**: The module identifier, which is the middle part of a module name. For example, "Goobstation" in `Content.Goobstation.Common`.

## 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:

```mermaid
flowchart TD
CoreServer[Content.Server]
style CoreServer stroke:#6b9bb3
CoreClient[Content.Client]
style CoreClient stroke:#6b9bb3
CoreShared[Content.Shared]
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
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
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 `.Common` module 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.

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.

That's why you should **always use `Content.Modules` projects for development!**
Loading