Skip to content

nilpunch/massive-ecs

Repository files navigation

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#v20.0.0-alpha.5
Stable: https://github.com/nilpunch/massive-ecs.git#v19.2.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.
  • Fully managed — no unsafe code, no Dispose() methods.
  • IL2CPP friendly, tested with high stripping level on PC, Android, and WebGL.

Features:

  • Clone() and CopyTo(other) methods for creating snapshots.
    Ideal for implementing replays, undo/redo, or rollbacks.
  • Lightweight views for fast iteration over entities and components.
  • Fully stable storage, 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 2.3 ms.

Addons

  • Full-state serialization and deserialization (package).
  • Networking with input buffers, commands 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.

// Rich entity handle with simple syntax.
var npc = world.CreateEntity();
npc.Add<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 contain 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.Filter<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>().Entities())
{
	ref Position position = ref entity.Get<Position>();
	// ...
}

// Chain any number of components in filters.
var filter = world.Filter<
	Include<int, string, bool, Include<short, byte, uint, Include<ushort>>>,
	Exclude<long, char, float, Exclude<double>>>();

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

About

Sparse set ECS with rollbacks. C# library and Unity package.

Topics

Resources

License

MIT, Unknown licenses found

Licenses found

MIT
LICENSE
Unknown
LICENSE.meta

Stars

Watchers

Forks

Packages

No packages published

Languages