Contributing

We welcome contributions to MafiaNet! This guide will help you get started.

Getting Started

  1. Fork the repository on GitHub

  2. Clone your fork locally

  3. Create a branch for your changes

git clone https://github.com/YOUR_USERNAME/MafiaNet.git
cd MafiaNet
git checkout -b feature/my-new-feature

Code Style

  • Use C++17 features where appropriate

  • Follow existing code formatting conventions

  • Add comments for complex logic

  • Keep functions focused and reasonably sized

Building and Testing

The test suite lives in Samples/Tests and is built as part of the samples, so enable MAFIANET_BUILD_SAMPLES:

mkdir build && cd build
cmake -DMAFIANET_BUILD_SAMPLES=ON ..
cmake --build .
./Samples/Tests/Tests                  # run all tests
./Samples/Tests/Tests VirtualWorldTest # run one test by name

A test is a class deriving from TestInterface (see Samples/Tests/TestInterface.h). RunTest() returns 0 on success or a non-zero error code on failure; map each code to a human-readable string in ErrorCodeToString() so failures are diagnosable. Register a new test by adding its header to Samples/Tests/IncludeAllTests.h and a testList.Push(new MyTest(), _FILE_AND_LINE_) line in Samples/Tests/Tests.cpp. RPC4ContextTest and VirtualWorldTest are good templates.

How to test a new feature

Follow test-driven development: write the test first, watch it fail for the right reason, then write the minimal code to make it pass. A test that passes the moment you write it has proven nothing.

Test at two levels — both are required for anything that touches networking or replication:

  1. Unit level (deterministic). Exercise the feature’s logic directly with no sockets, so it is fast and never flaky. VirtualWorldTest constructs replicas/connections on the stack and asserts the exact return values of the query hooks. This pins down what each decision should be.

  2. End-to-end / integration (real wire). Stand up real peers (a server and one or more clients) over loopback and drive them with RakPeerInterface::Receive() until the expected state converges. Use condition-based waiting (pump until the condition holds or a generous ceiling is reached), never a fixed sleep. Samples/VirtualWorld is a self-contained example that doubles as a smoke test (it returns non-zero on failure).

Warning

Unit tests alone are not enough for replication/networking features. Many bugs only appear in a real topology, because plugins such as ReplicaManager3 run the same callbacks on every peer (server, clients, peers), and the interesting failures live in how those sides interact over the wire. The virtual world feature, for example, passed every unit test but had a bug where a client told the server to delete an object — something only a real client/server run could surface. If your feature sends anything over the network, you must exercise it end-to-end before it is “done”.

When something only fails over the wire, instrument the message boundary rather than guessing: temporary, env-var-guarded prints in the send path (Connection_RM3::SendConstruction) and the receive path (ReplicaManager3::OnConstruction / OnSerialize), keyed on the relevant NetworkID, will show exactly which peer did what. Remove the instrumentation before committing.

ReplicaManager3: respect the authority model

Replica3 callbacks (QueryConstruction, QueryDestruction, QuerySerialization) run on every system that holds the replica — the owner and every system that downloaded a copy. A copy on a non-authority must defer to the topology default (QueryConstruction_ServerConstruction, QueryConstruction_ClientConstruction, QueryConstruction_PeerToPeer) and must not send construction or destruction upstream.

A reliable test for “is this system the authority for sending this entity to this connection?” is whether its within-topology construction decision is RM3CS_SEND_CONSTRUCTION. Only the authority should make per-connection visibility decisions (such as hiding an object); a downloaded copy that applies such logic can, for instance, tell the owner to destroy the object. Gate any custom visibility/scoping logic on authority, and add a unit test for the non-authority case.

Gotchas when running several peers in one process

Self-contained tests and samples often create multiple peers in a single process. Two collisions to be aware of:

  • GUID collision. On POSIX a peer’s GUID is seeded from the microsecond clock at construction, so peers created back-to-back can get identical GUIDs and a server will drop the duplicate connection. Space out the GetInstance() calls (e.g. a short RakSleep between them) so each GUID is distinct.

  • NetworkID collision. Each NetworkIDManager assigns ids from the same base, and NetworkID carries no creator information, so two independent creators can mint the same id (the second is rejected as a duplicate on the receiver). Use a single authoritative creator, or otherwise guarantee globally unique ids.

Releasing

Version bumps touch several files that must stay in sync. The full procedure is documented in CLAUDE.md (the “Releasing” section) — follow it exactly when cutting a release.

Submitting Changes

  1. Ensure all tests pass

  2. Commit your changes with a clear message

  3. Push to your fork

  4. Open a Pull Request

License

By contributing, you agree that your contributions will be licensed under the MIT License.