Skip to content
Back to skills

Ecs System And Component Design

ASecurity

ECS system and component design using Arch ECS. Use when creating or modifying systems, components, queries, cleanup, singletons, or tests -- also for code reviewing World/Entity/component usage.

  • 23 stars
  • 0 votes
  • 1 copy
  • 7 views
  • Added May 27, 2026
devopsc#testing

Works with

  • cli

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned May 27, 2026

npx -y skills add decentraland/unity-explorer --skill ecs-system-and-component-design --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ecs System And Component Design?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Ecs System And Component Design
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/decentraland-ecs-system-and-component-design/badge)](https://www.skillsdirectory.com/skills/decentraland-ecs-system-and-component-design)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: ecs-system-and-component-design
description: "ECS system and component design using Arch ECS. Use when creating or modifying systems, components, queries, cleanup, singletons, or tests -- also for code reviewing World/Entity/component usage."
user-invocable: false
---

# ECS System & Component Design

## Sources

- `docs/development-guide.md` -- Primary ECS development reference
- `docs/architecture-overview.md` -- High-level ECS architecture and patterns
- `docs/systems.md` -- Scene bounds and rendering system details
- `CLAUDE.md` -- Condensed ECS rules

---

## System Design Rules

> Condensed rules are in CLAUDE.md sections 1-7. This skill provides expanded patterns and guidance not covered there. For a full clean system code example, see [reference.md](reference.md).

Key reminders:
- Systems inherit from `BaseUnityLoopSystem`, constructors are `internal`, accept shared dependencies only
- Systems must not hold persistent entity/component collections or contain state
- `Update()` must be allocation-free; no LINQ
- Split systems >200 lines into static counterparts
- Assign each system to a `SystemGroup`; use `[UpdateBefore]`/`[UpdateAfter]` for ordering

---

## Querying Best Practices

Ranked from most preferred to least:

1. **Source-generated queries** (preferred) -- Use `[Query]` attribute with `[Data]` parameters
2. **`GetChunkIterator()`** -- For generic systems
3. **`World.InlineQuery`** -- Outside systems or generic cases; uses `IForEach<T>` struct
4. **`World.Query`** -- Last resort due to delegate/closure overhead

Always filter out `DeleteEntityIntention`. No nested queries. Use `TryGet` for known entities over queries.

---

## Safe Component Mutation

```csharp
// CORRECT -- ref var modifies the component in-place
ref var component = ref world.Get<MyComponent>(entity);
component.Value = 42;

// WRONG -- var copies the component; changes are lost
var component = world.Get<MyComponent>(entity);
component.Value = 42; // This modification is lost!
```

- **NEVER perform structural changes (Add/Remove) after obtaining a `ref`, `in`, or `out` reference.** Structural changes relocate entity data in memory, invalidating all outstanding pointers.
- Components should be data-only (no logic); may have pool, static factory, or non-empty constructor
- Prefer `struct` for components; use `class` for MonoBehaviors, existing class lifecycle, or cross-world references
- Clarify `ref` vs `in` intent; use `ref readonly` for immutable refs

---

## Component Cleanup Patterns

Cleanup must handle three triggers:

### 1. Component Removed

```csharp
[Query] [None(typeof(PBAudioSource), typeof(DeleteEntityIntention))]
private void HandleComponentRemoval(ref AudioSourceComponent component)
    => releaseAudioSourceComponent.Update(ref component);
```

### 2. Entity Destroyed

```csharp
[Query] [All(typeof(DeleteEntityIntention))]
private void HandleEntityDestruction(ref AudioSourceComponent component)
    => releaseAudioSourceComponent.Update(ref component);
```

### 3. World Disposed

```csharp
public void FinalizeComponents(in Query query)
{
    World.InlineQuery<ReleaseAudioSourceComponent, AudioSourceComponent>(
        in new QueryDescription().WithAll<AudioSourceComponent>(),
        ref releaseAudioSourceComponent);
}
```

**Cleanup tasks include:** pool returns, promise invalidation/dereferencing, cache dereferencing, custom logic (e.g., avatar teardown).

Prefer `ReleasePoolableComponentSystem<T, TProvider>` for pooled disposals. For the full cleanup lifecycle code example with `IForEach<T>` struct pattern, see [reference.md](reference.md).

### Pool hygiene in component disposal

If a component or asset wrapper rents from a pool in its constructor or factory (e.g. `RENDERERS_POOL.Get()`, `COLLIDERS_POOL.Get()`), the matching `Pool.Release(...)` **must** run in its `Dispose()`. Missing pool releases do not leak Unity objects (those are owned by the GameObject hierarchy and destroyed via `SafeDestroy(Root)`) but they strand the C# `List<T>` containers — the pool's working set inflates by one list per disposed instance and `Get()` keeps allocating fresh lists instead of reusing.

Audit pool hygiene by mirroring every `Pool.Get()` against a `Pool.Release(...)` on the disposal path. Scratch-only rentals inside `using (PoolExtensions.Scope<T>.AutoScope())` blocks return automatically — those don't need explicit release.

---

## Intention Components (Request-Response Pattern)

The codebase uses "intention" struct components to model async ECS requests. A system creates an entity with an intention; a loading system processes it and adds `StreamableLoadingResult<T>`.

**4-step pattern:**
1. **Create** -- Build an `AssetPromise` entity with the intention component
2. **Load** -- A `LoadSystemBase<TAsset, TIntention>` picks it up, loads the asset, adds `StreamableLoadingResult<T>`
3. **Consume** -- The requesting system calls `TryConsume` / `TryGetResult` to retrieve the result
4. **Cleanup** -- Dereference via `TryDereference`, destroy via `ForgetLoading` or `Consume`

**Base interfaces:** `IAssetIntention` (has `CancellationTokenSource`), `ILoadingIntention : IAssetIntention` (adds `CommonLoadingArguments`).

**Common intention types:** `GetTextureIntention`, `GetAudioClipIntention`, `GetSceneFacadeIntention`, `DeleteEntityIntention`, `GetProfilesBatchIntent`, and 15+ others.

See the **asset-promise-lifecycle** skill for full creation, polling, error handling, and cleanup patterns.

---

## ECS Singletons

> See CLAUDE.md section 8 for core singleton rules.

- Cache via `WorldExtensions` (`world.CacheCamera()`, `world.CachePlayer()`)
- Use `SingleInstanceEntity` struct; cache once, store in system field
- Use `TryGet` for mutation, `Has` for presence checks, `Query` for grouped access (prefer `TryGet` -- 2x faster)

---

## Testing Systems

- Use `UnitySystemTestBase<T>` for world lifecycle management
- Expose system constructors via `[InternalsVisibleTo]`
- Use NSubstitute for mocking dependencies
- Test multiple scenarios: creation, update (dirty flag), cancellation, cleanup

See the **testing-infrastructure** skill for full test patterns, utilities, and examples.

---

## Detailed Reference

For detailed code examples, see [reference.md](reference.md).

---

## Cross-References

- **testing-infrastructure** -- `UnitySystemTestBase<T>`, test utilities, mocking strategies
- **asset-promise-lifecycle** -- `AssetPromise` creation, polling, consumption, and cleanup
- **plugin-architecture** -- System registration via `InjectToWorld`, plugin lifecycle

Files in this skill

  • SKILL.md6.4 KB
  • reference.md4.9 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.