# Creación de componentes

Para crear nuevos componentes, necesitamos hacer un par de cosas antes de empezar a codificarlos en el renderer. Hay una guía paso a paso que debes seguir. Enumeraré todos los pasos y luego podrás seguir cada paso con una explicación más larga.

1. Crear la definición proto en [@dcl/protocol](https://github.com/decentraland/protocol)
2. Generar el nuevo código proto TypeScript en el [js-sdk-toolchain](https://github.com/decentraland/js-sdk-toolchain)
3. Crear una prueba para `js-sdk-toolchain`
4. Generar el nuevo código proto C# en el proyecto
5. Codificar el nuevo componente
6. Asegurarse de que el componente sigue la convención

## Crear la definición proto en [@dcl/protocol](https://github.com/decentraland/protocol)

Para crear una definición, debes ir a este repo: <https://github.com/decentraland/protocol>

1. Crea la definición proto en esta carpeta: <https://github.com/decentraland/protocol/tree/main/ecs/components>
2. Crear un PR con los cambios nuevos

> ***NOTA:*** Después de crear el PR, un Bot de GitHub comentará con el enlace del paquete para probar el PR. Puedes usar ese enlace para las pruebas en los siguientes pasos

Cosas a tener en cuenta

* Estamos usando proto 3, por lo que toda la definición del proto debe compilar con su [syntax](https://developers.google.com/protocol-buffers/docs/proto3/)
* Tenemos algunos tipos comunes que no se pueden recrear
* El proto debe tener la definición básica
* Debes añadir el siguiente código para enumerar el componente

```
import "common/id.proto";
option (ecs_component_id) = 1100;
```

Ejemplo de .proto

```
syntax = "proto3";

import "common/id.proto";
option (ecs_component_id) = 1020;

message PBAudioSource {
  optional bool playing = 1;
  optional float volume = 2; // default=1.0f
  optional bool loop = 3;
  optional float pitch = 4; // default=1.0f
  string audio_clip_url = 5;
}
```

## Generar el nuevo código proto TypeScript en el [js-sdk-toolchain](https://github.com/decentraland/js-sdk-toolchain)

Descarga el siguiente repo: <https://github.com/decentraland/js-sdk-toolchain>

Dentro de él, navega a `packages/dcl/ecs` (`https://github.com/decentraland/js-sdk-toolchain/tree/main/packages/%40dcl/ecs`)

Y allí, puedes ejecutar

```
npm install @dcl/protocol@next
```

O el comando generado por el Bot de GitHub en tu PR de @dcl/protocol (esto debe ser temporal para probar los PR).

Luego ejecuta los siguientes comandos en la raíz del `js-sdk-toolchain` project:

```
make install
make build
```

Y sube el código generado.

## Generar el nuevo código proto C# en el proyecto

Para generar el código C#, necesitamos ir al `protocol-gen` path in the root of the `@decentraland/unity-renderer` repository. And execute the following commands:

```
npm install
npm run build-components
```

Para actualizar a la última versión del `@dcl/protocol` (main branch), deberíamos actualizar usando

```
npm install @dcl/protocol@next
npm run build-components
```

Para probar un PR, podemos usar una URL generada por el Bot de GitHub en el `@dcl/protocol` PR: Ejemplo:

```
npm install "https://sdk-team-cdn.decentraland.org/@dcl/protocol/branch//dcl-protocol-1.0.0-3143233696.commit-45f1290.tgz"
npm run build-components
```

Después de fusionar el PR de @dcl/protocol, debemos usar el `@dcl/protocol@next` y generar el código.

## Codificar el nuevo componente

Ahora es momento de implementar la funcionalidad del nuevo componente.

Los componentes tienen cinco partes esenciales.

* ComponentID El ID que usará el componente. Debe ser único y generado a partir de la definición proto
* Model Este es el modelo del componente. Estos son los datos que usaremos para manejar el componente. Ha sido autogenerado con la generación proto y tiene el mismo nombre del archivo proto con un PB al frente. Por ejemplo, si tienes un `BoxShape.proto` definición la clase generada del modelo será `PBBoxShape`
* Component Handler El manejador del componente administrará toda la funcionalidad del componente. En esta clase debes implementar el `IECSComponentHandler<ModelClass>` (ModelClass es el modelo. Es una clase generada a partir del proto, el nombre será PB + nombre del archivo .proto). Esta interfaz tiene 3 métodos que son importantes de implementar para crear un componente

```
        void OnComponentCreated(IParcelScene scene, IDCLEntity entity);
        void OnComponentRemoved(IParcelScene scene, IDCLEntity entity);
        void OnComponentModelUpdated(IParcelScene scene, IDCLEntity entity, ModelType model);
```

* Serializer Cada componente es responsable de implementar su serialización y deserialización del componente. Este serializer debe ser capaz de serializar/deserializar a array de bytes
* Register Esto registrará el componente en el sistema, conectándolo al sistema. Este registro añadirá el componente en la factory y el component writer

El diseño de los componentes busca evitar la herencia, por lo que recomendamos usar funciones puras tanto como sea posible

Para crearlos, debemos seguir los siguientes pasos

1. Crea la carpeta del componente y la assembly. Tenemos todos los componentes bajo la siguiente carpeta `DCLPlugins/ECS7/ECSComponents`. Necesitas crear una carpeta y una nueva assembly que contendrá el componente
2. En la nueva assembly, debes referenciar la siguiente `DCL.ECSComponents.Data`. Esto referenciará el nuevo modelo del componente que acabas de actualizar
3. Debes crear el component handler con toda la lógica (Mira `ECSBoxShapeComponentHandler.cs` como ejemplo)
4. Debes crear la clase serializer (probablemente puedas copiarla de otra clase y adaptarla a la tuya)
5. Debes crear la clase register

```sh
   public static class AudioSourceSerializer
    {
        public static byte[] Serialize(PBAudioSource model)
        {
            int size = model.CalculateSize();
            byte[] buffer = new byte[size];
            CodedOutputStream output = new CodedOutputStream(buffer);
            model.WriteTo(output);
            return buffer;
        }

        public static PBAudioSource Deserialize(object data)
        {
            return PBAudioSource.Parser.ParseFrom((byte[])data);
        }
    }
```

6. Agrega el nuevo register al `ECS7ComponentsComposer` class con su correspondiente ID

```sh
   public class ECS7ComponentsComposer : IDisposable
    {
        private readonly TransformRegister transformRegister;
        private readonly SphereShapeRegister sphereShapeRegister;
        private readonly BoxShapeRegister boxShapeRegister;
        private readonly PlaneShapeRegister planeShapeRegister;
        private readonly CylinderShapeRegister cylinderShapeRegister;
        private readonly AudioStreamRegister audioStreamRegister;
        private readonly AudioSourceRegister audioSourceRegister;

        public ECS7ComponentsComposer(ECSComponentsFactory componentsFactory, IECSComponentWriter componentsWriter)
        {
            transformRegister = new TransformRegister(ComponentID.TRANSFORM, componentsFactory, componentsWriter);
            sphereShapeRegister = new SphereShapeRegister(ComponentID.SPHERE_SHAPE, componentsFactory, componentsWriter);
            boxShapeRegister = new BoxShapeRegister(ComponentID.BOX_SHAPE, componentsFactory, componentsWriter);
            planeShapeRegister = new PlaneShapeRegister(ComponentID.PLANE_SHAPE, componentsFactory, componentsWriter);
            cylinderShapeRegister = new CylinderShapeRegister(ComponentID.CYLINDER_SHAPE, componentsFactory, componentsWriter);
            audioStreamRegister = new AudioStreamRegister(ComponentID.AUDIO_STREAM, componentsFactory, componentsWriter);
            audioSourceRegister = new AudioSourceRegister(ComponentID.AUDIO_SOURCE, componentsFactory, componentsWriter);
        }

        public void Dispose()
        {
            transformRegister.Dispose();
            sphereShapeRegister.Dispose();
            boxShapeRegister.Dispose();
            planeShapeRegister.Dispose();
            cylinderShapeRegister.Dispose();
            audioStreamRegister.Dispose();
            audioSourceRegister.Dispose();
        }
    }
```

¡Y ahora tienes tu componente añadido y funcionando!

## Asegurarse de que el componente sigue la convención

Hay una lista de verificación que debemos tener en cuenta mientras desarrollamos nuevos componentes; esta parte intenta resumirla.

* Unit test Todos los componentes deben incluir pruebas unitarias que cubran su funcionalidad, el dispose y la serialización/deserialización al menos para asegurar que el componente funcionará
* Ten en cuenta lo que sucede cuando el componente no está dentro de la escena (Revisa `SceneBoundariesChecker` clase para más información)
* Si el componente renderiza algo en el world, debe añadir la información renderizable al data store, de esta manera añadimos la información del renderer a la escena para que cuente hacia el límite
* Si el componente renderiza algo en el world, debe añadir el `MeshesInfo` a la entidad
* Debe ser lo más eficiente posible. Este código se ejecutará muchas veces, por lo que debemos asegurar que todo funcione lo más fluido posible
* Debe funcionar con `Hot reload` en el modo preview. Si has codificado el `OnComponentRemoved` correctamente, esto funcionará automáticamente, pero el hot reload es una forma de probar que todo funciona bien con el dispose del componente
* Si el componente usa un recurso, debes implementar la gestión de recursos con un `AssetPromiseKeeper`. El componente debe notificar al `AssetPromiseKeeper` cuando el recurso se está usando y cuando ya no se usa
