Flecs ECS Documentation

repository·master·Indexed 27 days ago

https://github.com/sandermertens/flecs

A high-performance, lightweight Entity Component System (ECS) for games and simulations. Flecs supports millions of entities and features a zero-dependency C99 API and a type-safe C++17 API. Key capabilities include entity relationships, hierarchies, prefabs, a lockless multi-core scheduler, and an integrated reflection framework with JSON serialization. It includes a web-based monitoring UI (Flecs Explorer) and supports browser deployment via Emscripten.

Tokens
71.2K
Snippets
228
Records
342
Agent score
90%

What's inside Flecs

  1. Introduction to Flecs Script

    master
    Flecs Script is a runtime interpreted Domain Specific Language (DSL) designed for creating entities and components. It is optimized for defining scenes, assets, and configurations. It provides native support for named entities, hierarchies, inheritance, component value assignment, expressions, variables, conditionals, loops, and integration with templates (procedural assets).
  2. Overview of Flecs ECS

    master

    Flecs is a fast, lightweight Entity Component System (ECS) designed for building games and simulations with millions of entities. It provides a zero-dependency C99 API and a modern, type-safe C++17 API.

    Key features include:

    • Entity Relationships: Full support for complex relationships.
    • Hierarchies & Prefabs: Native support for scene graphs and object templates.
    • Performance: Cache-friendly archetype/SoA storage and a fast lockless multi-core scheduler.
    • Reflection: Integrated reflection framework with JSON serialization and runtime component support.
    • Web Support: Can run in the browser via Emscripten.
    • Monitoring: Includes a web-based UI (Flecs Explorer) for monitoring and controlling applications.
  3. Understand Observers in Flecs

    master
    Observers are a mechanism for reacting to events. Unlike systems, which execute periodically for all matching entities, observers are executed only when a specific matching event occurs. Events can be user-defined or built-in (such as adding, removing, or setting components).
  4. Use the Flecs Query Language

    master
    The Flecs Query Language is a string-based representation of queries used to match entities at runtime. This is particularly useful for tooling, modding, or requesting data from game servers. A query consists of a comma-separated list of 'terms', where each term is a condition an entity must satisfy.
  5. Understand Flecs Pipelines

    master

    A pipeline is a list of systems executed when ecs_progress/world::progress is called. The pipeline is determined by a pipeline query (a regular ECS query matching system entities).

    Key characteristics:

    • Ordering: By default, systems are ordered by their entity ID for determinism. Systems can also be ordered using cascade or group_by mechanisms.
    • Sync Points: Pipelines analyze component read/write access to automatically insert sync points, ensuring enqueued commands are processed so subsequent systems see mutations.
  6. Use change detection in queries

    master

    Flecs allows queries to detect if components have changed. This is useful for optimizing systems so they only run when necessary.

    To use change detection:

    1. Use .detect_changes() when building the query.
    2. Use .changed() (C++) or .is_changed() (Rust) on the iterator to check if the current table has changed.
    3. In C++, if you are using a query to write components (using inout or out terms) but determine no changes are actually needed, call it.skip() to prevent the iterator from marking those components as dirty.

    Note: Change detection is only supported for cached queries.

    // C++ Example
    // Query used for change detection.
    flecs::query<const Position> q_read = world.query_builder<const Position>()
      .detect_changes()
      .build();
    
    // Query used to create changes
    flecs::query<Position> q_write = world.query<Position>(); // defaults to inout
    
    // Test if changes have occurred for anything matching the query.
    bool changed = q_read.changed();
    
    // Setting a component will update the changed state
    flecs::entity e = world.entity()
      .set<Position>({10, 20});
    
    q_write.run([](flecs::iter& it) {
      if (it.next()) {
        if (!changed) {
          it.skip();
        }
      }
    });
    
    q_read.run([](flecs::iter& it) {
      if (it.next()) {
        if (it.changed()) {
          // Respond to changes
        }
      }
    });
  7. Organize scripts with module and include statements

    master

    Module Statement

    The module statement encapsulates all contents of a script into a specific namespace (an entity with the Module tag).

    Include Statement

    The include statement loads another script file.

    • Paths are relative to the current script.
    • Absolute paths and .. are not allowed.
    • If the path doesn't end in .flecs, the extension is added automatically.
    • In managed scripts, the included script is also loaded as a managed script.
    • include must be at the root scope and cannot be inside a template.
    module components.transform
    
    struct Position {
      x = f32
      y = f32
    }
    
    include components
    include scenes/level_1.flecs
  8. Create a Custom Pipeline

    master

    Applications can define custom pipelines to control exactly which systems are matched and in what order. A custom pipeline uses a query to match systems (e.g., systems with a specific tag) and then replaces the builtin pipeline via set_pipeline.

    // C++ example: Create custom pipeline matching systems with 'Foo' tag
    flecs::entity pipeline = world.pipeline()
      .with(flecs::System)
      .with(Foo) 
      .build();
    
    // Configure the world to use the custom pipeline
    world.set_pipeline(pipeline);
    
    // Create system that runs in this pipeline
    auto move = world.system<Position, Velocity>("Move")
      .kind(Foo)
      .each([](Position& p, Velocity& v) { ... });
    
    world.progress();
  9. Use query variables for hierarchical lookups and constraints

    master

    Query variables allow you to perform by-name lookups within a hierarchy or constrain query results by setting a variable before iteration.

    Lookup Variables: Use $this to refer to the current entity being matched. You can look up child entities by name relative to $this. For example, SpaceShip($this), !Powered($this.cockpit) matches spaceships where the child entity named cockpit does not have the Powered component.

    Setting Variables: You can define variables in a query (e.g., $Location) and then set them to a specific entity before iterating. This allows reusing a single query for different constraints, which is more performant than creating multiple queries.

    // C++ Example: Defining and setting a variable
    auto q = world.query_builder()
      .with<SpaceShip>()
      .with<DockedTo>().second("$Location")
      .with<Planet>().src("$Location")
      .build();
    
    flecs::entity earth = world.entity();
    
    // Constrain results to entities docked to 'earth'
    q.iter().set_var("Location", earth).each([]{
      // iterate as usual
    });
  10. Run systems manually or via pipeline

    master

    Manually running a single system

    You can trigger a specific system's execution directly.

    Running all systems in a pipeline

    By default, systems are registered to a pipeline (ordered by their phase, e.g., EcsOnUpdate). To execute all systems in the pipeline, call the world's progress method. Note that running pipelines requires the FLECS_PIPELINE addon (enabled by default).

    Preventing pipeline registration

    To prevent a system from being automatically added to a pipeline, set its phase/kind to 0 during declaration.

    // C: Run a specific system
    ecs_run(world, ecs_id(Move), 0.0 /* delta_time */, NULL /* param */)
    
    // C++: Run all systems in pipeline
    world.progress();
    
    // C++: Prevent system from being in pipeline
    flecs::system sys = world.system<Position, const Velocity>("Move")
        .kind(0)
        .each([](Position& p, const Velocity &v) { /* ... */ });
  11. Understand Cleanup Order and World Teardown

    master

    Cleanup traits do not enforce a strict execution order. This is critical when using OnRemove triggers or hooks, as the order depends on the deletion sequence.

    Best Practices for Predictable Cleanup

    To ensure predictable cleanup, especially during world teardown:

    1. Use Modules: Organize components, triggers, observers, and systems into modules. This ensures they stay alive as long as possible and are deleted after the entities using them.
    2. Tag Non-Module Scopes: If you organize entities under a non-module entity in the root, add the EcsModule (or flecs::Module / Ecs.Module) tag to that root to prevent it from being cleaned up prematurely with regular entities.

    World Teardown Sequence

    When a world is deleted, it follows these steps:

    1. Find all root entities: Entities without the builtin ChildOf relationship (excluding empty entities).
    2. Query out modules, components, observers, and systems: Prevents components from being deleted before their users, and ensures observers/systems remain active while events are being generated.
    3. Query out entities with no children: Reduces complex cleanup logic.
    4. Delete root entities.
    5. Delete everything else: Remaining entities are deleted; at this stage, cleanup traits are no longer considered and order is undefined.