Product roadmap

Sur les DOTS : Système de composants d'entités

LUCAS MEIJER / UNITY TECHNOLOGIESContributor
Mar 8, 2019|9 Min
Sur les DOTS : Système de composants d'entités
Cette page a été traduite automatiquement pour faciliter votre expérience. Nous ne pouvons pas garantir l'exactitude ou la fiabilité du contenu traduit. Si vous avez des doutes quant à la qualité de cette traduction, reportez-vous à la version anglaise de la page web.

Cet article fait partie d'une série de billets consacrés à notre nouvelle pile technologique orientée données (DOTS). Il donne un aperçu des raisons pour lesquelles nous sommes arrivés là où nous sommes aujourd'hui et de nos objectifs futurs.

Dans mon dernier article, j'ai parlé de HPC# et de Burst en tant que technologies fondamentales de bas niveau pour Unity. J'aime appeler ce niveau de notre pile le "moteur de jeu". N'importe qui peut utiliser cette pile pour écrire un moteur de jeu. Nous pouvons. Nous le ferons. Vous aussi, vous pouvez le faire. Vous n'aimez pas les nôtres ? Rédigez votre propre texte ou modifiez le nôtre à votre guise.

Le système de composants d'Unity

La couche suivante que nous construisons est un nouveau système de composants. Unity a toujours été centré sur les concepts de composants. Vous ajoutez un composant Rigidbody à un GameObject et il commencera à tomber. Vous ajoutez un composant Light à un GameObject et celui-ci commence à émettre de la lumière. Ajoutez un composant AudioEmitter et le GameObject commencera à produire du son.

Il s'agit d'un concept très naturel pour les programmeurs comme pour les non-programmeurs, et il est facile de créer des interfaces utilisateur intuitives. Je suis en fait assez étonné de voir à quel point ce concept a bien vieilli. Si bien que nous voulons le garder.

Ce qui n'a pas bien vieilli, c'est la manière dont nous avons mis en œuvre notre système de composants. Il a été écrit dans un esprit orienté objet. Les composants et les objets de jeu sont des objets "c++ lourds". Leur création/destruction nécessite un verrou mutex pour modifier la liste globale des pointeurs d'objets id->objectpointers. Tous les objets de jeu ont un nom. Chacun d'entre eux reçoit un objet enveloppant C# qui pointe vers l'objet C++. Cet objet C# peut se trouver n'importe où dans la mémoire. L'objet C++ peut également se trouver n'importe où dans la mémoire. Des caches manqués à foison. Nous essayons d'atténuer les symptômes du mieux que nous pouvons, mais il y a des limites à ce que nous pouvons faire.

