Entity-Component-System
O que é Entity-Component-System?
[editar | editar código-fonte]Entity-Component-System, ou ECS, é um padrão de arquitetura de software.
Esse padrão visa separar a representação dos dados de um objeto da lógica de seu comportamento, e consiste na interação entre três grandes peças: as entidades, os componentes e os sistemas.
- Entidades: são objetos de propósito geral, normalmente identificados por um
id
. Não possuem lógica nem dados, exceto pelo identificador e por uma lista de componentes, os quais definem suas propriedades e características.
- Componentes: representam dados de um aspecto (posição, velocidade, etc.) que pode ser atribuído a uma entidade. Também não possuem lógica.
- Sistemas: são responsáveis por realizar as operações nos componentes e consequentemente implementar os comportamentos das entidades.
A arquitetura é considerada um padrão orientado a dados. Um dos primeiros casos de uso do design orientado a dados baseado em composição em um software de larga escala ocorreu em um jogo digital chamado Thief: The Dark Project (1998), onde os desenvolvedores adotaram uma filosofia de criar componentes altamente reutilizáveis para a game engine. Segundo o lead programmer do jogo Tom Leonard, essa abordagem resultou na não existência de qualquer tipo de hierarquia (definida em código) entre objetos do jogo. A abordagem deu tão certo que a equipe foi capaz de usar o mesmo arquivo executável durante grande parte do desenvolvimento para Thief e System Shock, dois jogos muito diferentes, apenas mudando a hierarquia de objetos e conjunto de dados durante a execução.
Desde então, a arquitetura foi sendo refinada e melhor descrita. Diversos frameworks foram criados e o padrão tem sido amplamente usado no desenvolvimento de jogos.
Exemplo
[editar | editar código-fonte]Considere que gostaríamos de desenvolver um jogo onde os objetos são capazes de se mover e são desenhados na tela em suas respectivas posições. Portanto, cada objeto pode ter uma posição no espaço (position
), uma velocidade (velocity
) e um sprite que será desenhado (sprite
). Usando ECS, temos que cada característica dessas é um componente diferente, além de termos dois sistemas: um para movimentar as entidades e outro para desenhar as entidades. Um UML dessa implementação seria:
Assim, o update()
dos sistemas seria chamado regularmente pela classe Game
, que gerencia o jogo, providenciando uma lista de entidades para a função. Então, a função update()
de cada sistema é algo como:
class MovementSystem : System {
override fun update(entities: List<Entity>) {
for (entity in entities) {
if entity.contains(Position) and entity.contains(Velocity):
positionComponent = entitiy.getComponent(Position)
velocityComponent = entitiy.getComponent(Velocity)
positionComponent.position += velocityComponent.velocity
}
}
}
class DrawSystem : System {
override fun update(entities: List<Entity>) {
for (entity in entities) {
if entity.contains(Position) and entity.contains(Sprite):
positionComponent = entitiy.get_component(Position)
spriteComponent = entitiy.get_component(Sprite)
draw(positionComponent.position, spriteComponent.texture)
}
}
fun draw(position: Vector2D, texture: Texture) { /* ... */ }
}
Note como uma entidade pode, em tempo de execução, deixar de se mover: basta remover seu componente Velocity
. Semelhantemente, uma entidade poderia deixar de ser desenhada na tela, caso tivesse seu Sprite
removido. Além disso, o sistema de movimentação poderia ser completamente reimplementado sem a necessidade de alterar os componentes ou entidades.
Por que usar ECS?
[editar | editar código-fonte]Um dos grandes contrapontos entre a ECS e o uso da programação orientada a objetos tradicional é quanto ao uso de herança. A arquitetura ECS sempre favorece a criação por meio da composição em vez da herança, isso acontece porque a composição traz mais flexibilidade em relação à herança e evita a repetição desnecessária de código.
Com a composição, entidades podem ter propriedades dinamicamente alteradas, ou seja, modificas em tempo de execução. Outra vantagem do uso da composição em vez da herança é que a herança inevitavelmente aumenta o grau de complexidade do código por conta da hierarquia entre classes. Já a composição, por sua vez, não apresenta esse aumento de complexidade e assim auxilia os programadores a terem um entendimento mais claro e rápido do código desenvolvido.
Outra divergência entre o ECS e o POO tradicional é quanto ao encapsulamento de dados. Em POO, os objetos restringem o acesso aos seus dados e disponibilizam métodos controlados para a modificação desses dados. Já na arquitetura ECS, os dados e os comportamentos não possuem essa relação direta e os dados são expostos completamente por objetos POD (plain old data).
Um grande ponto positivo da separação comportamentos/dados é que o código de comportamentos fica mais enxuto, localizado e mais fácil de ler. Isso traz grandes benefícios para a aplicação porque qualquer mudança necessária em um sistema fica restrita ao código daquele sistema e não requer mudanças em componentes ou entidades. Além disso, com o código desta maneira a procura de erros e testabilidade fica mais facil pelas mesmas razões.
A manipulação da memória em sistemas ECS pode ser feita de algumas maneiras diferentes, cada uma possuindo vantagens distintas, portanto sua escolha deve depender do que é desejável otimizar. Mais informações podem ser vistas aqui.
De maneira geral, a arquitetura favorece o armazenamento de dados em blocos contíguos de memória. Esse armazenamento de memória beneficia o cache-hit, ou seja, faz com que os dados a serem utilizados em seguida pela aplicação estejam mais frequentemente na memória cache, então não precisam ser solicitados a outras memórias, melhorando o desempenho. Manipular dados em bloco contíguos também reduz a fragmentação de memória. Além disso, por conta da interdependência entre os sistemas, o padrão ECS se torna ainda mais eficiente com a utilização da programação paralela e multithreading.
Vale ressaltar que o ganho de performance depende da natureza da aplicação e do uso eficiente das partes do ECS, não sendo uma garantia universal de melhoria.
A arquitetura ECS é muito recomendada para projetos de desenvolvimento de jogos, simulações complexas, e projetos que necessitam de ampla flexibilidade como projetos de realidade virtual.
Aqui está uma lista de projetos ativos e com licença MIT que utilizam a arquitetura ECS nas mais diversas linguagens de programação:
- Java: Dominion
- C++: Entt
- C#: Flecs
- Kotlin: Fleks
- RUST: Specs
- Python: Esper
- Go: Arche
- Zig: Mach
- Lua: LuaECS
Uma lista dos projetos mais relevantes que utilizam do padrão ECS pode ser encontrada aqui. Dois dos mais relevantes são os jogos Minecraft e Overwatch 2.
Por que não usar ECS?
[editar | editar código-fonte]A arquitetura ECS pode não ser tão recomendada para aplicações que não necessitem de perfomance ou que tenham requisitos de processamento limitados. Para projetos mais enxutos, a arquitetura pode trazer uma sobrecarga desnecessária, dificultando o desenvolvimento.
Além disso, se sua aplicação não necessita de tamanha flexibilidade, ou seja, os dados são relativamente estáticos e não requerem tanta manipulação ou se as componentes da sua aplicação são utilizadas de maneira limitada pelas entidades então o overhead de memória da arquitetura possa não valer a pena.
Por fim, vale ressaltar que o padrão Entity-Component-System é um padrão relativamente novo, com um grande potencial em um escopo bastante específico, e acima de tudo de relativa dificuldade de implementação, logo não é recomendado o uso do padrão caso a equipe de desenvolvimento não possua conhecimento sólido da abordagem da arquitetura.
Bibliografia
[editar | editar código-fonte]EHRLICH, Justin. The Component Entity System for Virtual Environments.
HÄRKÖNEN, Toni. Advantages and Implementation of Entity-Component-Systems. 2019.
LANGE, Patrick; WELLER, Rene; ZACHMANN, Gabriel. Wait-free hash maps in the entity-component-system pattern for realtime interactive systems. In: 2016 IEEE 9th Workshop on Software Engineering and Architectures for Realtime Interactive Systems (SEARIS). IEEE, 2016. p. 1-8.
LEONARD, TOM. Postmortem: Thief: The Dark Project. Gamasutra. Disponível em: https://web.archive.org/web/20220518165826/http://www.gamasutra.com/view/feature/3355/postmortem_thief_the_dark_project.php?print=1.
MERTENS, SANDERS. ECS-FAQ. GitHub. Disponível em: https://github.com/SanderMertens/ecs-faq?tab=readme-ov-file.
WEBSTER, Carson. Entity Component Systems Usefulness.