Skip to content
Magicolo edited this page Jan 31, 2019 · 17 revisions

Content


Description

A system a piece of logic of a game. It can be thought as an entity processor since a typical system will query a subset of entities and process their components in some way. To accomplish this, a system can claim parts of the world as injectables by declaring public fields of an IInjectable type. All types under the namespace Entia.Injectables are such injectables.

A system has to implement at least one of the ISystem interfaces found under the namespace Entia.Systems. Each one of these system interfaces will be executed during a specific execution phase.

A system may also be reactive if it implements at least one of the system interface IReact<T>. The type T is a message type and the IReact<T> method will be called immediately when a message of that type is emitted.

A system must:

  • be a struct
  • implement at least one of ISystem interfaces which can be found under the namespace Entia.Systems
  • have its public instance fields have injectables types
  • store state as private instance fields

A system should:

  • have its public instance injectable fields be readonly.
  • not refer to another system (to share logic between systems, use utility functions)
  • not have fields that store another Entia type (such as a component, a system, a message, a queryable, etc.) except for an entity or injectable.

Usage

using System.Collections.Generic;
using Entia;
using Entia.Injectables;
using Entia.Queryables;
using Entia.Systems;

namespace Components
{
    public struct Health : IComponent { public float Current, Maximum; }
}

namespace Messages
{
    public struct OnBirth : IMessage { public Entity Entity; }
    public struct OnDeath : IMessage { public Entity Entity; }
    public struct DoKill : IMessage { public int Count; }
}

namespace Systems
{
    // The 'IRun' interface is one of the system interfaces that
    // inherit from the 'ISystem' interface.
    // It executes during the 'Phases.Run' phase.
    public struct Death : IRun
    {
        // Queries all entities that have a 'Health' component.
        public readonly struct Query : IQueryable
        {
            public readonly Entity Entity;
            public readonly Write<Components.Health> Health;
        }

        // Require a couple injectables.
        public readonly AllEntities Entities;
        public readonly Emitter<Messages.OnDeath> OnDeath;
        public readonly Group<Query> Group;

        public void Run()
        {
            foreach (ref readonly var item in Group)
            {
                // Unpack the group item.
                var entity = item.Entity;
                ref var health = ref item.Health.Value;

                if (health.Current <= 0)
                {
                    // Emits a on death message such that other 
                    // systems can be notified.
                    OnDeath.Emit(new Messages.OnDeath { Entity = entity });
                    Entities.Destroy(entity);
                }
            }
        }
    }

    // These interfaces are all system interfaces.
    // The 'IInitialize' interface executes during the 'Phases.Initialize' phase.
    // The 'IRun' interface executes during the 'Phases.Run' phase.
    // The 'IReact<T>' interface execute immediatly when a message of type 
    // 'T' is emitted.
    public struct Killer :
        IInitialize, IRun,
        IReact<Messages.OnBirth>, IReact<Messages.OnDeath>
    {
        // An injectable that gives write access to 'Health' components.
        public readonly Components<Components.Health>.Write Healths;
        // An injectable that queues all 'DoKill' messages.
        public readonly Receiver<Messages.DoKill> DoKill;

        // A system state must be private.
        // This set will keep track of the entities have been born.
        HashSet<Entity> _live;

        public void Initialize()
        {
            // The set must be initialized or else it will be 'null'.
            _live = new HashSet<Entity>();
        }

        public void Run()
        {
            var enumerator = _live.GetEnumerator();
            // Dequeues all 'DoKill' messages.
            while (DoKill.TryPop(out var message))
            {
                for (int i = 0; i < message.Count && enumerator.MoveNext(); i++)
                {
                    ref var health = ref Healths.GetOrDummy(
                        enumerator.Current,
                        out var success);
                    if (success) health.Current = 0;
                }
            }
        }

        // Called immediately after an 'OnBirth' message is emitted.
        public void React(in Messages.OnBirth message) =>
            _live.Add(message.Entity);
        // Called immediately after an 'OnDeath' message is emitted.
        public void React(in Messages.OnDeath message) =>
            _live.Remove(message.Entity);
    }
}

Related