Develop
Develop
Select your platform

Systems in Spatial SDK

Updated: Sep 15, 2025

Overview

Systems contain the logic that brings your spatial applications to life. While components store data about what entities are and have, systems define what entities do. They run every tick, finding entities with specific component combinations and processing their data to create behaviors like movement, animation, user interaction, and physics simulation.

What systems do

Systems define what entities do by continuously processing entities that have specific component combinations:
  • Movement systems read Transform components to update positions
  • Rendering systems use Mesh and Material components to draw 3D objects
  • Input systems handle Grabbable components for user interaction

How systems work

Every system follows the same fundamental pattern:
  1. Query: Find entities with the components you need.
  2. Process: Read component data and apply your logic.
  3. Update: Save changes back to the entities.
This pattern ensures data consistency and enables the ECS architecture’s performance benefits.

System and component processing lifecycle

Understanding the component lifecycle is crucial:
  • Retrieve: Get components with entity.getComponent<ComponentType>().
  • Modify: Change component data directly.
  • Persist: Save changes with entity.setComponent(component).
Always retrieve the latest component state, modify it, and save it back to ensure thread safety and data consistency.

Components provide the data

First, entities need the right components to store behavioral data. Let’s use the same UpAndDown component from the Components documentation that stores animation parameters:
// An entity with both positional and animation data
val animatedCube = Entity.create(listOf(
    Transform(Pose(Vector3(0f, 1.5f, -2f))),   // Position in 3D space
    Mesh(Uri.parse("apk:///assets/cube.glxf")), // Visual representation
    UpAndDown(
        amount = 0.1f,                         // Animation distance (matches XML default)
        speed = 0.5f,                          // Animation speed (matches XML default)
        offset = 0.0f,                         // Current animation offset
        startPosition = Vector3(0f, 1.5f, -2f) // Base position
    )
))
Components store what the entity has: position data (Transform), animation configuration (UpAndDown), and visual representation (Mesh).

Systems provide the behavior

Now create an UpAndDownSystem that processes the UpAndDown component data to animate entities smoothly up and down. This example comes directly from the PhysicsSample:
import com.meta.spatial.core.Entity
import com.meta.spatial.core.Query
import com.meta.spatial.core.SystemBase
import com.meta.spatial.toolkit.Mesh
import com.meta.spatial.toolkit.Transform
import kotlin.math.sin

class UpAndDownSystem() : SystemBase() {
    private var lastTime = System.currentTimeMillis()
    private var entities = mutableListOf<Entity>()

    public fun resetEntities() {
        entities.clear()
    }

    private fun findNewObjects() {
        val q = Query.where { changed(Mesh.id) and has(UpAndDown.id, Transform.id) }
        for (entity in q.eval()) {
            if (entities.contains(entity)) {
                continue
            }

            val upAndDown = entity.getComponent<UpAndDown>()
            val transform = entity.getComponent<Transform>()
            upAndDown.startPosition = transform.transform.t
            entity.setComponent(upAndDown)
            entities += entity
        }
    }

    private fun animate(deltaTimeSeconds: Float) {
        for (entity in entities) {
            val transform = entity.getComponent<Transform>()
            val upAndDown = entity.getComponent<UpAndDown>()

            // Calculate new y offset based on speed and time passed.
            upAndDown.offset += (upAndDown.speed * deltaTimeSeconds)
            upAndDown.offset %= 1f

            // Update the y position set on the Transform component.
            transform.transform.t.y =
                upAndDown.startPosition.y - (sin(upAndDown.offset * Math.PI.toFloat()) * upAndDown.amount)

            // update the components on the entity
            entity.setComponent(transform)
            entity.setComponent(upAndDown)
        }
    }

    // Systems are run by calling the execute() function
    override fun execute() {
        val currentTime = System.currentTimeMillis()
        // clamp the max dt if the interpolation is too large
        val deltaTimeSeconds = Math.min((currentTime - lastTime) / 1000f, 0.1f)
        findNewObjects()
        animate(deltaTimeSeconds)
        lastTime = currentTime
    }
}
This system demonstrates the core pattern adapted from the PhysicsSample:
  1. Query discovers new entities with changed(Mesh.id) and required components, then caches them for efficient processing.
  2. Process uses frame-based delta timing to create smooth sine wave animation that’s independent of frame rate.
  3. Update directly modifies component data and saves changes back to entities.
The system specializes in one behavior: creating smooth up-and-down motion for entities. The implementation uses several important patterns:
  • Entity caching: Avoids expensive queries every frame by tracking discovered entities.
  • Delta time animation: Ensures consistent animation speed regardless of frame rate.
  • Direct transform manipulation: Modifies transform.transform.t.y directly for performance.
  • Offset accumulation: Uses the offset component field to track animation progress over time.

Registering systems and components

Before systems can process components, both must be registered with your application. This typically happens in your activity’s onCreate() method:
class MyActivity : AppSystemActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Register custom components first
        componentManager.registerComponent<UpAndDown>(UpAndDown.Companion)

        // Then register systems that use those components
        systemManager.registerSystem(UpAndDownSystem())
    }
}
Components must be registered before the systems that use them, as systems need to know about component types during initialization.

Key design principles

These principles help you create maintainable and performant systems:
  • Single responsibility: Each system should handle one specific behavior or concern. The UpAndDownSystem only handles vertical animation, not collision detection or user input. This makes systems easier to understand, test, and modify.
  • Use component data: Systems should be stateless and work only with component data. Instead of storing object positions inside a system, read and write Transform components. This keeps data centralized and accessible to other systems.
  • Work with others: Systems coordinate by sharing component data, not by calling each other directly. This loose coupling makes systems more flexible and reusable.
These patterns make your spatial applications more modular and easier to extend with new behaviors.

Next steps

Learn how to create custom systems and how queries work:
Did you find this page helpful?
Thumbs up icon
Thumbs down icon