Unclaimed Package Is this your package? Claim it to unlock full analytics and manage your listing.
Claim This Package

Install via UPM

Add to Unity Package Manager using this URL

https://www.pkglnk.dev/massive-ecs.git

README Markdown

Copy this to your project's README.md

Style
Preview
pkglnk installs badge
## Installation

Add **Massive ECS** to your Unity project via Package Manager:

1. Open **Window > Package Manager**
2. Click **+** > **Add package from git URL**
3. Enter:
```
https://www.pkglnk.dev/massive-ecs.git
```

[![pkglnk](https://www.pkglnk.dev/badge/massive-ecs.svg?style=pkglnk)](https://www.pkglnk.dev/pkg/massive-ecs)

Dependencies (1)

README

Massive ECS

Massive is a fast and simple library for game programming and more.
Designed for use in games with deterministic prediction-rollback netcode.
Based on bitsets. Inspired by EnTT.
Reinforced with insights from Static ECS.

Does not reference Unity Engine, so it could be used in a regular C# project.

[!NOTE] Some APIs are subject to change, but overall the architecture is stable.

Installation

Make sure you have standalone Git installed first. Reboot after installation.
In Unity, open "Window" -> "Package Manager".
Click the "+" sign at the top left corner -> "Add package from git URL..."
Paste link to the version:
Latest: https://github.com/nilpunch/massive-ecs.git
Stable: https://github.com/nilpunch/massive-ecs.git#v20.1.0
See minimum required Unity version in the package.json file.

Overview

This is a library, not a framework. Thus, it does not try to take control of the user codebase or the main game loop.

It’s organized as a set of loosely coupled containers that can be used in different ways.

Entity Component System (wiki)

Design considerations:

  • Supports components of any type.
  • Deterministic with lazy initialization.
  • No deferred command execution — all changes apply immediately.
  • Minimal storage for fast saving. No archetypes.
  • Standard .Net lifecycle - no mutable global state, fully GC-managed.
  • IL2CPP friendly, tested with high stripping level on PC, Android, and WebGL.
  • NativeAOT supported, but requires further testing.

Features:

  • Clone() and CopyTo(other) methods for creating snapshots.
    Ideal for implementing replays, undo/redo, or rollbacks.
  • Lightweight queries for fast iteration over entities and components.
  • Stable storage with no reference invalidation.
  • Allocator lets you use collections inside components and easily integrate external tools in rollback pipeline.

Rollbacks (wiki)

  • Fully optional and non-intrusive, integrates seamlessly with the existing ECS core.
  • Minimalistic API: SaveFrame() and Rollback(frames)
  • Supports components with managed data (e.g., arrays, strings, etc.).
  • Performance reference (PC, CPU i7-11700KF, RAM 2666 MHz):
    • 1000 entities, each with 150 components, can be saved 24 times in 6 ms.
      The 150 components include 50 components of 64 bytes, 50 components of 4 bytes, and 50 tags.
    • Need more entities or reduced overhead? Adjust components or saving amount.
      For example, 10000 entities with 15 components each can be saved 12 times in 3 ms.

Addons

  • Full-state serialization and deserialization (package).
  • Client-server, input prediction and resimulation loop (package).
  • Unity integration (package).

Consider this list a work in progress as well as the project.

Code Examples

struct Player { }
struct Position { public float X; public float Y; }
class Velocity { public float Magnitude; } // Classes work just fine.
interface IDontEvenAsk { }

// Create a world.
var world = new World();

// Create entities.
var enemy = world.Create(); // Empty entity.
var player = world.Create<Player>(); // With a component.

// Add components.
world.Add<Velocity>(player); // Adds component without initializing data.
world.Get<Velocity>(player) = new Velocity() { Magnitude = 10f };

world.Set(enemy, new Velocity()); // Adds component and sets its data.

// Or use feature-rich entity handle.
var npc = world.CreateEntity();
npc.Add<Position>();
if (npc.Has<Position>())
{
	npc.Destroy();
}

// Get full entity identifier from player ID.
// Useful for persistent storage of entities.
Entifier playerEntifier = world.GetEntifier(player);

var deltaTime = 1f / 60f;

// Iterate using lightweight queries.
// ForEach will select only those entities that has all the necessary components.
world.ForEach((Entity entity, ref Position position, ref Velocity velocity) =>
{
	position.Y += velocity.Magnitude * deltaTime;

	if (position.Y > 5f)
	{
		// Create and destroy any amount of entities during iteration.
		entity.Destroy();
	}
});

// Pass arguments to avoid boxing.
world.ForEach((world, deltaTime),
	(ref Position position, ref Velocity velocity,
		(World World, float DeltaTime) args) =>
	{
		// ...
	});

// Filter entities right in place.
// You don't have to cache anything.
world.Include<Player>().Exclude<Velocity>()
	.ForEach((ref Position position) =>
	{
		// ...
	});

// Iterate using foreach with data set. (faster)
var positions = world.DataSet<Position>();
foreach (var entityId in world.Include<Player, Position>())
{
	ref Position position = ref positions.Get(entityId);
	// ...
}

// Iterate over rich entities. (simpler)
foreach (var entity in world.Include<Player, Position>().Entities)
{
	ref Position position = ref entity.Get<Position>();
	// ...
}

// Chain any number of components in queries.
var query = world
	.Include<int, string, bool, short, byte, uint, And<ushort, ulong>>()
	.Exclude<long, char, float, double, decimal, nint, And<nuint>>();

// Reuse the same query to iterate over different components.
query.ForEach((ref int n, ref bool b) => { });
query.ForEach((ref string str) => { });

Comments

No comments yet. Be the first!