Skip to content

chore: Add linux support and containerize the project - #5

Open
clouths wants to merge 38 commits into
developfrom
linux+containerize
Open

clouths wants to merge 38 commits into
developfrom
linux+containerize

Conversation

@clouths

@clouths clouths commented Mar 15, 2026

Copy link
Copy Markdown
Collaborator

Linux Support, Containerization & CI Automation

Overview

Enables full Linux cross-platform support, containerizes the entire Perpetuum server stack with Docker Compose, adds performance optimizations, developer tooling, and automated CI test pipelines.


Key Changes

1. Linux Compatibility & Modernization

  • SkiaSharp Migration: Replaced Windows-only System.Drawing.Common with SkiaSharp across terrain, bitmap, color conversion, and pathfinding modules.
  • TransactionScope Fix: Added DbConnectionManager to share database connections within transaction scopes, preventing MSDTC promotion errors on Linux.
  • Container Stack: Added complete Docker Compose configuration (compose.yml, Dockerfiles for server, migration, db, asset, and test runner) with Linux sleep watchdog to prevent SQL Server scheduler spin.

2. Performance Optimizations

  • SIMD Acceleration: Added SimdMath with vector intrinsics for spatial calculations and distance evaluations.
  • Terrain & Memory Improvements: Introduced CompactPassabilityMask and HeightfieldMetadata to reduce memory footprints.
  • Memory Management: Configured .NET 8 DATAS dynamic GC heap scaling.

3. Developer Tooling & CLI Client

  • CLI Client (Perpetuum.CliClient): Interactive console client supporting server handshake, authentication, chat channels, and character management.
  • Automation Scripts: Added create-account.sh, smoke-test.sh, check_assets.py, and cli.sh.
  • Database Migrations: Automated dynamic patch discovery and snapshot hash caching (perpetuumsa_migrated.bak) for near-instant container boots.

4. Testing & CI Pipeline

  • Workflows (.github/workflows/docker-tests.yml): GitHub Actions jobs for containerized unit and integration testing.
  • Data Setup Action (.github/actions/setup-test-data/action.yml): Automated SteamCMD server data (App 693060) and Google Drive gamma layer downloads with caching.
  • Test Suites:
    • Unit Tests: Coverage for SkiaSharp image operations, SIMD vector math, CLI client logic, passability masks, and terrain layers.
    • Integration Tests: Live database validation, content definitions, and terrain invariant tests.
    • Smoke Tests: End-to-end container verification, TCP connection, authentication, and packet exchanges.

How to test

  • Run unit tests
make test-unit
  • Start the local stack (docker compose)
    ⚠️ Note that you need to download custom assets to launch the server. Open readme in docker compose section to get started ⚠️
make up
  • Run integration tests
make test-integration

clouths added 2 commits March 15, 2026 15:08
…r linux, add template for perpetuum.ini, add Makefile for some helper commands, update server to take DistributedTransactions as argument to be able to disable it for linux
@clouths clouths self-assigned this Mar 15, 2026
@aqpanaciy

Copy link
Copy Markdown
Collaborator

Regarding using DistributedTransactions, we use a single database and the connection string doesn't change. Therefore, using DistributedTransactions is not necessary. I'm not very knowledgeable about the intricacies of database operation, but after reading the documentation, I found out that:

  1. After closing the connection to the database, it is not closed immediately, but is returned to the pool, and when reopened, it is taken from there.
  2. For each request inside a TransactionScope, you can open and close your own connection; this is a well-known technique.

Therefore, you can remove: TransactionManager.ImplicitDistributedTransactions = true; in PerpetuumBootstrapper.cs
AND
Rremove: dbConnection.EnlistTransaction(Transaction.Current); in DbQuery.cs (This line of code is responsible for forcing the use of distributed transactions for the current transaction)

With these changes, I was able to launch the server on Linux. And there are no errors when working with the database. I hope this information will at least be useful.

…ipt and fix migration when using a directory
@clouths

clouths commented Mar 16, 2026

Copy link
Copy Markdown
Collaborator Author

Regarding using DistributedTransactions, we use a single database and the connection string doesn't change. Therefore, using DistributedTransactions is not necessary. I'm not very knowledgeable about the intricacies of database operation, but after reading the documentation, I found out that:

1. After closing the connection to the database, it is not closed immediately, but is returned to the pool, and when reopened, it is taken from there.

2. For each request inside a TransactionScope, you can open and close your own connection; this is a well-known technique.

Therefore, you can remove: TransactionManager.ImplicitDistributedTransactions = true; in PerpetuumBootstrapper.cs AND Rremove: dbConnection.EnlistTransaction(Transaction.Current); in DbQuery.cs (This line of code is responsible for forcing the use of distributed transactions for the current transaction)

With these changes, I was able to launch the server on Linux. And there are no errors when working with the database. I hope this information will at least be useful.

This PR disables it but I have left a default value (true) to keep the same behavior to keep compatibility for the Windows build. If removing the EnlistTransaction do the trick, we might be able to disable it if the live server using the Windows build don't have any issue.

@clouths

clouths commented Mar 24, 2026

Copy link
Copy Markdown
Collaborator Author

At this point, the PR is almost ready for review.

The client is able to connect and perform all actions tested (chat, enter/leave chat, buy from market, unpack item, activate bot, undock/dock, move, assignments, looting, combat, tutorial, rift)

What is left todo?

  • Add readme how how to setup development environment (server, assets, gamma layers) (can be done in a follow-up PR)

@clouths
clouths marked this pull request as ready for review March 25, 2026 01:31
… the EnlistTransaction configurable with the DistributedTransactions state
@clouths

clouths commented Mar 28, 2026

