Component-based Development #2/4 How do components communicate with each other?

ARBEON Co., Ltd.
Arbeon
Published in
5 min readJan 11, 2023

Hello! My name is David from Arbeon’s MetaverseLAB. Great to see you again!

This is the second article following the last: Component-based development #1/4. In the last session, we introduced what CBD is as well as its pros and cons. Today, I will cover how components communicate with one another.

Are you excited to find out what it will be about?

Let’s start right away!

How do components

communicate with each other?

Before we start, let’s review the last session briefly.

Last session
Component-based development (hereafter, CBD) is the methodology of creating component and combining them to create objects and modify software. Its benefits and inconveniences are:

Benefits

  1. It is possible to make various objects by expanding the components
  2. It is relatively convenient to make an editor
  3. It reduces work on compiling

Inconveniences

  1. It is difficult to design the components
  2. It is slower than when a component is not used
  3. It requires more tests

Communication among components
Components are highly independent, as they operate even in the closed environment of the CBD. However, things do not always work out as we want. At some point, it is necessary for the components to communicate with each other. Then, what should be done? They communicate through events.

By communicating through events, which will be discussed soon, you will find out that components can communicate with each other while retaining loose coupling and high cohesion.

[Figure 1. All the components register as observers for the EventContainer (its name may differ)]

[Figure 2. Component A delivers the event that the task is completed to the EventContainer. At this time, it is also possible to hand over several parameters as well]

[Figure 3. The event container informs all the components that are registered as observers that the event has occurred]

[Figure 4. The software operates as each component performs the tasks for the corresponding event and repeats figure 2 to 4]

Let’s demonstrate it with a simple example.

  • Object Manager
  • Class to manage all objects created in the program
  • Component
  • Parent class of all the components that exist in the program
  • ConcreteComponentA, ConcreteComponentB, ConcreteComponentC
    : Only simple features are implemented as an example
  • In theory, the entity should contain a component. However, there is no issue even when only the id is assigned to the entity.
    ex) ear component, leg component, hair component
  • Entity
  • Class that explains where the components belong to; it is replaceable with an integer type if there is no major function
  • IEventParam
  • Every event parameter class or structure has to inherit this interface
  • EventType
  • List of the events used in this program
  • DefaultEventParam
  • It inherits the event parameter IEventParam interface

[Source Code]

namespace ComponentWithEvent
{
public class Component
{
public int EntityId { get; set; }

public virtual void OnEvent(EventType eventType, IEventParam param)
{
//code here what todo
}
}
}
namespace ComponentWithEvent
{
public class ConcreteComponentA : Component
{
public override void OnEvent(EventType eventType, IEventParam param)
{
base.OnEvent(eventType, param);
switch (eventType)
{
case EventType.Event1:
Console.WriteLine($"componentName : ConcreteComponentA, entityId : {EntityId} , eventType : {eventType}");
break;
default:
break;
}
}
}
}
namespace ComponentWithEvent
{
public class ConcreteComponentB : Component
{
public override void OnEvent(EventType eventType, IEventParam param)
{
base.OnEvent(eventType, param);
switch (eventType)
{
case EventType.Event2:
Console.WriteLine($"componentName : ConcreteComponentB, entityId : {EntityId} , eventType : {eventType}");
break;
default:
//nothing todo
break;
}
}
}
}
namespace ComponentWithEvent
{
public class ConcreteComponentC : Component
{
public override void OnEvent(EventType eventType, IEventParam param)
{
base.OnEvent(eventType, param);
switch (eventType)
{
case EventType.Event3:
var convertedParam = (DefaultEventParam)param;
//These examples can cause events to be ignored.

if (convertedParam.target != this.EntityId)
return;

Console.WriteLine($"componentName : ConcreteComponentC, entityId : {EntityId} , eventType : {eventType}"); break;
default:
break;
}
}
}
}
namespace ComponentWithEvent
{
public static class ObjectManager
{
static HashSet<Entity> = new HashSet<Entity>();
static List components<Entity> = new List<Entity>();
static int objectCount = int.MinValue;
public static Entity GenerateEntity()
{
var newEntity = new Entity(objectCount++);
entities.Add(newEntity);
return newEntity;
}

public static void AddComponent<T>(Entity entity)
where T : Component, new()
{
T newComponent = new T();
newComponent.EntityId = entity.Id;
components.Add(newComponent);
}

public static void Emit(EventType eventType, IEventParam param)
{
for (int i = 0; i < components.Count; i++)
{
components[i].OnEvent(eventType, param);
}
}
}
}
namespace ComponentWithEvent
{
public interface IEventParam
{
}
}
namespace ComponentWithEvent
{
public struct DefaultEventParam : IEventParam
{
public int target;

public DefaultEventParam(int target)
{
this.target = target;
}
}
}
namespace ComponentWithEvent
{
public class Entity
{
public int Id { get; private set }
public Entity(int id)
{
this.Id = id;
}
}
}

[ Main Program ]

using ComponentWithEvent;

//Create Entity A
Entity entityA = ObjectManager.GenerateEntity();
//Create by having Component A and Component B point to Entity
ObjectManager.AddComponent<ConcreteComponentA>(entityA);
ObjectManager.AddComponent<ConcreteComponentB>(entityA);

//Create Entity B
Entity entityB = ObjectManager.GenerateEntity();
//Create one more component C and point to B
ObjectManager.AddComponent<ConcreteComponentC>(entityB);

Entity entityC = ObjectManager.GenerateEntity();
ObjectManager.AddComponent<ConcreteComponentA>(entityC);
ObjectManager.AddComponent<ConcreteComponentC>(entityC);

DefaultEventParam eventParam = new DefaultEventParam(entityB.Id);
//Make Event
ObjectManager.Emit(EventType.Event1, eventParam);

Console.WriteLine("============================================");

eventParam = new DefaultEventParam(entityB.Id);
ObjectManager.Emit(EventType.Event2, eventParam);

Console.WriteLine("============================================");

eventParam = new DefaultEventParam(entityC.Id);
ObjectManager.Emit(EventType.Event3, eventParam);

[ Result ]

componentName : ConcreteComponentA, entityId : -2147483648 , eventType : Event1
componentName : ConcreteComponentA, entityId : -2147483646 , eventType : Event1
============================================
componentName : ConcreteComponentB, entityId : -2147483648 , eventType : Event2
============================================
componentName : ConcreteComponentC, entityId : -2147483646 , eventType : Event3

Today, we covered how components communicate. As each component’s OnEvent is called through the event, it is generally slower than when the component-based development is not used. Events can be filtered using various methods to improve performance.

In the next article, we will cover how the component-based development is used in Unity. Hope this article was helpful!

:)

Ref.

[1] https://ko.wikipedia.org/wiki
[2] https://docs.unity3d.com/kr/2018.4/Manual/Components.html
[3] https://docs.unrealengine.com/4.26/en-US/Basics/Components/
[4] https://hrcak.srce.hr/file/69311
[5] https://www.youtube.com/watch?v=cxyG_REKD4Y
[6] http://gameprogrammingpatterns.com/component.html

--

--