Skip to content

Message

Outerminds edited this page Feb 5, 2019 · 12 revisions

Content


Description

A message is conceptually similar to a component in that it is an inert chunk of data, but it is not associated with an entity and it is ephemeral in nature: once consumed, it no longer exists. Messages allow thread-safe anonymous communication between systems and between Entia and other parts of a game.

3 parts make up the messaging system:

  • The Emitter<T> emits message such that others can consume them.
  • The Receiver<T> receives emitted message and enqueues them in a thread-safe queue. To process those messages, they must be dequeued which consumes them.
  • The Reaction<T> reacts immediately to an emitted message. To process those messages, a consumer must register a callback to the reaction which will be called by the emitter. Reactions allow for reactive systems.

A message must:

  • be a struct
  • implement the IMessage empty interface

A message should:

  • contain only public instance non-readonly fields (no methods, constructors, properties, events, etc.)
  • not have fields that stores an Entia type (such as a component, a system, a message, a queryable, an injectable, etc.) except for an entity

Usage

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

public enum ControllerInputs
{
    SwipeLeft,
    SwipeRight,
    ButtonX
}

namespace Components
{
    public struct Input : IComponent { public Queue<ControllerInputs> Inputs; }
    public struct Velocity : IComponent { public float X, Y; }
}

namespace Messages
{
    public struct DoSwipe : IMessage
    {
        public Entity Entity;
        public int Direction;
    }
    public struct DoJump : IMessage { public Entity Entity; }
}

namespace Systems
{
    // The 'IReact<T>' interface execute immediatly when a message of type 
    // 'T' is emitted. It is a shorthand for using 'Reaction<T>'.
    public struct InputInitializer : IReact<OnAdd<Components.Input>>
    {
        public readonly Components<Components.Input>.Write Inputs;

        // 'OnAdd<T>' is one of Entia's messages that are emitted everytime
        // a component of type 'T' is added to an entity. 
        // See the 'Entia.Messages' namespace for more Entia messages.
        public void React(in OnAdd<Components.Input> message)
        {
            ref var input = ref Inputs.Get(message.Entity);
            // The queue has to be initialized, otherwise it is 'null'.
            input.Inputs = new Queue<ControllerInputs>();
        }
    }

    // Alternatively to the 'IReact<T> system interface, 'Reaction<T>' can
    // be used to manually register and unregister callbacks.
    public struct InputReactionInitializer : IInitialize, IDispose
    {
        // An injectable that allows to register and unregister callbacks that 
        // will be called immediately when a message of type 'T' is emitted.
        // In most cases, using the 'IReact<T>' system interface will be
        // much more convenient since it registers and unregisters its
        // callback automatically.
        public readonly Reaction<OnAdd<Components.Input>> OnAddReaction;

        public readonly Components<Components.Input>.Write Inputs;

        // Register the 'React' callback.
        public void Initialize() => OnAddReaction.Add(React);
        // Unregister the 'React' callback.
        public void Dispose() => OnAddReaction.Remove(React);

        // 'OnAdd<T>' is one of Entia's messages that are emitted everytime
        // a component of type 'T' is added to an entity. 
        // See the 'Entia.Messages' namespace for more Entia messages.
        public void React(in OnAdd<Components.Input> message)
        {
            ref var input = ref Inputs.Get(message.Entity);
            // The queue has to be initialized, otherwise it is 'null'.
            input.Inputs = new Queue<ControllerInputs>();
        }
    }

    public struct InputCommander : IRun
    {
        // These are injectables that allow to emit messages of type 'T'.
        public readonly Emitter<Messages.DoSwipe> DoSwipeEmitter;
        public readonly Emitter<Messages.DoJump> DoJumpEmitter;

        public readonly Group<Entity, Read<Components.Input>> Group;

        public void Run()
        {
            foreach (ref readonly var item in Group)
            {
                var entity = item.Value1;
                ref readonly var input = ref item.Value2.Value;

                while (input.Inputs.TryDequeue(out var current))
                {
                    switch (current)
                    {
                        case ControllerInputs.SwipeLeft:
                            DoSwipeEmitter.Emit(new Messages.DoSwipe
                            {
                                Entity = entity,
                                Direction = -1
                            });
                            break;
                        case ControllerInputs.SwipeRight:
                            DoSwipeEmitter.Emit(new Messages.DoSwipe
                            {
                                Entity = entity,
                                Direction = 1
                            });
                            break;
                        case ControllerInputs.ButtonX:
                            DoJumpEmitter.Emit(new Messages.DoJump 
                            { 
                                Entity = entity 
                            });
                            break;
                    }
                }
            }
        }
    }

    public struct Jumper : IRun
    {
        // These are injectables that queues emitted messages of their type 'T'.
        public readonly Receiver<Messages.DoSwipe> DoSwipeReceiver;
        public readonly Receiver<Messages.DoJump> DoJumpReceiver;

        public readonly Components<Components.Velocity> Velocities;

        public void Run()
        {
            while (DoJumpReceiver.TryPop(out var message))
            {
                ref var velocity = ref Velocities.GetOrDummy(
                    message.Entity,
                    out var success);
                if (success) velocity.Y += 100f;
            }

            while (DoSwipeReceiver.TryPop(out var message))
            {
                ref var velocity = ref Velocities.GetOrDummy(
                    message.Entity,
                    out var success);
                if (success) velocity.X += 100f * message.Direction;
            }
        }
    }
}

Related