Copy link
Copy Markdown
Collaborator Author

At this point, the documentation has been added and the DistributedTransactions config is now correctly applied in the codebase.

Comment thread src/Perpetuum/BitmapExtensions.cs Outdated
Comment thread src/Perpetuum.RequestHandlers/Zone/StatsMapDrawing/ZoneDrawStatMap.cs Outdated
clouths added 2 commits March 28, 2026 18:37
…d of SKSurface, update usage of canvas.DrawText to fix Y position by taking into account the font size since the origin is on top left instead of bottom left.
@clouths clouths changed the title WIP chore: Add linux support and containerize the project chore: Add linux support and containerize the project Mar 28, 2026
Comment thread src/Perpetuum.RequestHandlers/Zone/ZoneSetLayerWithBitMap.cs Outdated
@aqpanaciy

Copy link
Copy Markdown
Collaborator

I successfully tested this branch in Docker.Desktop on Windows. I did have to manually update the submodules to the latest versions, though.

image

Comment thread src/Perpetuum.Tests/Fakes/Data/FakeDb.cs
@clouths

clouths commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator Author

All unit and integration tests pass now.

Added Live_36 to the migration with the Raw_SQL for now until the patch is complete.

@clouths
clouths requested a review from aqpanaciy August 31, 2026 02:33
Comment thread docs/codebase/TESTING.md
- Add SHA-256 change detection and compressed DB backup restoration
- Automate chronological execution for Pre_Alpha_* and Live_* patches
- Add clean-cache Makefile target and README Mermaid migration diagram
…tling

- Add SimdMath with AVX-512 / AVX2 / Vector128 distance calculation fallbacks
- Add HeightfieldMetadata 16x16 chunk bounding grid for raycast optimization
- Add CompactPassabilityMask 1-bit per tile bitmask
- Throttle empty zone update loop to 1 Hz when no players are present
- Enable DOTNET_GCDynamicAdaptationMode for dynamic GC heap sizing
- Add unit test coverage for SIMD math and terrain structures
…promotion on Linux

- Introduce DbConnectionManager to track and reuse open connections per ambient Transaction
- Update DbQuery.ExecuteHelper to avoid multiple connection checkouts in the same scope
- Fix System.PlatformNotSupportedException thrown on Linux during player logout and transactions
- Add unit tests verifying connection reuse, cleanup on commit, and cleanup on abort
@clouths

clouths commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator Author

I've reworked the migration to load files automatically without having to add new patch in the migration.sh file with a cache mechanism to accelerate migration (development QoL).

I also had some instances of Distributed transaction happening due to Enlist some times which was causing errors, to fix this issue, I refactored the DbConnectionManager to avoid multiple connections in the same scope to reuse them. When distributed transactions is enabled, the behavior is unchanged. This refactor will allow in theory better scaling with many players/npcs.

I also added SIMD acceleration, did some optimization on the loaded assets by caching the essential content. I also added idle optimization to slow down tick when no player is on an island from from 20hz to 1hz while keeping precision on events on the island. Overall saving (40-60% reduction) 1.5-2.5GB on RAM and (60-90% reduction) 0.6-0.9 CPU core. One notable change is that a zone metadata can now fit in L2 cache (from 36MB to 512KB per zone).

- Implement Perpetuum.CliClient interactive console tool and tests
- Add smoke-test.sh, check_assets.py, and create-account.sh scripts
- Fix terrain layer bounds clamping and coordinate validation
- Add COVERAGE.md and update test documentation
Comment on lines +348 to +363
// Throttle unit physics, AI, and visibility processing when no players are in the zone
if (_players.IsEmpty)
{
_idleUpdateTimer.Update(time);
if (!_idleUpdateTimer.Passed)
{
RiftManager?.Update(time);
RelicManager?.Update(time);
MiningLogHandler.Update(time);
HarvestLogHandler.Update(time);
return;
}

_idleUpdateTimer.Reset();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my local testing, NPC-vs-NPC mechanics have slowed down. It appears to disrupt all processes related to time accumulation. The PBS energy consumption system, PBSActiveObject functionality (including periodic actions and boost states), periodic saving of PBS objects, and object expiration/deletion timers cease to function correctly. All of these rely on UpdateUnits(time).

Suggested change
// Throttle unit physics, AI, and visibility processing when no players are in the zone
if (_players.IsEmpty)
{
_idleUpdateTimer.Update(time);
if (!_idleUpdateTimer.Passed)
{
RiftManager?.Update(time);
RelicManager?.Update(time);
MiningLogHandler.Update(time);
HarvestLogHandler.Update(time);
return;
}
_idleUpdateTimer.Reset();
}
var elapsed = _idleUpdateTimer.Update(time).Elapsed;
if (_players.IsEmpty && !_idleUpdateTimer.Passed)
{
return;
}
_idleUpdateTimer.Reset();
time = elapsed;

}

private readonly ShiftedConsumerTimer _updateUnitsTimer = new ShiftedConsumerTimer(500);
private readonly IntervalTimer _idleUpdateTimer = new IntervalTimer(1000);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

250 ms is already five times the standard interval. I think 1 second (20 times longer) is excessive.

Suggested change
private readonly IntervalTimer _idleUpdateTimer = new IntervalTimer(1000);
private readonly IntervalTimer _idleUpdateTimer = new IntervalTimer(250);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did I understand correctly that this optimization isn't used on the server itself? Plants growth needs to be taken into account; they dynamically change their height and whether or not they act as a solid block.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the change solves the issue on Linux. However, the tests do not cover all possible use cases—specifically, the tests for DistributedTransactions = true. We need to ensure that this change does not cause a regression on Windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants