# Component Creation

To create new components, we need to do a couple of things before starting coding them in the renderer. There is a step-by-step guide that you need to follow. I will list all the steps, and then you can follow each step with a longer explanation.

1. Create the proto definition in [@dcl/protocol](https://github.com/decentraland/protocol)
2. Generate the new proto TypeScript code in the [js-sdk-toolchain](https://github.com/decentraland/js-sdk-toolchain)
3. Create a test for `js-sdk-toolchain`
4. Generate the new proto C# code in the project
5. Code the new component
6. Ensure that the component follows the convention

## Create the proto definition in [@dcl/protocol](https://github.com/decentraland/protocol)

To create a definition, you must go to this repo: <https://github.com/decentraland/protocol>

1. Create the proto definition in this folder: <https://github.com/decentraland/protocol/tree/main/ecs/components>
2. Create a PR with the news changes

> ***NOTE:*** After creating the PR, a GitHub Bot will comment with the package link to test the PR. You can use that link for testing in the following steps

Things to take into account

* We are using proto 3, so all the definition of the proto must compile with their [syntax](https://developers.google.com/protocol-buffers/docs/proto3/)
* We have some common types that cannot be recreated
* The proto should have the basic definition
* You must add the following code to enumerate the component

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

Example of .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;
}
```

## Generate the new proto TypeScript code in the [js-sdk-toolchain](https://github.com/decentraland/js-sdk-toolchain)

Download the following repo: <https://github.com/decentraland/js-sdk-toolchain>

Inside it, navigate to `packages/dcl/ecs` (`https://github.com/decentraland/js-sdk-toolchain/tree/main/packages/%40dcl/ecs`)

And there, you can execute

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

Or the command generated by the GitHub Bot in your @dcl/protocol PR (this must be temporal for testing the PR's).

Then run the following commands at the root of the `js-sdk-toolchain` project:

```
make install
make build
```

And push the generated code.

## Generate the new proto C# code in the project

To generate the C# code, we need to go to the `protocol-gen` path in the root of the `@decentraland/unity-renderer` repository. And execute the following commands:

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

To upgrade to the latest version of the `@dcl/protocol` (main branch), we should update using

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

To test a PR, we can use a URL generated by the GitHub Bot in the `@dcl/protocol` PR: Example:

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

After merging @dcl/protocol PR, we must use the `@dcl/protocol@next` and generate the code.

## Code the new component

Now it is time to implement the functionality of the new component.

The components have five essential parts.

* ComponentID The ID that the component will use. It must be unique and generated from the proto definition
* Model This is the model of the component. This is the data that we will use to handle the component. It has been auto-generated with the proto generation and it has the same name of the proto file with a PB in front. For example, if you have a `BoxShape.proto` definition the generated class of the model will be `PBBoxShape`
* Component Handler The component handler will manage all the functionality of the component. In this class you must implement the `IECSComponentHandler<ModelClass>` (ModelClass is the model. It is a generated class from the proto, the name will be PB + name of the file .proto). This interface has 3 method that are important to implement in order to create a component

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

* Serializer Each component is responsible to implement his serialization and deserialization of the component. This serializer must be able to serialize/deserialize to byte array
* Register This will register the component into the system, connecting it to the system. This register will register the component in the factory and the component writer

The design of the components is to avoid inheritance so we encourage to use pure functions as much as possible

In order to create them, We must follow the next steps

1. Create the component folder and assembly. We have all the components unders the follow folder `DCLPlugins/ECS7/ECSComponents`. You need to create a folder and a new assembly that will hold the component
2. In the new assembly, you must reference the following one `DCL.ECSComponents.Data`. This will reference the new model of the component that you just updated
3. You must create the component handler with all the logic (Take a look at `ECSBoxShapeComponentHandler.cs` as an example)
4. You must create the serializer class (probably you can copy it from another class and adapt to your)
5. You must create the register class

```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. Add the new register to the `ECS7ComponentsComposer` class with his corresponding 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();
        }
    }
```

And now you have your component added and working!

## Ensure that the component follows the convention

There is some checklist that we need to have into account while developing new components, this part tries to summarize them.

* Unit test All the components must include unit test that cover its functionality, dispose and the serialization/deserialization at least to ensure that the component will work
* Take into account what happens when the component is not inside the scene (Check `SceneBoundariesChecker` class for more info)
* If the component renders something into the world, It must add the rendereable info to the data store, this way we add the information of the renderer to the scene so it can counts toward the limit
* If the component renders something into the world, It must add the `MeshesInfo` to the entity
* It must be as perfomant as possible. This code will be executed lots of time so we need to ensure that everything work as smooth as possible
* It must work with `Hot reload` in the preview mode. If you has coded the `OnComponentRemoved` correctly, this will work out of the box, but the hot reload it a way to test that everything work fine with the dispose of the component
* If the component uses a resource, you must implement the resource management with an `AssetPromiseKeeper`. The component should notify the `AssetPromiseKeeper` when the resource is used and when it is not longer used
