Message
Outerminds edited this page Feb 5, 2019
·
12 revisions
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
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;
}
}
}
}