Avec un état d'esprit axé sur les données, nous pouvons faire beaucoup mieux. Nous pouvons conserver les mêmes propriétés intéressantes du point de vue de l'utilisateur (ajoutez un composant Rigidbody, et l'objet tombera), mais aussi obtenir des performances et un parallélisme étonnants grâce à notre nouveau système de composants.

Ce nouveau système de composants est notre système de composants d'entités (ECS). En gros, ce que vous faites avec un objet de jeu aujourd'hui, vous le faites avec une entité dans le nouveau système. Les composants sont toujours appelés composants. Qu'est-ce qui est différent ? La présentation des données.

Examinons quelques modèles courants d'accès aux données
Un composant typique que vous écririez dans Unity de manière traditionnelle pourrait ressembler à ceci :

classe Orbit : MonoBehaviour
{
public Transform _objectToOrbitAround;

void Update()
{
//ne pas tenir compte du fait que les maths sont cassées, ce n'est pas le but ici :)
var currentPos = GetComponent<Transform>().position;
var targetPos = _objectToOrbitAround.position;
GetComponent<RigidBody>().velocity += SomehowSteerTowards(currentPos,targetPos)
}
}

Ce schéma revient sans cesse. Un composant doit trouver un ou plusieurs autres composants sur le même objet de jeu et lire/écrire certaines valeurs sur celui-ci.

Il y a beaucoup de choses qui ne vont pas :

  • La méthode Update() est appelée pour un seul composant orbital. Le prochain appel à Update() pourrait concerner un composant complètement différent, ce qui entraînerait probablement l'éviction de ce code du cache la prochaine fois qu'il devra exécuter cette trame pour un autre composant Orbit.
  • Update() doit utiliser GetComponent() pour trouver son corps rigide. (Il pourrait être mis en cache, mais il faudrait alors veiller à ce que le composant Rigidbody ne soit pas détruit).
  • Les autres composants sur lesquels nous opérons se trouvent à des endroits complètement différents de la mémoire.

L'agencement des données utilisé par ECS reconnaît qu'il s'agit d'un schéma très courant et optimise l'agencement de la mémoire pour que les opérations de ce type soient rapides.

Mise en page des données ECS

L'ECS regroupe en mémoire toutes les entités qui ont exactement le même ensemble de composants. Il appelle cet ensemble un archétype. Voici un exemple d'archétype : "Position & Velocity & Rigidbody & Collider". ECS alloue la mémoire par tranches de 16k. Chaque bloc ne contiendra que les données relatives aux composants des entités d'un seul archétype.

Au lieu que la méthode de mise à jour de l'utilisateur recherche d'autres composants sur lesquels opérer au moment de l'exécution, par instance d'orbite, dans le système ECS, il faut déclarer statiquement "Je veux effectuer des opérations sur toutes les entités qui ont à la fois une vélocité, un corps rigide et un composant orbite". Pour trouver toutes ces entités, il suffit de trouver tous les archétypes qui correspondent à une "requête de recherche de composants" spécifique. Chaque archétype possède une liste de Chunks où sont stockées les entités de cet archétype. Nous passons en revue tous ces blocs et, à l'intérieur de chacun d'entre eux, nous effectuons une boucle linéaire de mémoire serrée, pour lire et écrire les données du composant. Cette boucle linéaire qui exécute le même code sur chaque entité constitue également une opportunité de vectorisation pour Burst.

Dans de nombreux cas, ce processus peut être trivialement divisé en plusieurs tâches, ce qui permet au code opérant le composant ECS de fonctionner à près de 100 % d'utilisation des cœurs.

ECS fait tout ce travail pour vous, il vous suffit de fournir le code que vous souhaitez exécuter sur chaque entité. (Vous pouvez cependant effectuer l'itération des morceaux manuellement si vous le souhaitez).

Lorsque vous ajoutez/supprimez un composant d'une entité, il change d'archétype. Nous la déplaçons de son bloc actuel vers un bloc du nouvel archétype, et nous échangeons la dernière entité du bloc précédent pour "combler le trou".

Dans ECS, vous déclarez également de manière statique ce que vous avez l'intention de faire avec les données du composant. En lecture seule ou en lecture-écriture. En promettant (la promesse est vérifiée) de ne lire que dans le composant Position, ECS peut obtenir une programmation plus efficace de ses tâches. Les autres tâches qui souhaitent également lire le composant Position n'auront pas à attendre.

Cette disposition des données nous permet également de répondre à une frustration de longue date, à savoir les temps de chargement et les performances de la sérialisation. Le chargement/le flux de données ECS pour une grande scène n'est pas beaucoup plus que le simple chargement d'octets bruts à partir d'un disque et leur utilisation en l'état.

C'est la raison pour laquelle la démo Megacity se charge en quelques secondes sur un téléphone.

Accidents heureux

Si les entités peuvent faire ce que les objets de jeu font aujourd'hui, elles peuvent faire plus grâce à leur légèreté. En fait, qu'est-ce qu'une entité ? Dans une première version de ce billet, j'ai écrit "nous stockons les entités en morceaux", puis je l'ai remplacé par "nous stockons les données des composants des entités en morceaux". Il est important de faire cette distinction et de comprendre qu'une entité n'est qu'un nombre entier de 32 bits. Il n'y a rien à stocker ou à allouer pour lui, si ce n'est les données de ses composants. En raison de leur faible coût, vous pouvez les utiliser pour des scénarios pour lesquels les objets de jeu n'étaient pas adaptés. Comme l'utilisation d'une entité pour chaque particule individuelle dans un système de particules.

HPC#, Burst, ECS. Génial, mais où est mon moteur de jeu ?

La prochaine couche que nous devons construire est très importante. Il s'agit de la couche "moteur de jeu" composée d'éléments tels que le "moteur de rendu", la "physique", le "réseau", l'"entrée", l'"animation", etc. C'est à peu près là que nous en sommes aujourd'hui. Nous avons commencé à travailler sur ces pièces, mais elles ne seront pas prêtes du jour au lendemain.

Cela peut sembler ennuyeux. D'une certaine manière, c'est le cas, mais d'une autre manière, ça ne l'est pas. Comme ECS et tout ce qui est construit au-dessus sont écrits en C#, ils peuvent fonctionner dans Unity traditionnel. Parce qu'il fonctionne dans Unity, vous pouvez écrire des composants ECS qui utilisent des fonctionnalités pré-ECS. Il n'existe pas à l'heure actuelle de système de dessin de maillage ECS pur. Cependant, vous pouvez écrire un MeshRenderSystem ECS qui utilise l'API Graphics.DrawMeshIndirect pré-ECS comme implémentation, en attendant qu'une version ECS pure soit livrée. C'est exactement la technique qu'utilise notre démo Megacity. Le chargement, la lecture en continu, l'extraction, la création de LOD et l'animation sont réalisés avec des systèmes ECS purs, mais le dessin final ne l'est pas.

Vous pouvez donc les mélanger et les assortir. Ce qui est formidable, c'est que vous pouvez déjà profiter des avantages du codegen Burst et des performances ECS pour votre code de jeu, au lieu d'avoir à attendre que nous livrions des versions ECS pures de tous les sous-systèmes. Ce qui n'est pas génial, c'est que dans cette phase de transition, vous pouvez voir et sentir cette friction que vous "utilisez deux mondes différents qui sont collés l'un à l'autre".

Nous livrerons tout le code source de nos sous-systèmes ECS HPC# sous forme de paquets. Vous pouvez inspecter, déboguer et modifier chaque sous-système, ainsi que contrôler plus finement le moment où vous souhaitez mettre à niveau tel ou tel sous-système. Vous pouvez, par exemple, mettre à jour le paquet du sous-système physique sans mettre à jour quoi que ce soit d'autre.

Qu'adviendra-t-il des objets du jeu ?

Les objets de jeu ne vont nulle part. Depuis plus d'une décennie, des jeux extraordinaires ont été livrés avec succès sur ce système. Cette fondation ne va nulle part.

Ce qui va changer, c'est qu'au fil du temps, notre énergie pour apporter des améliorations va basculer du monde des objets de jeu vers le monde de l'ECS.

Facilité d'utilisation de l'API / Modèle de base

Un point commun, très valable, que les gens soulèvent lorsqu'ils regardent le SCE est qu'il y a beaucoup de dactylographie. Beaucoup de code passe-partout qui vous empêche d'atteindre vos objectifs.

De nombreuses améliorations se profilent à l'horizon, qui visent à supprimer la plupart des formules passe-partout et à simplifier l'expression de vos intentions. Nous n'avons pas encore mis en œuvre beaucoup d'entre eux car nous nous sommes concentrés sur les performances fondamentales, mais nous pensons qu'il n'y a pas de bonne raison pour que le code du jeu ECS ait beaucoup de code passe-partout, ou soit particulièrement plus difficile à écrire que l'écriture d'un MonoBehaviour.

Le projet Tiny a déjà mis en œuvre certaines de ces améliorations (comme une API d'itération basée sur des lambdas). En parlant de cela...

Quelle est la place de l'ECS du projet Tiny dans tout cela ?

Project Tiny s'appuiera sur le même ECS C# que celui dont il est question dans ce billet de blog. Le projet Tiny constituera pour nous une étape importante de l'ECS à plusieurs égards :

  • Il pourra fonctionner dans un environnement ECS complet. Un nouveau joueur qui n'a pas d'antécédents.
  • Cela signifie qu'il s'agit également d'un pur ECS et qu'il doit être livré avec tous les sous-systèmes ECS dont un vrai (petit) jeu a besoin.
  • Nous adopterons le support de l'éditeur du projet Tiny pour l'édition d'entités pour tous les scénarios ECS, et pas seulement pour le projet Tiny.
Nous rejoindre ?

Nous avons des offres d'emploi pour toutes les différentes parties de la pile DOTS, en particulier à Burbank et à Copenhague, consultez careers.unity.com.

N'oubliez pas non plus de nous rejoindre sur le forum Unity Entity Component System et C# Job System pour nous faire part de vos commentaires et obtenir des informations sur les fonctionnalités expérimentales et de prévisualisation.