# Component Creation

Para criar novos componentes, precisamos fazer algumas coisas antes de começar a codificá-los no renderer. Há um guia passo a passo que você precisa seguir. Vou listar todos os passos e, em seguida, você pode seguir cada passo com uma explicação mais longa.

1. Crie a definição proto em [@dcl/protocol](https://github.com/decentraland/protocol)
2. Gere o novo código proto TypeScript no [js-sdk-toolchain](https://github.com/decentraland/js-sdk-toolchain)
3. Crie um teste para `js-sdk-toolchain`
4. Gere o novo código proto C# no projeto
5. Codifique o novo componente
6. Garanta que o componente siga a convenção

## Crie a definição proto em [@dcl/protocol](https://github.com/decentraland/protocol)

Para criar uma definição, você deve ir para este repositório: <https://github.com/decentraland/protocol>

1. Crie a definição proto nesta pasta: <https://github.com/decentraland/protocol/tree/main/ecs/components>
2. Crie um PR com as novas alterações

> ***NOTA:*** Após criar o PR, um Bot do GitHub comentará com o link do pacote para testar o PR. Você pode usar esse link para testar nas etapas seguintes

Coisas a ter em conta

* Estamos usando proto 3, então toda a definição do proto deve compilar com sua [syntax](https://developers.google.com/protocol-buffers/docs/proto3/)
* Temos alguns tipos comuns que não podem ser recriados
* O proto deve ter a definição básica
* Você deve adicionar o seguinte código para enumerar o componente

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

Exemplo 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;
}
```

## Gere o novo código proto TypeScript no [js-sdk-toolchain](https://github.com/decentraland/js-sdk-toolchain)

Baixe o seguinte repositório: <https://github.com/decentraland/js-sdk-toolchain>

Dentro dele, navegue para `packages/dcl/ecs` (`https://github.com/decentraland/js-sdk-toolchain/tree/main/packages/%40dcl/ecs`)

E lá, você pode executar

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

Ou o comando gerado pelo Bot do GitHub no seu PR do @dcl/protocol (isso deve ser temporário para testar os PRs).

Então execute os seguintes comandos na raiz do `js-sdk-toolchain` project:

```
make install
make build
```

E envie (push) o código gerado.

## Gere o novo código proto C# no projeto

Para gerar o código C#, precisamos ir para o `protocol-gen` caminho na raiz do `@decentraland/unity-renderer` repositório. E executar os seguintes comandos:

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

Para atualizar para a versão mais recente do `@dcl/protocol` (main branch), devemos atualizar usando

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

Para testar um PR, podemos usar uma URL gerada pelo Bot do GitHub no `@dcl/protocol` PR: Exemplo:

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

Após mesclar o PR do @dcl/protocol, devemos usar o `@dcl/protocol@next` e gerar o código.

## Codifique o novo componente

Agora é hora de implementar a funcionalidade do novo componente.

Os componentes têm cinco partes essenciais.

* ComponentID O ID que o componente usará. Deve ser único e gerado a partir da definição proto
* Model Este é o modelo do componente. Esses são os dados que usaremos para manipular o componente. Ele foi gerado automaticamente com a geração do proto e tem o mesmo nome do arquivo proto com um PB na frente. Por exemplo, se você tiver um `BoxShape.proto` definition a classe gerada do modelo será `PBBoxShape`
* Component Handler O component handler gerenciará toda a funcionalidade do componente. Nesta classe você deve implementar o `IECSComponentHandler<ModelClass>` (ModelClass é o modelo. É uma classe gerada a partir do proto, o nome será PB + nome do arquivo .proto). Esta interface tem 3 métodos que são importantes implementar para criar um componente

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

* Serializer Cada componente é responsável por implementar sua serialização e desserialização do componente. Este serializer deve ser capaz de serializar/desserializar para um array de bytes
* Register Isto registrará o componente no sistema, conectando-o ao sistema. Este registro irá registrar o componente na factory e no component writer

O design dos componentes evita herança, então incentivamos o uso de funções puras tanto quanto possível

Para criá-los, devemos seguir os próximos passos

1. Crie a pasta do componente e a assembly. Temos todos os componentes sob a seguinte pasta `DCLPlugins/ECS7/ECSComponents`. Você precisa criar uma pasta e uma nova assembly que conterá o componente
2. Na nova assembly, você deve referenciar a seguinte `DCL.ECSComponents.Data`. Isso referenciará o novo modelo do componente que você acabou de atualizar
3. Você deve criar o component handler com toda a lógica (Dê uma olhada em `ECSBoxShapeComponentHandler.cs` como exemplo)
4. Você deve criar a classe serializer (provavelmente você pode copiá-la de outra classe e adaptar ao seu)
5. Você deve criar a classe 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. Adicione o novo register ao `ECS7ComponentsComposer` class com seu ID correspondente

```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();
        }
    }
```

E agora você tem seu componente adicionado e funcionando!

## Garanta que o componente siga a convenção

Há uma lista de verificação que precisamos ter em conta ao desenvolver novos componentes; esta parte tenta resumi-las.

* Unit test Todos os componentes devem incluir unit tests que cubram sua funcionalidade, dispose e a serialização/desserialização pelo menos para garantir que o componente funcionará
* Leve em conta o que acontece quando o componente não está dentro da cena (Verifique `SceneBoundariesChecker` classe para mais informações)
* Se o componente renderiza algo no world, ele deve adicionar a informação renderizável ao data store, dessa forma adicionamos a informação do renderer à cena para que conte para o limite
* Se o componente renderiza algo no world, ele deve adicionar o `MeshesInfo` à entidade
* Deve ser o mais performático possível. Este código será executado muitas vezes, então precisamos garantir que tudo funcione o mais suavemente possível
* Deve funcionar com `Hot reload` no modo preview. Se você codificou o `OnComponentRemoved` corretamente, isso funcionará automaticamente, mas o hot reload é uma forma de testar que tudo funciona bem com o dispose do componente
* Se o componente usa um recurso, você deve implementar o gerenciamento de recursos com um `AssetPromiseKeeper`. O componente deve notificar o `AssetPromiseKeeper` quando o recurso é usado e quando não é mais usado
